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

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

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

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

// 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);
    
    • Advantages:
      • Keeps shaders simple.
      • Простой код для вычисления матриц вне шейдеров (например, с использованием GLKit).
    • Disadvantages:
      • Massively increases the size of my point sprite data (from 16 to 80 bytes/point for a 4x4 matrix; to 52 bytes/point for a 3x3 matrix... I believe it's possible to use a 3x3 rotation matrix?). This could potentially cause my app to crash 3-5 times sooner!
      • Выдвигает гораздо больше вычислений на ЦП (сотни / тысячи матричных вычислений на кадр).


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

    • Advantages:
      • Keeps point sprite data size small (from 16 to 20 bytes/point).
      • Передает тяжелую матричную математику на GPU.
    • Disadvantages:
      • Need to write custom GLSL function to create rotation matrix. Not a massive problem, but my matrix maths is rusty, so this could be error prone, especially if I'm trying to figure out the 3x3 matrix solution...
      • Учитывая, что это должно происходить на сотнях / тысячах вершин, будет ли это серьезным тормозом для производительности (несмотря на то, что это обрабатывается графическим процессором)?


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

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


person Stuart    schedule 19.10.2012    source источник


Ответы (4)


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

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

Недостатки, о которых я догадывался, похоже, не применимы. Я не заметил снижения производительности даже на iPad 1-го поколения. Вычисление матрицы в 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;

    //...
}

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

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

person Stuart    schedule 11.01.2013

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

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

person hanno    schedule 11.01.2013
comment
Привет, я на самом деле не пробовал это, и это звучит как разумное решение. С момента публикации вопроса я решил свою проблему, но забыл опубликовать ответ (спасибо, что напомнили мне!). В итоге я вычислил матрицу вращения в вершинном шейдере. Падение производительности незначительно даже на iPad 1-го поколения, и мои структуры данных остаются небольшими. Ваш метод также может работать, но с одной стороны, он увеличит использование моей текстурной памяти (мне понадобится более 200 поворотов для каждого спрайта). Мне нравится решение вершинного шейдера, так как мне не нужно возиться со спрайтами, а производительность приемлемая. - person Stuart; 11.01.2013

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

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);
person nminhtai    schedule 14.06.2013

FWIW, это предварительно вычисленная матрица 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 имеют формат основного столбца.

person Chen Lim    schedule 29.06.2013