GLSL шейдер генерация нормалей
Привет, я пишу приложение для 3D моделирования и хочу ускорить рендеринг в OpenGL. В настоящее время я использую glBegin/glEnd, что очень медленно и не рекомендуется. Мне нужно рисовать очень быстрые модели с плоским затенением. Я генерирую нормали на процессоре каждый кадр. Это очень медленно. Я пытался использовать glDrawElements с индексированной геометрией, но есть проблема при генерации нормалей, потому что нормали указаны в вершине, а не на уровне треугольника.
Другая идея заключалась в том, чтобы использовать GLSL для генерации нормали на графическом процессоре в геометрическом шейдере. Я написал этот код для нормального поколения:
#version 120
#extension GL_EXT_geometry_shader4 : enable
vec3 NormalFromTriangleVertices(vec3 triangleVertices[3])
{
// now is same as RedBook (OpenGL Programming Guide)
vec3 u = triangleVertices[0] - triangleVertices[1];
vec3 v = triangleVertices[1] - triangleVertices[2];
return cross(v, u);
}
void main()
{
// no change of position
// computes normal from input triangle and front color for that triangle
vec3 triangleVertices[3];
vec3 computedNormal;
vec3 normal, lightDir;
vec4 diffuse;
float NdotL;
vec4 finalColor;
for(int i = 0; i < gl_VerticesIn; i += 3)
{
for (int j = 0; j < 3; j++)
{
triangleVertices[j] = gl_PositionIn[i + j].xyz;
}
computedNormal = NormalFromTriangleVertices(triangleVertices);
normal = normalize(gl_NormalMatrix * computedNormal);
// hardcoded light direction
vec4 light = gl_ModelViewMatrix * vec4(0.0, 0.0, 1.0, 0.0);
lightDir = normalize(light.xyz);
NdotL = max(dot(normal, lightDir), 0.0);
// hardcoded
diffuse = vec4(0.5, 0.5, 0.9, 1.0);
finalColor = NdotL * diffuse;
finalColor.a = 1.0; // final color ignores everything, except lighting
for (int j = 0; j < 3; j++)
{
gl_FrontColor = finalColor;
gl_Position = gl_PositionIn[i + j];
EmitVertex();
}
}
EndPrimitive();
}
Когда я интегрировал шейдеры в свое приложение, улучшения скорости не произошло. Это было хуже, чем раньше. Я новичок в GLSL и шейдерах в целом, поэтому я не знаю, что я сделал неправильно. Я попробовал этот код на MacBook с Geforce 9400M.
Чтобы быть более понятным, это код, который я хочу заменить:
- (void)drawAsCommandsWithScale:(Vector3D)scale
{
float frontDiffuse[4] = { 0.4, 0.4, 0.4, 1 };
CGFloat components[4];
[color getComponents:components];
float backDiffuse[4];
float selectedDiffuse[4] = { 1.0f, 0.0f, 0.0f, 1 };
for (uint i = 0; i < 4; i++)
backDiffuse[i] = components[i];
glMaterialfv(GL_BACK, GL_DIFFUSE, backDiffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, frontDiffuse);
Vector3D triangleVertices[3];
float *lastDiffuse = frontDiffuse;
BOOL flip = scale.x < 0.0f || scale.y < 0.0f || scale.z < 0.0f;
glBegin(GL_TRIANGLES);
for (uint i = 0; i < triangles->size(); i++)
{
if (selectionMode == MeshSelectionModeTriangles)
{
if (selected->at(i))
{
if (lastDiffuse == frontDiffuse)
{
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, selectedDiffuse);
lastDiffuse = selectedDiffuse;
}
}
else if (lastDiffuse == selectedDiffuse)
{
glMaterialfv(GL_BACK, GL_DIFFUSE, backDiffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, frontDiffuse);
lastDiffuse = frontDiffuse;
}
}
Triangle currentTriangle = [self triangleAtIndex:i];
if (flip)
currentTriangle = FlipTriangle(currentTriangle);
[self getTriangleVertices:triangleVertices fromTriangle:currentTriangle];
for (uint j = 0; j < 3; j++)
{
for (uint k = 0; k < 3; k++)
{
triangleVertices[j][k] *= scale[k];
}
}
Vector3D n = NormalFromTriangleVertices(triangleVertices);
n.Normalize();
for (uint j = 0; j < 3; j++)
{
glNormal3f(n.x, n.y, n.z);
glVertex3f(triangleVertices[j].x, triangleVertices[j].y, triangleVertices[j].z);
}
}
glEnd();
}
Как видите, это очень неэффективно, но работает.triangles
это массив индексов в vertices
массив.
Я пытался использовать этот код для рисования, но у меня не может быть только один индексный массив, а не два (один для вершин и второй для нормалей).
glEnableClientState(GL_VERTEX_ARRAY);
uint *trianglePtr = (uint *)(&(*triangles)[0]);
float *vertexPtr = (float *)(&(*vertices)[0]);
glVertexPointer(3, GL_FLOAT, 0, vertexPtr);
glDrawElements(GL_TRIANGLES, triangles->size() * 3, GL_UNSIGNED_INT, trianglePtr);
glDisableClientState(GL_VERTEX_ARRAY);
Теперь, как я могу указать указатель на нормали, когда некоторые вершины совместно используются разными треугольниками, поэтому для них разные нормали?
3 ответа
Так что мне наконец удалось увеличить скорость рендеринга. Я пересчитываю нормали на процессоре, только когда меняются вершины или треугольники, что происходит только при работе в одной сетке, а не во всей сцене. Это не решение, которое я хотел, но в реальном мире это лучше, чем предыдущие подходы.
Я кэширую всю геометрию в отдельный массив нормалей и вершин, индексированное рисование не может быть использовано, потому что я хочу плоское затенение (проблема, аналогичная сглаживанию групп в 3ds max).
Я использую простой glDrawArrays
и для освещения вершинного шейдера, потому что я хочу в режиме треугольника другой цвет для выбранного треугольника и еще один для невыбранного, и нет никакого набора материалов (я не нашел ни одного).
Обычно вы не вычисляете нормали в каждом кадре, только когда меняется геометрия. И чтобы иметь одну нормаль на треугольник, просто установите одну и ту же нормаль для каждой вершины в треугольнике. Это означает, что вы не можете делить вершины между соседними треугольниками в вашей сетке, но это совсем не необычно для такого рода вещей.
Ваш вопрос заставляет меня вспомнить этот пост в блоге " Нормалы без нормалей".