OpenGL ES 2.0 точечные спрайты с различными поворотами - вычислить матрицу в шейдере?

Я пытаюсь найти решение, которое позволит мне вращать точечные спрайты вокруг оси z с переменным атрибутом (то есть с равномерным не подойдет).

В моем приложении у меня есть много сотен / тысяч точечных спрайтов, нарисованных за кадр, которые затем сохраняются в VBO (вполне возможно, что они получат>1 000 000). Поэтому я ищу лучший компромисс между использованием памяти и производительностью.

Текущие вершинные и фрагментные шейдеры выглядят так:

// VERTEX SHADER
attribute vec4 a_position;
attribute vec4 a_color;
attribute float a_size;
uniform mat4 u_mvpMatrix;
varying vec4 v_color;

void main()
{
    v_color = a_color;
    gl_Position = u_mvpMatrix * a_position;
    gl_PointSize = a_size;
}


// FRAGMENT SHADER
precision mediump float;
uniform sampler2D s_texture;
varying vec4 v_color;

void main()
{
    vec4 textureColor = texture2D(s_texture, gl_PointCoord);
    gl_FragColor = v_color * textureColor;
}

В настоящее время я могу представить следующие возможности:

  • Добавить mat4 rotMatrix приписать мои точечные данные спрайта. Передайте это фрагментному шейдеру и поверните каждый фрагмент:

    vec2 texCoord = (rotMatrix * vec4(gl_PointCoord, 0, 1)).xy
    gl_FragColor = v_color * texture2D(s_texture, texCoord);
    
    • Преимущества:
      • Держит шейдеры простыми.
      • Простой код для вычисления матриц вне шейдеров (используя GLKit например).
    • Недостатки:
      • Значительно увеличивает размер моих точечных данных спрайта (с 16 до 80 байт / точка для матрицы 4x4; до 52 байт / точка для матрицы 3x3... Я считаю, что возможно использовать матрицу вращения 3x3?). Это может привести к сбою приложения в 3-5 раз раньше!
      • Выдвигает намного больше вычислений на процессор (сотни / тысячи матричных вычислений на кадр).


  • Добавить float angle приписать мои точечные данные спрайта, затем вычислить матрицу вращения в вершинном шейдере. Передайте матрицу вращения фрагментному шейдеру, как указано выше.

    • Преимущества:
      • Сохраняет размер данных точечного спрайта небольшим (от 16 до 20 байт / точка).
      • Толкает тяжелую математику с подъемной матрицей в графический процессор.
    • Недостатки:
      • Нужно написать собственную функцию GLSL для создания матрицы вращения. Не большая проблема, но моя матричная математика ржавая, так что это может быть подвержено ошибкам, особенно если я пытаюсь найти матричное решение 3х3...
      • Учитывая, что это должно происходить на сотнях / тысячах вершин, будет ли это серьезным тормозом для производительности (несмотря на то, что он обрабатывается графическим процессором)?


  • Я мог бы реально справиться с 1 байтом для атрибута угла (было бы достаточно 255 различных углов). Есть ли способ, которым я мог бы использовать какой-то поиск, чтобы мне не нужно было без необходимости пересчитывать одни и те же матрицы вращения? Хранение констант в вершинном шейдере было моей первой мыслью, но я не хочу начинать добавлять операторы ветвления в мои шейдеры.

Есть мысли по поводу хорошего подхода?

4 ответа

Решение

Решение, которое я выбрал в конце, было 2-м из вопроса: вычислить матрицу вращения в вершинном шейдере. Это имеет следующие преимущества:

  • Сохраняет точечный размер данных спрайта маленьким.
  • Расчеты вращения выполняются графическим процессором.

Недостатки, о которых я догадывался, похоже, не применяются. Я не заметил снижения производительности, даже на iPad первого поколения. Матричный расчет в GLSL несколько громоздок, но работает нормально. В интересах всех, кто пытается сделать то же самое, вот соответствующая часть вершинного шейдера:

//...
attribute float a_angle;
varying mat4 v_rotationMatrix;

void main()
{
    //...

    float cos = cos(a_angle);
    float sin = sin(a_angle);
    mat4 transInMat = mat4(1.0, 0.0, 0.0, 0.0,
                           0.0, 1.0, 0.0, 0.0,
                           0.0, 0.0, 1.0, 0.0,
                           0.5, 0.5, 0.0, 1.0);
    mat4 rotMat = mat4(cos, -sin, 0.0, 0.0,
                       sin, cos, 0.0, 0.0,
                       0.0, 0.0, 1.0, 0.0,
                       0.0, 0.0, 0.0, 1.0);
    mat4 resultMat = transInMat * rotMat;
    resultMat[3][0] = resultMat[3][0] + resultMat[0][0] * -0.5 + resultMat[1][0] * -0.5;
    resultMat[3][1] = resultMat[3][1] + resultMat[0][1] * -0.5 + resultMat[1][1] * -0.5;
    resultMat[3][2] = resultMat[3][2] + resultMat[0][2] * -0.5 + resultMat[1][2] * -0.5;
    v_rotationMatrix = resultMat;

    //...
}

Учитывая отсутствие заметного снижения производительности, это решение является идеальным, так как нет необходимости создавать текстурные карты / поиски и использовать дополнительную память, а остальная часть кода остается чистой и простой.

Я не могу сказать, что нет недостатков в расчете матрицы для каждой вершины (например, сокращение времени автономной работы), и производительность может быть проблемой в разных сценариях, но это хорошо для того, что мне нужно.

Задумывались ли вы об использовании различных предварительно рассчитанных и повернутых текстур (текстурный атлас)? Если для достижения эффекта, которого вы пытаетесь достичь, достаточно нескольких углов, это будет очень быстрое решение.

С другой стороны, существует ограничение производительности для вычисления координат текстуры в фрагментном шейдере (косвенный поиск текстуры). Это может быть не важно для вашего случая, но это стоит иметь в виду.

Вот ваша предварительно умноженная матрица вращения:

v_rotationMatrix = mat3(cos, sin, 0.0,
                        -sin, cos, 0.0,
                        (sin-cos+1.0)*0.5, (-sin-cos+1.0)*0.5, 1.0);

Кстати, это предварительно вычисленная матрица 3x3, которая соответствует коду Стюарта:

v_rotationMatrix = mat3 (cos, -sin, 0.0, sin, cos, 0.0, (1.0-cos-sin) * 0,5, (1,0+sin-cos)*0,5, 1,0);

Обратите внимание, что матрицы glsl представлены в основном формате столбца.

Другие вопросы по тегам