Wednesday, 22 July 2009

Wavefront .OBJ files & Vertex Buffer Objects


OBJ files are handy for many reasons:
  • they're text files
  • well documented
  • easy to parse
  • and many professional softwares can export them

This is why they are so popular as standard files for 3D graphics.

Their structure is basically a (long) list of vectors definitions (spatial, normals, texture coordinates, and so on) followed by a (long) list of structures defining faces. What are faces? Faces are tuples of indexes, which point specific vertexes, normals and texture coordinate in the previous lists. At least three vertexes are needed for each face, because it forms a triangle, that is the smaller polygon we can create.

Why is this needed? Well, because a single vertex could be shared by many faces (=polygons). That's important in real-time 3D programming, because avoiding redundancy is good for performances!

This is the very same idea behind OpenGL's indexed vertex arrays: rather than sending to the video card a list of vertexes, normals and texture coordinates (the so called immediate mode) we prepare a couple of arrays filled with informations, and another array of indexes which tell OpenGL what vertex use in a certain primitive.

Sounds good, uh? Well, there is a couple of drawbacks actually.

The biggest is that every index points simultaneously at vertex, normals and texture arrays! For instance, if our first index is "1", that face points vertex[1], normal[1] and texture_coord[1]. That's a real nightmare, because there's no ensurance that our modeling softwares will produce .OBJ files featuring correctly ordered lists! Moreover, there's often a few of vertexes (which are often shared by many faces) and lot of normals!

So what?? Well, OpenGL does not aid us in any way: you gotta solve with your hands. My solution is the following:
  • parse the OBJ file, loading vertexes, normals, and faces;
  • from faces, create a definitive vertex index and a temporary arrays for normals and textures coordinates;
  • for each i in number_of_indexes do
    definitive_normal_array[ index_array[i] ] :=
    Temporary_normals_array[ TemporaryNormalIndexes[i] ]
And there you go, you have correct normals bound to the right vertex. You could even have time for re-normalizing them, if they aren't.

The most interesting way for drawing this series of arrays is the use of Vertex Buffer Objects (VBO). They're similar to the indexed vertex arrays, but have interesting features... like dropping geometries in the fastest video card memory and use it! The boost of performances is impressive, and they are easy enough to deal with.

My PLTmesh::Draw() method is this:

// PLT_VBOnames are references to the VBOS - see glGenBuffers()

glEnableClientState(GL_NORMAL_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, PLT_VBOnames[2]);
glNormalPointer(GL_FLOAT, 0, NULL);

glEnableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, PLT_VBOnames[0]);
glVertexPointer(3, GL_FLOAT, 0, (char *) NULL );

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, PLT_VBOnames[1]);

glDrawElements(GL_TRIANGLES, this->IndexesArray.size(), GL_UNSIGNED_INT, (GLvoid*)((char*)NULL));

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
Quite simple and clear! :)

3 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Prometheis said...

Grandissimo! Non sai che favore mi hai fatto con quella semplice spiegazione su come riordinare le normali (e le coordinate di texture) rispetto agli inidici dei vertici!
Erano giorni che ci sbatteva la testa! Io e un mio amico stiamo progettando un gioco in Java (ammalliati dal nuovo trend dettato da Minecraft) e stiamo cercando di farci da noi il motore grafico e non potendo usare Assimp che è solo per C/C++, abbiamo iniziato a pasticciare per fare importers di file .obj e file .md3.
Domanda forse niubba, ma siccome ho letto che è consigliabile utilizzare un solo VBO, una volta ottenuti i vari array di vertici/normali/tex coords come li riorganizzi per ottenre un "interleaved array"? Grazie ancora!

Marco Nobile said...

Ciao! prima di tutto, son felice di esserti stato utile, il senso di questo post era ESATTAMENTE evitare mal di testa a chi sarebbe venuto dopo di me :D

Dunque, un solo VBO. Non ti saprei dire, perché ho sempre preferito farne uno per ogni elemento della scena. Posso immaginare che il metodo sia il medesimo: appendi tutto (indici, vertici, facce, ecc.) agli stessi arrays e procedi all'ordinamento.

devi avere l'accortezza di segnarti da quale punto a quale punto inizia ogni oggetto del tipo:

indice 0 - 100 = primo oggetto
indice 101 - 203 = secondo oggetto

e così via. in questo modo hai le informazioni da passare a glDrawElements: il "count" delle facce del singolo oggetto e dove far iniziare "indices" (ovvero con quale offset).

prova a guardare questo post, credo faccia al caso tuo:

http://stackoverflow.com/questions/1663908/how-do-i-use-a-pointer-as-an-offset

se ti va, fammi sapere se hai risolto :) ciao