c++openglblenderglm-mathwavefront

Drawing Quads in OpenGL from .obj File


I'm parsing a .obj (created in Blender, if that helps) into a C++ program in OpenGL, and it seems to be coming close. It seems to correctly find the vertices that are needed here. I'm trying to create a capital block letter I that moves around. Here's my code for parsing:

bool loadObjectFile(const char * filepath, std::vector < glm::vec3 > & out_vertices, std::vector < glm::vec3 > & out_normals)
{
    std::vector< unsigned int > vertexIndices;
    std::vector< glm::vec3 > temp_vertices;
    std::vector< glm::vec3 > temp_normals;
    glm::vec3 tempFaceNormal;

    FILE * file = fopen(filepath, "r");
    if (file == NULL)
    {
        printf("Impossible to open the file !\n");
        return false;
    }

    while (1)
    {
        char lineHeader[128];
        // read the first word of the line
        int res = fscanf(file, "%s", lineHeader);
        if (res == EOF)
            break; // EOF = End Of File. Quit the loop.

        // else : parse lineHeader
        if (strcmp(lineHeader, "v") == 0)
        {
            glm::vec3 vertex;
            fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
            temp_vertices.push_back(vertex);
            glm::vec3 zeroface;
            zeroface.x = 0, zeroface.y = 0, zeroface.z = 0;
            temp_normals.push_back(zeroface);
        }
        else if (strcmp(lineHeader, "f") == 0)
        {
            unsigned int vertexIndex[3];
            int matches = fscanf(file, "%d %d %d\n", &vertexIndex[0], &vertexIndex[1], &vertexIndex[2]);

            if (matches != 3)
            {
                printf("File can't be read by our simple parser : ( Try exporting with other options\n");
                return false;
            }

            vertexIndices.push_back(vertexIndex[0]);
            vertexIndices.push_back(vertexIndex[1]);
            vertexIndices.push_back(vertexIndex[2]);

            tempFaceNormal = computeNormal(temp_vertices[vertexIndex[0] - 1], temp_vertices[vertexIndex[1] - 1], temp_vertices[vertexIndex[2] - 1]);

            temp_normals[vertexIndex[0] - 1] += tempFaceNormal;
            temp_normals[vertexIndex[1] - 1] += tempFaceNormal;
            temp_normals[vertexIndex[2] - 1] += tempFaceNormal;
        }
    }

    // For each vertex of each triangle
    for (unsigned int i = 0; i < vertexIndices.size(); i++)
    {
        unsigned int vertexIndex = vertexIndices[i];
        glm::vec3 vertex = temp_vertices[vertexIndex - 1];
        out_vertices.push_back(vertex);
    }

    //For each vertex normal
    for (unsigned int i = 0; i < temp_normals.size(); i++)
    {
        out_normals.push_back(glm::normalize(temp_normals[i]));
    }
}

glm::vec3 computeNormal(glm::vec3 const & a, glm::vec3 const & b, glm::vec3 const & c)
{
    //Returns the (not normalized) face normal of each triangle
    return glm::cross(c - a, b - a);
}

Here's the code I'm using to draw the parsed vertices:

glBegin(GL_QUADS);
glColor3f(0.2, 0.2, 0);
for (int i = 0; i < vertices.size(); i++)
{
    glVertex3f(vertices[i].x, vertices[i].y, vertices[i].z);
    glTexCoord3f(vertices[i].x, vertices[i].y, vertices[i].z);
}
for (int i = 0; i < normals.size(); i++)
{
    glNormal3f(normals[i].x, normals[i].y, normals[i].z);
}
glEnd();

This produces the following (screenshots):

https://drive.google.com/file/d/0B6qCowcn51DnQk1mT0hSUjZWc00/view?usp=sharing

https://drive.google.com/file/d/0B6qCowcn51DndTBJRFctV1lOQlk/view?usp=sharing

Here, the vertices seem to be correct, but the faces seem to be drawn incorrectly.


Solution

  • There are a number of issues in this code. I won't go into a lot of detail on most of these, but wanted to at least point out the main issues.

    OBJ parsing

    The main part of what you're doing is probably fine as long as you generate the OBJ files yourself, and control exactly what they contain. If you wanted to be able to parse more general OBJ files, you would need a lot more. For example:

    Primitive type mismatch

    This is probably the biggest problem. Your parsing code reads 3 vertices per face, which means that it expects all faces to be triangles:

    int matches = fscanf(file, "%d %d %d\n", &vertexIndex[0], &vertexIndex[1], &vertexIndex[2]);
    if (matches != 3)
    {
        printf("File can't be read by our simple parser : ( Try exporting with other options\n");
        return false;
    }
    

    But then the rendering code renders the faces as quads:

    glBegin(GL_QUADS);
    

    If the file contains triangles, you need to use that as primitive type for rendering:

    glBegin(GL_TRIANGLES);
    

    If it contains quads, you need to parse 4 vertex indices per face, and update the rest of the parsing code accordingly.

    Normal array

    The code contains logic to rearrange the vertices based on the order of the vertex indices in the faces:

    for (unsigned int i = 0; i < vertexIndices.size(); i++)
    {
        unsigned int vertexIndex = vertexIndices[i];
        glm::vec3 vertex = temp_vertices[vertexIndex - 1];
        out_vertices.push_back(vertex);
    }
    

    But it just copies the normals in the original order of the vertices:

    for (unsigned int i = 0; i < temp_normals.size(); i++)
    {
        out_normals.push_back(glm::normalize(temp_normals[i]));
    }
    

    Instead, it needs to rearrange the normals the same way as the vertices. This could be done as part of the vertex loop:

    for (unsigned int i = 0; i < vertexIndices.size(); i++)
    {
        unsigned int vertexIndex = vertexIndices[i];
        out_vertices.push_back(temp_vertices[vertexIndex - 1]);
        out_normals.push_back(glm::normalize(temp_normals[vertexIndex - 1]));
    }
    

    Specification of normals during rendering

    The rendering code contains a separate loop over the normals after looping over the vertices. Specifying the normals this way does absolutely noting. glNormal3fv() needs to be called before glVertex3fv() to specify the normal for that specific vertex.

    Instead of having two separate loops, the rendering loop should look like this:

    for (int i = 0; i < vertices.size(); i++)
    {
        glNormal3f(normals[i].x, normals[i].y, normals[i].z);
        glVertex3f(vertices[i].x, vertices[i].y, vertices[i].z);
    }
    

    The glTexCoord3f() calls would also go before glVertex3f(), but you don't have texture coordinates in the first place.

    Obsolete OpenGL

    The rendering calls you are using, glBegin()/glEnd(), glVertex3f(), etc., are legacy functionality. This is commonly called immediate mode, is deprecated, and not available anymore in modern version of OpenGL (OpenGL 3.x/4.x core profile, OpenGL ES 2.0/3.x).

    For modern rendering, start by learning about Vertex Buffer Objects (VBO) and Vertex Array Objects (VAO) to specify your vertices. You can then use glDrawElements() or glDrawArrays() to draw entire objects with a single call.