Loading wavefront .obj files in Qt's OpenGL ES

Note : This method is now easily replaced by the awesome Qt3D module that has support for loading a range of different file formats into an OpenGL context.

[caption id=”attachment_244” align=”alignright” width=”300”] Suzanne rendered with Qt and OpenGL ES[/caption]

I’ve been looking all over the web for a Qt library for loading files exported from Blender into OpenGL. Since I didn’t find anything I figured I would create a library myself.

Even though I didn’t find anything specifically about Blender and Qt’s OpenGL ES implementation, I managed to find something about each of them separately. Loading files from Blender seems to be most easily done by exporting to another format first. A lot of people mentioned Wavefront’s .obj file format as a good candidate. It is a small format, easy to parse and a lot of libraries already exist. At least that’s what they said.

Finding a working .obj-loading library wasn’t that hard, but finding one that would load and compile nicely together with Qt was a bit harder. Finally I found GLM by Nate Robins, written way back in 1997. It might be old, but it certainly did the job. It even managed to draw on the screen! The next problem, however, was to make it draw with the newer OpenGL specifications. OpenGL ES 2.0 especially, since that’s what’s running on my Nokia N900.

The problem is that the old fixed API of OpenGL won’t compile with N900’s Scratchbox environment. I was close to giving up the whole GLM library, but I figured that I could try commenting out the functions which wouldn’t compile. And it worked!

I had to comment out glmDraw() and glmList(), and thus do the drawing on my own, but at least I had a library working which would load the files. I didn’t have to write all that myself, or even get into how .obj files are structured.

The next step was drawing the data. This was a bit of a pain, since the newer OpenGL ES API wants all data in certain arrays before drawing, while I was looking at just a bunch of vertices.

Stepping through the code in GLM, however, made me realize how it structured all groups and triangles from the .obj file, and made it possible for me to get these out and draw them on screen. Today I finally figured out how to draw the textures as well.

For testing I started with the hellogl_es2 example from Nokia and used this to draw my Blender monkey (named ‘Suzanne’). The whole example which now is loading the .obj model and drawing it on-screen is hosted over at GitHub. And for the interested, the main changed parts are loading the file and storing the data in arrays:

model = glmReadOBJ("monkey1.obj");
model = glmReadOBJ("monkey1.obj");
GLMgroup* group;
group = model->groups;
while (group) {
    Group grp;
    for(int i = 0; i < group->numtriangles; i++) {
        Triangle triangle;
        QVector verts;
        for(int j = 0; j < 3; j++) {
            QVector3D vector(model->vertices[3 * model->triangles[group->triangles[i]].vindices[j] + 0],
                             model->vertices[3 * model->triangles[group->triangles[i]].vindices[j] + 1],
                             model->vertices[3 * model->triangles[group->triangles[i]].vindices[j] + 2]);
            verts.append(vector);
        }
        QVector norms;
        for(int j = 0; j < 3; j++) {
            QVector3D vector(model->normals[3 * model->triangles[group->triangles[i]].nindices[j] + 0],
                             model->normals[3 * model->triangles[group->triangles[i]].nindices[j] + 1],
                             model->normals[3 * model->triangles[group->triangles[i]].nindices[j] + 2]);
            norms.append(vector);
        }

        QVector texs;
        for(int j = 0; j < 3; j++) {
            QVector3D vector(model->texcoords[2 * model->triangles[group->triangles[i]].tindices[j] + 0],
                             model->texcoords[2 * model->triangles[group->triangles[i]].tindices[j] + 1],
                             model->texcoords[2 * model->triangles[group->triangles[i]].tindices[j] + 2]);
            texs.append(vector);
        }
        triangle.vertices = verts;
        triangle.normals = norms;
        triangle.texcoords = texs;
        grp.triangles.append(triangle);
    }
    groups.append(grp);
    group = group->next;
}

And drawing the model:

glBindTexture(GL_TEXTURE_2D, m_uiTexture);
foreach(Group grp, groups) {
    foreach(Triangle triangle, grp.triangles) {
        program1.setUniformValue(textureUniform1, 0);    // use texture unit 0
        program1.enableAttributeArray(normalAttr1);
        program1.enableAttributeArray(vertexAttr1);
        program1.enableAttributeArray(texCoordAttr1);
        program1.setAttributeArray(vertexAttr1, triangle.vertices.constData());
        program1.setAttributeArray(normalAttr1, triangle.normals.constData());
        program1.setAttributeArray(texCoordAttr1, triangle.texcoords.constData());
        glDrawArrays(GL_TRIANGLES, 0, triangle.vertices.size());
        program1.disableAttributeArray(normalAttr1);
        program1.disableAttributeArray(vertexAttr1);
        program1.disableAttributeArray(texCoordAttr1);
    }
}

I will turn this all into a Qt OpenGL ES library in the future, but for now I figured you might be interested in a working example. Happy coding!