当使用glDrawElements时,如何在GLSL中将法线传递给顶点着色器

3
我正在练习制作一个简单的3D游戏,但在使用索引渲染时传递法向量到着色器时遇到了问题。对于一个多边形的每个面,在每个顶点处都会有相同的法向量值。对于具有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);

1
索引在顶点、法线和UV之间共享。您需要更改数据以适应此情况。 - TheAmateurProgrammer
你的答案和@KillianDS为我解决了问题。看来我需要停止尝试使用8个独特的位置,而是最终会得到24个位置,其中12个位置在两个三角形之间共享。 - Clintonio
1个回答

8
对于一个有8个顶点的立方体,将会有36个法线(因为每个面由两个三角形渲染)。
如果我错了,请纠正我,但我只看到6个法线,每个面一个。该面上的所有顶点都将使用相同的法线。
使用索引绘图,我只能传递8个法线,每个顶点一个。这不允许我传递表面法线,只能是平均的顶点法线。
这就是你推理失败的地方。你不仅要把顶点位置传递给着色器,还要传递一堆使顶点独特的顶点属性。
所以,如果你使用相同的顶点位置6次(通常情况下会这样做),但每次使用不同的法线(实际上两个三角形将共享相同的数据),你实际上应该发送该顶点的所有数据6次,包括位置的重复。
话虽如此,你不需要36个,你只需要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,
                      ... }

请注意,在normalspositions中有重复,但在同一位置上的两者组合是唯一的。

这些都很棒,但我不确定如何实际编写代码。如果我使用glDrawElements,有8个唯一的顶点(位置)和36个索引,那么在C++代码中,如何将正确的法向量值发送到每个索引?如果您查看我的代码,您将看到一个立方体将存储确切的8个唯一位置和法线值,并且索引将生成36个总顶点。这是我的问题,我完全理解有多少个唯一的法线,我只需要知道如何编码它。编辑:我看到我需要更改我的数据格式。谢谢! - Clintonio
3
我看不到任何顶点的计算,只有缓冲区。现在你将拥有两个缓冲区,每个缓冲区都有24个元素,而不是一个包含8个元素和另一个包含36个元素的缓冲区,还有一个包含36个索引的索引缓冲区。 - KillianDS
确实。我曾考虑使用24个元素,但我说服自己一定有其他方法。谢谢你让我开了眼界。我已经接受了你的答案。我以为我可能缺少某些函数的知识或对图形的理解,但实际上这只是我的固执和渴望找到从未存在的某种优化的问题。这对我帮助很大。 - Clintonio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接