【问题标题】:How to pass normals to a Vertex Shader in GLSL when using glDrawElements使用glDrawElements时如何将法线传递给GLSL中的顶点着色器
【发布时间】:2012-08-15 17:12:27
【问题描述】:

我正在构建一个简单的 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);

【问题讨论】:

  • 索引在顶点、法线和 UV 之间共享。您必须更改数据以适应这种情况。
  • 您的回答与@KillianDS 一起为我解决。似乎我需要停止尝试使用 8 个独特的位置,而是最终得到 24 个,其中 12 个在两个三角形之间共享。

标签: c++ opengl glsl shader lighting


【解决方案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,
                      ... }

注意normalspositions内部虽然有重复,但同一位置的组合是唯一的。

【讨论】:

  • 这一切都很好,但我不确定如何实际编写代码。如果我使用具有 8 个唯一顶点(位置)和 36 个索引的 glDrawElements,我如何在 C++ 代码中将正确的法线值发送到每个索引?如果您查看我的代码,您将看到为一个立方体存储了 8 个唯一位置和正常值,并且索引将生成 36 个总顶点。这是我问题的真正部分,我完全理解有多少独特的法线,我只需要知道如何编码。编辑:我看到我需要正式更改我的数据。谢谢!
  • 我看不到任何顶点的计算,只有缓冲区。您现在将拥有两个每个包含 24 个元素的缓冲区,而不是一个包含 8 个元素和一个包含 36 个元素的缓冲区。并且仍然是一个具有 36 个索引的索引缓冲区。
  • 确实如此。我曾考虑过使用 24 个元素,但我说服自己一定有另一种方法。谢谢你让我大开眼界。我已经接受了你的回答。我以为我可能错过了某些功能的知识,或者对图形的一些理解,但实际上这是我自己的固执和渴望找到某种从未存在过的优化的问题。这对我帮助很大。
猜你喜欢
  • 1970-01-01
  • 2015-02-24
  • 2013-02-03
  • 1970-01-01
  • 2014-11-22
  • 1970-01-01
  • 1970-01-01
  • 2020-05-26
  • 1970-01-01
相关资源
最近更新 更多