Как передать нормали в вершинный шейдер в GLSL при использовании glDrawElements

Я создаю простую трехмерную игру для практики, и у меня возникают проблемы с передачей нормалей в шейдер при использовании индексированного рендеринга. Для каждой грани многоугольника в каждой вершине будет одно и то же нормальное значение. Для куба с 8 вершинами будет 6 * 6 = 36 нормалей (поскольку каждая поверхность отображается с двумя треугольниками). С индексированным рисунком я могу передать только 8, по одному на каждую вершину. Это не позволяет мне передавать нормали к поверхности, только усредненные нормали вершин.

Как я мог передать 36 разных нормалей 36 различным индексам? Использование glDrawArrays очевидно медленное, поэтому я решил не использовать его.

Вот мой шейдер:

#version 330

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 vertNormal;

smooth out vec4 colour;

uniform vec4 baseColour;
uniform mat4 modelToCameraTrans;
uniform mat3 modelToCameraLight;
uniform vec3 sunPos;

layout(std140) uniform Projection {
    mat4 cameraToWindowTrans;
};

void main() {
    gl_Position = cameraToWindowTrans * modelToCameraTrans * vec4(position, 1.0f);

    vec3 dirToLight   = normalize((modelToCameraLight * position) - sunPos);
    vec3 camSpaceNorm = normalize(modelToCameraLight * vertNormal);

    float angle = clamp(dot(camSpaceNorm, dirToLight), 0.0f, 1.0f);

    colour = baseColour * angle * 0.07;
}

А вот код, который я сейчас использую для привязки к VAO:

    glGenVertexArrays(1, &vertexArray);
    glBindVertexArray(vertexArray); 

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, polygonBuffer);

    // The position input to the shader is index 0
    glEnableVertexAttribArray(POSITION_ATTRIB);
    glVertexAttribPointer(POSITION_ATTRIB, 3, GL_FLOAT, GL_FALSE, 0, 0);

    // Add the vertex normal to the shader
    glBindBuffer(GL_ARRAY_BUFFER, vertexNormBuffer);

    glEnableVertexAttribArray(VERTEX_NORMAL_ATTRIB);
    glVertexAttribPointer(VERTEX_NORMAL_ATTRIB, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindVertexArray(0);

Это мой рендерер:

glBindVertexArray(vertexArray);
glDrawElements(GL_TRIANGLES, polygonVertexCount, GL_UNSIGNED_SHORT, 0);
glBindVertexArray(0);

person Clintonio    schedule 20.08.2012    source источник
comment
Индексы разделяются между вершинами, нормалями и UV. Вам придется изменить свои данные, чтобы приспособиться к этому.   -  person TheAmateurProgrammer    schedule 20.08.2012
comment
Ваш ответ вместе с @KillianDS решит эту проблему для меня. Кажется, мне нужно перестать пытаться использовать 8 уникальных позиций, вместо этого я получу 24, причем 12 из них поделены между двумя треугольниками.   -  person Clintonio    schedule 20.08.2012


Ответы (1)


Для куба с 8 вершинами будет 6 * 6 = 36 нормалей (поскольку каждая поверхность отображается с двумя треугольниками).

Поправьте меня, если ошиблись, но я вижу только 6 нормалей, по одной на лицо. Все вершины на этой грани будут использовать одну и ту же нормаль.

С индексированным рисунком я могу передать только 8, по одному на каждую вершину. Это не позволяет мне передавать нормали к поверхности, только усредненные нормали вершин.

Вот где ваши рассуждения терпят неудачу. Вы не просто передаете шейдеру местоположения вершин, вы передаете целый набор атрибутов вершин, которые делают вершину уникальной.

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

При этом вам не нужно 36, вам нужно 4*6=24 уникальных атрибутов. Вам нужно отправить все вершины для каждой грани отдельно, потому что нормали различаются и для каждой грани вам нужно различать 4 положения. Вы также можете увидеть это как 8 * 3, потому что у вас есть 8 позиций, которые необходимо воспроизвести для обработки 3 разных нормалей.

В итоге вы получите что-то вроде:

GLFloat positions[] = { 0.0f, 0.0f, 0.0f, 
                        0.0f, 0.0f, 0.0f, 
                        0.0f, 0.0f, 0.0f,
                        1.0f, 0.0f, 0.0f,
                        ...}
GLFloat normals[] = { 1.0f, 0.0f, 0.0f, 
                      0.0f, 1.0f, 0.0f, 
                      0.0f, 0.0f, 1.0f,
                      1.0f, 0.0f, 0.0f,
                      ... }

Обратите внимание, что в пределах normals и positions есть повторение, но их комбинация в одной и той же позиции уникальна.

person KillianDS    schedule 20.08.2012
comment
Это все здорово и все такое, но я не уверен, как на самом деле написать для этого код. Если я использую glDrawElements с 8 уникальными вершинами (позициями) и 36 индексами, как я могу в коде C ++ отправить правильные нормальные значения для каждого из этих индексов? Если вы посмотрите мой код, вы увидите, что для куба будет сохранено ровно 8 уникальных значений положения и нормальных значений, а индексы сгенерируют всего 36 вершин. Это настоящая часть моего вопроса, я прекрасно понимаю, сколько существует уникальных нормалей, мне просто нужно знать, как это кодировать. Изменить: я вижу, что мне нужно изменить мои данные формально. Спасибо! - person Clintonio; 20.08.2012
comment
Я вообще не вижу вычисления вершин, только буферы. Теперь у вас будет два буфера по 24 элемента каждый вместо буфера с 8 элементами и одного с 36 элементами. и по-прежнему индексный буфер с 36 индексами. - person KillianDS; 20.08.2012
comment
Верно. Я рассматривал возможность использования 24 элементов, но убедил себя, что должен быть другой способ. Спасибо, что открыли мне глаза. Я принял твой ответ. Я думал, что, возможно, мне не хватало некоторых знаний о какой-то функции или некоторого понимания графики, но на самом деле это было вопросом моего собственного упрямства и желания найти какую-то оптимизацию, которой никогда не было. Это мне очень помогло. - person Clintonio; 20.08.2012