Zemanta Related Posts Thumbnail

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.

Suzanne rendered with Qt and OpenGL ES

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

        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]);
        triangle.vertices = verts;
        triangle.normals = norms;
        triangle.texcoords = texs;
    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.setAttributeArray(vertexAttr1, triangle.vertices.constData());
        program1.setAttributeArray(normalAttr1, triangle.normals.constData());
        program1.setAttributeArray(texCoordAttr1, triangle.texcoords.constData());
        glDrawArrays(GL_TRIANGLES, 0, triangle.vertices.size());

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!

Published by

Svenn-Arne Dragly

I'm a physicist and programmer, writing about the stuff I figure out as I go.

8 thoughts on “Loading wavefront .obj files in Qt’s OpenGL ES”

  1. Hi Svenn,

    I downloaded your QT Obj loader application and run it. But .exe file does not run and programs exits without opening the Output. Is there any updated version of ur application or a working copy? Can you check and upload a working copy.



    1. I’m afraid I haven’t worked on that example for a while, but it should still work. I think you’ll find more information about where it crashes if you launch the application in debug mode in Qt Creator (pressing F5), or maybe even if you just launch it from the command line in Windows.

      Most likely this is caused by a .obj file missing, and if I remember correctly the applications would fail with a segfault if this happens. I don’t think I implemented any file check to see if the file existed and was valid.

  2. Good afternoon Svenn.

    The code is great, it works fine for me, but I don’t know enough about Qt + OpenGl to understand the code completely, and I really would like to make a setter for the “monkey” position, I noticed that in the code I can move the “monkey” using “mvMonkey5.translate(player.x(),player.y(),player.z());” in the glwidget.cpp but I would like to modify that parameters in the main code (main.cpp), I tried something like : “mvMonkey5.translate( GLWidget::objetoX, GLWidget::objetoY, GLWidget::objetoZ );” and “void GLWidget::setObjetoZ(int pObjetoZ) {
    GLWidget::objetoZ = pObjetoZ;
    But it doesn’t work, if you could teach me how to move the “monkey” position and if it’s possible to move the camera position in the main.cpp it would be awesome for me.

    1. Hi Giovanny!

      Are you trying to update it just once in the main code and never change it? Or are you trying to change it multiple times (as in an animation or moving monkey)?

      Setting it once should work fine, but updating it is probably not possible in the main.cpp as things are set up now. The reason is that as soon as a.exec() is called, the “event loop” is passed on to the Qt classes, such as MainWindow which again calls GLWidget. In other words, if you try to do the changes before a.exec() they will be called once, but never again, and if you try to do them after a.exec() they will be called once you quit the application – and that doesn’t really help you out.

      The fact is that everything is redrawn within a.exec(), so you can’t animate anything outside the GLWidget (or MainWindow, perhaps). I’m sure some Qt gurus probably can prove me wrong on this, but I wouldn’t start messing around too much with this if you don’t have to.

      Setting the initial values for the position should work, so if that is the problem, I’m not sure where it goes wrong. What you do looks perfectly fine in that manner. Are you getting any errors?

  3. Hey

    Impressive work, but when I tried to compile I got the following error ” model.cpp:77: error: C4716: ‘Model::linkShaderProgram’ : must return a value “. Have you got any idea what I can do?

    1. Hi!

      This was because I had forgotten to return any bool value in the linkShaderProgram function. I think newer C++ compilers raise errors when this happens while the older GCC compiler apparently didn’t.

      I have fixed the error and pushed it to the repository now.

Leave a Reply