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 байт / точка).
- Толкает тяжелую математику с подъемной матрицей в графический процессор.
- Сохраняет размер данных точечного спрайта небольшим (от 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 представлены в основном формате столбца.