Search code examples
c++openglglslassimp

Unable to visualize a simple head mesh with smooth surface in OpenGL (C++) like any other 3D graphic software?


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

enter image description here

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)

enter image description here

enter image description here

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

Solution

  • 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.

    enter image description here