英文:
OpenGL: where is the connection between indices and normals for proper lighting?
问题
I have a working application that displays 3D models in wireframe mode properly. Now I want to add a new mode where these models are shown as solids lit by a light source.
Changing the polygon mode from glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
to glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
does not do the job as the complete normals and lighting stuff is missing. This is clear for me so far. What I do not understand is where the normals are applied to the current geometries properly, meaning where/how does OpenGL know the provided normal coordinates belong to the coordinates of my former wireframe mode?
That's what I have changed so far:
a) A new shader program for my solid 3D model:
#version 130
in vec3 Normal;
in vec3 fpos;
out vec4 fragment_color;
const vec3 lightPos = vec3(0.0, 0.0, 5.0);
const vec3 diffColor = vec3(1.0, 0.5, 0.0);
const vec3 specColor = vec3(1.0, 1.0, 1.0);
void main () {
vec3 normal = normalize(Normal);
vec3 viewDir = normalize(-fpos);
if (dot(normal, viewDir) < 0.0) normal *= -1.0;
vec3 lightDir = normalize(lightPos - fpos);
float lamb = max(dot(lightDir, normal), 0.0);
float spec = 0.0;
if (lamb > 0.0) {
vec3 refDir = reflect(-lightDir, normal);
float specAngle = max(dot(refDir, viewDir), 0.0);
spec = pow(specAngle, 4.0);
}
fragment_color = vec4(lamb * diffColor + spec * specColor, 1.0);
}\0";
b) after setting the coordinates (this is the old, working code already used for my wireframes view)...
glBindVertexArray(entity->m_gl3Element.VAO);
glBindBuffer(GL_ARRAY_BUFFER, entity->m_gl3Element.coordVBO);
glBufferData(GL_ARRAY_BUFFER, m_vertexArray.size() * sizeof(float), m_vertexArray.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
...I now also have added the code for loading the normals data...
glBindBuffer(GL_ARRAY_BUFFER, entity->m_gl3Element.normalVBO);
glBufferData(GL_ARRAY_BUFFER, normalArray.size() * sizeof(float), normalArray.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, entity->m_gl3Element.normalVBO);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
...which finally is closed by a call to:
glBindVertexArray(0);
Later, the whole stuff is shown by a call to:
glDrawArrays(...)
After doing these modifications, my model appears solid but is still completely black, so the lighting/normals stuff is not working properly.
My problem: I do not understand where the normals are applied to the coordinates - is the magical index "2" from the calls glEnableVertexAttribArray(2)
and glVertexAttribPointer(2, ...)
doing this assignment? Does OpenGL always "know" this "2" are the normals?
The normals themselves should be fine as they come from a 3D model loaded from disk.
英文:
I have a working application that displays 3D models in wireframe mode properly. Now I want to add a new mode where these models are show as solids lit by a light source.
Changing the polygon mode from glPolygonMode(GL_FRONT_AND_BACK,GL_LINE) to glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ) does not do the job as the complete normals and lighting stuff is missing. This is clear for me so far. What I do not understand, is, where the normals are applied to the current geometries properly, means where/how does OpenGL know, the provided normal coordinates belong to the coordinates of my (former) wireframe mode?
That's hat I have changed so far:
a) A new shader program for my solid 3D model:
#version 130
in vec3 Normal;
in vec3 fpos;
out vec4 fragment_color;
const vec3 lightPos = vec3(0.0,0.0,5.0);
const vec3 diffColor = vec3(1.0,0.5,0.0);
const vec3 specColor = vec3(1.0,1.0,1.0);
void main () {
vec3 normal = normalize(Normal);
vec3 viewDir = normalize(-fpos);
if (dot(normal, viewDir) < 0.0) normal *= -1.0;
vec3 lightDir = normalize(lightPos - fpos);
float lamb = max(dot(lightDir, normal), 0.0);
float spec = 0.0;
if (lamb > 0.0) {
vec3 refDir = reflect(-lightDir, normal);
float specAngle = max(dot(refDir, viewDir), 0.0);
spec = pow(specAngle, 4.0);
}
fragment_color = vec4(lamb * diffColor + spec * specColor, 1.0);
}#version 130
in vec3 Normal;
in vec3 fpos;
out vec4 fragment_color;
const vec3 lightPos = vec3(0.0,0.0,5.0);
const vec3 diffColor = vec3(1.0,0.5,0.0);
const vec3 specColor = vec3(1.0,1.0,1.0);
void main () {
vec3 normal = normalize(Normal);
vec3 viewDir = normalize(-fpos);
if (dot(normal, viewDir) < 0.0) normal *= -1.0;
vec3 lightDir = normalize(lightPos - fpos);
float lamb = max(dot(lightDir, normal), 0.0);
float spec = 0.0;
if (lamb > 0.0) {
vec3 refDir = reflect(-lightDir, normal);
float specAngle = max(dot(refDir, viewDir), 0.0);
spec = pow(specAngle, 4.0);
}
fragment_color = vec4(lamb * diffColor + spec * specColor, 1.0);
}\0";
";
b) after setting the coordinates (this is the old, working code already used for my wireframes view)...
glBindVertexArray(entity->m_gl3Element.VAO);
glBindBuffer(GL_ARRAY_BUFFER,entity->m_gl3Element.coordVBO);
glBufferData(GL_ARRAY_BUFFER,m_vertexArray.size()*sizeof(float),m_vertexArray.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
...I now also have added the code for loading the normals data...
glBindBuffer(GL_ARRAY_BUFFER,entity->m_gl3Element.normalVBO);
glBufferData(GL_ARRAY_BUFFER, normalArray.size()*sizeof(float),normalArray.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER,entity->m_gl3Element.normalVBO);
glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,(void*)0);
...which finally is closed by a call to:
glBindVertexArray(0);
Later the whole stuff is shown by a call to
glDrawArrays(...)
After doing these modifications, my model appears solid but is still completely black, so the lighting/normals stuff is not working properly.
My problem: I do not understand where the normals are applied to the coordinates - is the magical index "2" from the calls glEnableVertexAttribArray(2) and glVertexAttribPointer(2,...) doing this assignment? Does OpenGL always "know" this "2" are the normals?
The normals itself should be fine as they come from a 3D model loaded from disk.
答案1
得分: 1
相应的位置和法线必须放置在两个缓冲区中的相同索引处。这意味着索引1处的法线属于索引1处的位置。
你当前的问题可能是你在属性中使用了错误的位置。属性位置(在glEnableVertexAttribArray
和glVertexAttribPointer
中使用的参数)指定了着色器中属性的位置(一种索引)。你可以在着色器链接后使用glGetAttributeLocation
来查询属性的位置:
auto location = glGetAttribLocation(program, "vertex_position");
glBindVertexArray(entity->m_gl3Element.VAO);
glBindBuffer(GL_ARRAY_BUFFER, entity->m_gl3Element.coordVBO);
glBufferData(GL_ARRAY_BUFFER, m_vertexArray.size() * sizeof(float), m_vertexArray.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(location);
glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
location = glGetAttribLocation(program, "vertex_normal");
glBindBuffer(GL_ARRAY_BUFFER, entity->m_gl3Element.normalVBO);
glBufferData(GL_ARRAY_BUFFER, normalArray.size() * sizeof(float), normalArray.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(location);
glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
或者你可以在着色器中使用布局限定符预定义属性的位置:
layout(location = 0) in vec3 vertex_position;
layout(location = 2) in vec3 vertex_normal;
无论哪种方式,VAO 设置中使用的位置必须与着色器中的位置匹配。
请注意,由于未显示顶点着色器,vertex_position
和 vertex_normal
必须替换为你的顶点着色器中的实际名称。
你的第一个版本可能之所以工作是因为你只有一个顶点属性(用于位置),很可能(但不保证)该属性被自动分配了位置0。现在你有两个属性,猜测它们的位置会很容易出错。
英文:
Corresponding positions and normals have to be placed at the same index in both buffers. That means that the normal at index 1 belongs to the position at index 1.
Your actual problem is that you are (probably) using the wrong locations for your attributes. Attribute locations, (the parameter used in glEnableVertexAttribArray
and glVertexAttribPointer
), specifies the location (kind of an index) of the attribute in the shader. You can either query the locations of the attributes after the shader has been linked with glGetAttributeLocation
:
auto location = glGetAttribLocation(program, "vertex_position");
glBindVertexArray(entity->m_gl3Element.VAO);
glBindBuffer(GL_ARRAY_BUFFER,entity->m_gl3Element.coordVBO);
glBufferData(GL_ARRAY_BUFFER,m_vertexArray.size()*sizeof(float),m_vertexArray.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(location);
glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
location = glGetAttribLocation(program, "vertex_normal");
glBindBuffer(GL_ARRAY_BUFFER,entity->m_gl3Element.normalVBO);
glBufferData(GL_ARRAY_BUFFER, normalArray.size()*sizeof(float),normalArray.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(location);
glVertexAttribPointer(location,3,GL_FLOAT,GL_FALSE,0,(void*)0);
or you can pre-define the locations in the shader using layout qualifiers:
layout(location = 0) in vec3 vertex_position;
layout(location = 2) in vec3 vertex_normal;
In either way, the locations you use during VAO setup have to match the locations in your shader.
Note, that since the vertex shader wasn't show, vertex_position
and vertex_normal
have to be replaced with the actual name in your vertex shader.
Your first version probably worked because you only had one vertex attribute (the one for the position) and it is very likely (but not guaranteed) that that attribute got location 0 auto-assigned. Now that you have two, guessing them is very error prone.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论