PDA

View Full Version : Loading a wavefront OBJ model as an indexed mesh



chronozphere
14-06-2010, 06:46 PM
Hey guys

I've run into a problem here. I'm trying to load an *.obj file. So far I've managed to load the geometry itsself (vertices + faces). I'm having issues with the texture-coordinates.

The faces look like this in the file:


f 15/1 14/2 18/3
f 18/4 19/5 15/6
f 16/7 15/8 19/9
f 19/10 20/11 16/12
f 14/13 13/14 17/15
f 17/16 18/17 14/18


Each face consists of 3 vertices (separated by spaces). Each vertex has a "vertex index" and a "uv coordinate index". If you also have normals, you will have a third index into the normal-array.

All these indices kinda suprise me and make my life hard. I expected only one index that points into all arrays at the same time. It's quite easy to get the right (vertex, UV, normal) triplet for each vertex. Now I need to resolve these arrays myself.

I thought that because the UV-indices seem te be increasing, I could cheat and just load the entire array directly. I found out that this is not good, because vertex 15 actually corresponds to UV coordinate 1, while UV coordinate 15 is used.

So, How can I resolve the normal and UV-coordinate arrays? What if I encounter 15/1 and 15/2? The 15th vertex can only be coupled with either UV 1 or UV 2.

Thanks a bunch! :)

de_jean_7777
15-06-2010, 10:02 AM
I see you've not had experience with indices. I'll try to explain this the best I can.

What you do is the following:

You have an array with all the vertices, and an array with all the uv coordinates. Then also you'll have an array with faces. These arrays can have different number of elements.
These faces only contain indices for vertices and uv coordinates, not the actual vertices and uv coordinates. The indices are used because faces share vertices and uv coordinates, and this reduces memory usage, but complicates things a bit.

So, when you render a face you get the vertices indicated by the vertex indices, and uv indices.

Imagine structures like these:


TYPE
TVector3f = array[0..2] of single; {this is for storing a vertex}
TVector2f = array[0..1] of single; {this is for storing a uv coordinate}

TFace = record
iv: array[0..2] of longint;
iuv: array[0..2] of longint;
end;

VAR
nVertices, nUV, nFaces: longint;
vertices: array of TVector3f;
uv: array of TVector2f;
faces: array of TFace;


First you read the vertices and uv coordinates into their respective arrays. Then you read the faces one by one into the faces array. Each new face goes to the next position in the array (increment by one). Store the number of vertices, uv coordinates and faces into nVertices, nUV and nFaces.

Once you've read all the information then you need to render the faces like this:


glBegin(GL_TRIANGLES);
for i := 0 to (nFaces-1) do begin
glTexCoord2f(uv[faces[i].iuv[0]]);
glVertex3f(vertices[faces[i].iv[0]]);
glTexCoord2f(uv[faces[i].iuv[1]]);
glVertex3f(vertices[faces[i].iv[1]]);
glTexCoord2f(uv[faces[i].iuv[2]]);
glVertex3f(vertices[faces[i].iv[2]]);
end;
glEnd();


I hope you understood.

In case you want to store everything as a single array then you'll need to do pretty much as above. Read vertices and uv coordinates, but the TFace type will be like this.



TFace = record {remember that triangles have tree vertices and three uv coords}
v: array[0..2] of TVector3f;
uv: array[0..2] of TVector2f;
end;


Then when you read the faces from the obj file, you get the three indices for faces and three indices for uv coords, and then you look up the vertices and uv coords in the arrays and store them in the current face.

We'll consider that 'i' is the index of the current face you're reading from the .obj file and that you store the indices in the following variables:


VAR
face_indices, uv_indices: array[0..2] of longint;


Then we assign the vertices and uv coords to the face:



faces[i].v[0] := vertices[face_indices[0]];
faces[i].v[1] := vertices[face_indices[1]];
faces[i].v[2] := vertices[face_indices[2]];
faces[i].uv[0] := uv[uv_indices[0]];
faces[i].uv[1] := uv[uv_indices[1]];
faces[i].uv[2] := uv[uv_indices[2]];


This may make rendering easier, but it takes somewhat more memory.

REMARK: I think this is the longest post I've ever written.

chronozphere
15-06-2010, 05:14 PM
Thanks for your reply.

Yeah. I understand your first method. I have a piece of code that does exactly that. The problem was that I needed to load it as an indexed mesh. Rendering itsself is not really a problem.

My goal was to load the obj file while still having it indexed, so that the same vertex is not sent to the GPU twice. I've decided to drop this, and just load the geometry as a simple list of triangles, each having unique vertex- and uv coordinates. This makes my life a bit easier. It's not really optimal, but for small meshes, you wouldn't notice any difference. :)

User137
15-06-2010, 05:58 PM
I see what you mean now; it's OpenGL making trouble by not allowing different sized index arrays for vertices, textures and normals. Thinking about it gives only 1 option if you want to keep using optimal indexing.

- First load all vertices from triangles to new unique indexes by array size that is (Triangles * 3). You need to first temporarily load all arrays as they are from OBJ file, and start creating actual indexes as you are finally loading faces.
- Then go iterate through all indexes looking for matching coordinates and delete where necessary. Change triangle corner to index that is still remaining.

The final iteration can be slow depending on how big the model is but the principle works on all model formats same time, to create optimal index array.

de_jean_7777
15-06-2010, 10:41 PM
Thanks for your reply.

Yeah. I understand your first method. I have a piece of code that does exactly that. The problem was that I needed to load it as an indexed mesh. Rendering itsself is not really a problem.

My goal was to load the obj file while still having it indexed, so that the same vertex is not sent to the GPU twice. I've decided to drop this, and just load the geometry as a simple list of triangles, each having unique vertex- and uv coordinates. This makes my life a bit easier. It's not really optimal, but for small meshes, you wouldn't notice any difference. :)


Personally, I hate the .obj format. I mostly store everything in binary form, as most of stuff is not meant to be edited by a human directly (nobody goes directly editing the .obj files, rather they load it in a 3d editor).

As for your goal, there is a problem. The problem with OBJ is that there are separate indices for vertices and uv coords and this is not how OpenGL works (there is only one set of indices for everything). It actually is an indexed mesh, just not the way you or I want it. I did not understand your first post well, but now I know what you mean. To get an indexed mesh, you'll probably have to do something like what User137 said.