C++ – How to pass normals to a Vertex Shader in GLSL when using glDrawElements

cglsllightingopenglshader

I am building a simple 3D game for practice, and I am having trouble passing normals to my shader when using indexed rendering. For each face of a polygon, at each vertex there would be the same normal value. For a cube with 8 vertices, there would be 6 * 6 = 36 normals (since each surface renders with two triangles). With indexed drawing I can only pass 8, one for each vertex. This does not allow me to pass surface normals, only averaged vertex normals.

How could I pass 36 different normals to the 36 different indexes? Using glDrawArrays is apparently slow so I have elected not to use it.

Here is my shader:

#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;
}

And here is the code I am currently using to bind to the 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);

This is my renderer:

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

Best Answer

For a cube with 8 vertices, there would be 6 * 6 = 36 normals (since each surface renders with two triangles).

Correct me if wrong, but I only see 6 normals, one per face. All vertices on that face will use the same normal.

With indexed drawing I can only pass 8, one for each vertex. This does not allow me to pass surface normals, only averaged vertex normals.

Here's where your reasoning fails. You do not just pass vertex locations to the shader, you pass a whole bunch of vertex attributes that make the vertex unique.

So if you use the same vertex location 6 times (which you'll often do), but with a different normal each time (actually two triangles will share the same data), you actually should send all the data for that vertex 6x, including duplicates of the location.

That being said, you don't need 36, you need 4*6=24 unique attributes. You have to send all vertices for each face separately because the normals differ and per face you have to differ between 4 positions. You could also see it as 8*3 because you have 8 positions that have to be replicated to handle 3 different normals.

So you'll end up with something like:

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,
                      ... }

Note that while within normals and positions there is repetition, but that the combination of both at the same position is unique.

Related Topic