0
\$\begingroup\$

I've written a very simple OpenGL program displaying a white cube. To do the job done I recovered vertex position and indice data from an OBJ file that describes my cube. Here's the OBJ file content :

o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
usemtl BoxMtl
s off
f 1 2 3
f 5 8 7
f 1 5 6
f 2 6 3
f 3 7 4
f 5 1 4
f 4 1 3
f 6 5 7
f 2 1 6
f 6 7 3
f 7 8 4
f 8 5 4

According to the OBJ file the vertex array is translated in C by :

static GLfloat position[24] =
{
    1.000000, -1.000000, -1.000000,
    1.000000, -1.000000, 1.000000,
    -1.000000, -1.000000, 1.000000,
    -1.000000, -1.000000, -1.000000,
    1.000000, 1.000000, -0.999999,
    0.999999, 1.000000, 1.000001,
    -1.000000, 1.000000, 1.000000,
    -1.000000, 1.000000, -1.000000
};

And the index array by :

static GLuint indices[36] = {
    0, 1, 2,
    4, 7, 6,
    0, 4, 5,
    1, 5, 2,
    2, 6, 3,
    4, 0, 3,
    3, 0, 2,
    5, 4, 6,
    1, 0, 5,
    5, 6, 2,
    6, 7, 3,
    7, 4, 3
};

Now the code to initialize the VBO for the vertex array :

GLuint vertex_vboId;
glGenBuffers(1, &vertex_vboId);
glBindBuffer(GL_ARRAY_BUFFER, vertex_vboId);
glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(GLfloat), position, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

The one for the IBO :

GLuint indexId;
glGenBuffers(1, &indexId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 36 * sizeof(GLuint), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

And finally the code to render the scene in the main loop (here the white cube):

glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertex_vboId);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, OFFSET_BUFFER(0));
glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexId);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, OFFSET_BUFFER(0));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

Here's the output :

enter image description here

Until here there's no problem ! But my objective is to render the same cube but this time with a texture (a uv map) and with a UNIQUE call of glDrawElements like above.

To do the job done, I've first created a uv mapped cube on Blender and exported a new OBJ file but, this time, with vertex texture (vt) like below :

o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.626059 0.265705
vt 0.626059 0.487398
vt 0.404365 0.487398
vt 0.626060 0.930786
vt 0.404365 0.930786
vt 0.404365 0.709091
vt 0.847752 0.487397
vt 0.847753 0.709091
vt 0.626059 0.709091
vt 0.182672 0.487397
vt 0.626059 0.044011
vt 0.404366 0.265704
vt 0.182671 0.709091
vt 0.404366 0.044011
usemtl BoxMtl
s off
f 1/1 2/2 3/3
f 5/4 8/5 7/6
f 1/7 5/8 6/9
f 2/2 6/9 3/3
f 3/3 7/6 4/10
f 5/11 1/1 4/12
f 4/12 1/1 3/3
f 6/9 5/4 7/6
f 2/2 1/7 6/9
f 6/9 7/6 3/3
f 7/6 8/13 4/10
f 8/14 5/11 4/12

So I wonder if it's possible to use glDrawElements with two kinds of indices (v and vt) in a unique indice array (with a size of 72 this time). I tried several way without any success. Or maybe I have to use glDrawArrays... I'm really lost. Thanks very much in advance for your help.

\$\endgroup\$
1
\$\begingroup\$

No. There is no direct translation of the OBJ format into any graphics API (even D3D or bleeding edge OpenGL). What you have to do is duplicate the data. Here is a generic procedure:

  1. For every index pair that exists in the face section, such as 1/1, 5/4, ..., 5/11, etc. assign it a new index which is a single integer. So we might do:

    1/1 -> 0
    2/2 -> 1
    3/3 -> 2
    5/4 -> 3
    ...
    

    But for example, 1/1, 5/11, etc. appear more than once, so we always give them the same index.

  2. Now we assemble the data by looking up the indices backwards in the above table, and then copy that data to a new array. For example, 1/1 -> 0. So in the vertex array, we put the 1st vertex position data and the 1st uvcoord into index 0. Similarly, we put the 5th vertex position and the 4th uvcoord into the index 3 slot in the vertex array.
  3. Now you have all the positions and uvcoords in a vertex array buffer. Into an element array buffer, we put the face data, but instead of the original pairs, we place the assigned index, which now points into an array with the right data. (we might need to break quads here) We have turned something like f 2/2 1/7 6/9 into f 1 6 12, or something, which the graphics card can support. Then you can use glDrawElements as normal.

Note this procedure duplicates some data, potentially a lot, but that is what is required to submit to the graphics card. This process can easily be extended for vertex colors/weights/etc. by considering triples, or 4-tuples of indices, etc.

Also, please don't implement this procedure this way, there are way more efficient ways to do this. Also this might not produce very optimal meshes in terms of transform cache behaviour.

\$\endgroup\$
  • \$\begingroup\$ Hello Jason, thank you very much for this complete answer. It's the response I was looking for and I wanted to be really sure that it was impossible to use directly all the data in the OBJ file. Have a nice day. Bye. \$\endgroup\$ – user1364743 Jan 28 '14 at 10:36
  • \$\begingroup\$ I have a question. Finally is it really faster to use glDrawElements than glDrawArrays? According to what you've written above I think it's looks like the same kind of work to load the vertex data into new arrays like for glDrawArrays. What do you think about this idea ? But I believe glDrawElements just send indice array towards the GPU and glDrawArrays the whole array... (I'm not sure). So after the mesh loading in the main loop glDrawElements should be more efficient than glDrawArrays ? Thanks again for your answer. \$\endgroup\$ – user1364743 Jan 28 '14 at 10:50
  • \$\begingroup\$ Both arrays (vertex & index) have to be uploaded to the graphics card to use glElements. In your data, 1/1 is repeated 3 times, 5/11 twice, etc. With index (glElement) rendering, you only need to upload each vertex's data once (which is what the above procedure does). An index takes 2 or 4 bytes, and usually vertex data is dozens of bytes, so this is a good thing. The graphics card can reuse vertex shader invocations if you use glDrawElements (see "post transform cache"), saving rendering time. If you don't have many repeats, use glDrawArrays, but in general prefer index rendering. \$\endgroup\$ – user41442 Jan 28 '14 at 17:53
  • \$\begingroup\$ Ok it's very clear for me now. Thanks very much again for all your explanations! Bye. \$\endgroup\$ – user1364743 Jan 28 '14 at 19:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.