I'm using modern OpenGL to visualize a head mesh imported from an .obj file whose normal vectors have been created by Blender. When I load that model in any 3D object viewer, everything looks fine
but when I try to visualize it myself using OpenGL, the surface doesn't appear as smooth (looks bumpy) and also the light appears on the wrong side of the head. To test my light rendering code I applied it on a box and it works perfectly but on the face mesh it doesn't (the position of the light source is visualized by the small white box right behind the head mesh)
For the box, I'm creating three instances for each vertex but each time with a different normal corresponding to the associated side. I first applied the same method for the head mesh by having multiple instances of a vertex with different normals for the intended quad. When the mesh appeared bumpy I suspected having different normals associated with the same vertex might not be a good idea, especially on a smooth surface like a head mesh. Because it may lead to a drastic change of the light direction. So I used Assimp to load the .Obj hoping that it would rearrange the vertices such that each vertex has unique specs (texture coordindate, normals, positions, etc) and used the associated rearranged indices to upload in my index buffer. But nothing changed. I'm not sure if I'm making a mistake in creating the vertex buffer or in my shaders but I directly imported them from what I wrote to visualize the cube as follows.
struct Vertex {
glm::vec3 position;
glm::vec2 texCoords;
glm::vec3 normal;
};
struct Mesh {
vector<Vertex> vertices;
vector<unsigned int> indices;
};
unsigned int createMeshVertexArray() {
unsigned int posComponents = 3; // xyz
unsigned int uvComponents = 2; // uv
unsigned int normComponents = 3; // ijk
unsigned int vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
unsigned int vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, _meshes[0].vertices.size() * sizeof(Vertex), &_meshes[0].vertices[0], GL_STATIC_DRAW); // perhaps gl_dynamic_draw is better
unsigned int posAttribId = 0;
unsigned int uvAttribId = 1;
unsigned int normAttribId = 2;
int posOffset = 0 * sizeof(float);
int uvOffset = (posOffset + posComponents) * sizeof(float);
int normOffset = (uvOffset + uvComponents) * sizeof(float);
size_t stride = (posComponents + uvComponents + normComponents) * sizeof(float);
glEnableVertexAttribArray(posAttribId);
glEnableVertexAttribArray(uvAttribId);
glEnableVertexAttribArray(normAttribId);
glVertexAttribPointer(posAttribId, posComponents, GL_FLOAT, GL_FALSE, stride, (const void*)posOffset);
glVertexAttribPointer(uvAttribId, uvComponents, GL_FLOAT, GL_FALSE, stride, (const void*)uvOffset);
glVertexAttribPointer(normAttribId, normComponents, GL_FLOAT, GL_FALSE, stride, (const void*)normOffset);
return vao;
}
unsigned int createMeshIndexBuffer() {
unsigned int ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, _meshes[0].indices.size() * sizeof(unsigned int), &_meshes[0].indices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // unbind to make sure nothing else is accidentally added to this index buffer later
return ibo;
}
The main function and the rendering loop.
int main(void) {
//=============================================================================
//------------------ light source and camera position
//=============================================================================
float cameraOffsetY = 1.5;
float radius = 8.0;
glm::vec3 lightPos(0.0, 3.0, 1.0);
glm::vec3 cameraPos(0.0, cameraOffsetY, radius);
glm::vec3 cameraFront(0.0, cameraOffsetY, 0.0);
glm::vec3 cameraUp(0.0, 1.0, 0.0);
//=============================================================================
//------------------ projection matrix
//=============================================================================
float f = windowWidth; // focal distance
float fov = 2 * atan(windowWidth / (float)(2 * f)); // in radian
float aspectRatio = windowWidth / (float)windowHeight;
glm::mat4 proj = glm::perspective(fov, aspectRatio, 0.1f, 100.0f);
//=============================================================================
//------------------ head mesh
//=============================================================================
glm::mat4 meshModel = glm::translate(glm::mat4(1.0), glm::vec3(2.0, 0.0, -2.0));
meshModel = glm::rotate(meshModel, glm::radians(-30.0f), glm::vec3(0.0, 1.0, 0.0));
float faceSc = 1.0;
meshModel = glm::scale(meshModel, glm::vec3(faceSc, faceSc, faceSc));
glVisual.loadModel("../files/pose_17_with_normals.obj");
unsigned int meshVao = glVisual.createMeshVertexArray(); // vertex array
unsigned int meshIbo = glVisual.createMeshIndexBuffer(); // index buffer
unsigned int meshSbo = glVisual.handleShaders("../files/faceMesh.glsl"); // shader
unsigned int meshLoc_mvp = glGetUniformLocation(meshSbo, "mvp");
unsigned int meshModelLoc = glGetUniformLocation(meshSbo, "model");
unsigned int meshNormMatLoc = glGetUniformLocation(meshSbo, "nMat");
unsigned int meshColorLoc = glGetUniformLocation(meshSbo, "objectColor");
unsigned int meshLightPosLoc = glGetUniformLocation(meshSbo, "lightPos");
unsigned int meshCamPosLoc = glGetUniformLocation(meshSbo, "cameraPos");
glm::mat3 meshNormMat = glm::mat3(glm::transpose(glm::inverse(meshModel)));
glUniformMatrix4fv(meshModelLoc, 1, GL_FALSE, glm::value_ptr(meshModel));
glUniformMatrix3fv(meshNormMatLoc, 1, GL_FALSE, glm::value_ptr(meshNormMat));
glUniform3f(meshColorLoc, 0.6, 0.6, 0.8);
glUniform3f(meshLightPosLoc, lightPos[0], lightPos[1], lightPos[2]);
size_t numOfMeshVisualVerts = NUM_OF_FACES * 4;
while (true) {
glm::mat4 view = glm::lookAt(cameraPos, cameraFront, cameraUp);
glBindVertexArray(meshVao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIbo);
glUseProgram(meshSbo);
glm::mat4 mvp = proj * view * meshModel;
glUniform3f(meshCamPosLoc, cameraPos[0], cameraPos[1], cameraPos[2]);
glUniformMatrix4fv(meshLoc_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
glDrawArrays(GL_QUADS, 0, numOfMeshVisualVerts);
glfwSwapBuffers(window);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear buffer
glfwPollEvents(); // Poll for and process events
if (glfwWindowShouldClose(window))
break;
}
glfwTerminate();
return 0;
}
The vertex and fragment shaders.
#shader vertex
#version 330 core
layout(location = 0) in vec3 n_position;
layout(location = 1) in vec2 n_uv;
layout(location = 2) in vec3 n_normal;
out vec3 faceNormal;
out vec3 fragPos; // fragment position
uniform mat4 mvp; // mvp matrix
uniform mat4 model; // model matrix
uniform mat3 nMat; // normal matrix
void main() {
gl_Position = mvp * vec4(n_position, 1.0);
fragPos = vec3(model * vec4(n_position, 1.0));
faceNormal = nMat * n_normal;
};
#shader fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec3 faceNormal;
in vec3 fragPos; // fragment position in world coordinates
uniform vec3 objectColor;
uniform vec3 lightPos;
uniform vec3 cameraPos;
void main() {
vec3 lightColor = vec3(1.0, 1.0, 1.0);
float ambientCoeff = 0.3;
vec3 normVec = normalize(faceNormal);
vec3 lightDir = normalize(lightPos - fragPos);
float diff = max(dot(normVec, lightDir), 0.0);
vec3 diffuseCoeff = diff * lightColor;
float specularStrength = 0.1; // depends on the material
int shininess = 4; // how much to spread on the surface
vec3 viewDir = normalize(cameraPos - fragPos);
vec3 reflectDir = reflect(-lightDir, normVec);
float reflectAmount = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
vec3 specularCoeff = specularStrength * reflectAmount * lightColor;
color = vec4((ambientCoeff + diffuseCoeff + specularCoeff) * objectColor, 1.0);
// color = vec4(objectColor, 1.0);
};
As @Spektre suggested in the comment section, the problem was in the way I was setting up the layout for my vertex buffer. Specifically, I had miscalculated my normal attribute offset as follows.
unsigned int posComponents = 3; // xyz
unsigned int uvComponents = 2; // uv
unsigned int normComponents = 3; // ijk
int posOffset = 0 * sizeof(float);
int uvOffset = (posOffset + posComponents) * sizeof(float);
int normOffset = (uvOffset + uvComponents) * sizeof(float);
glVertexAttribPointer(posAttribId, posComponents, GL_FLOAT, GL_FALSE, stride, (const void*)posOffset);
glVertexAttribPointer(uvAttribId, uvComponents, GL_FLOAT, GL_FALSE, stride, (const void*)uvOffset);
glVertexAttribPointer(normAttribId, normComponents, GL_FLOAT, GL_FALSE, stride, (const void*)normOffset);
Whereas it should have been done this way
int normOffset = (posComponents + uvComponents) * sizeof(float);
It was a silly mistake but I'm glad it's fixed now.