obj file - processing data

I’m working on obj loader. I want to draw an object using glDrawElements (smooth shading). Here are my assumptions:

  • the number of normals <= the number of vertices (I don’t need to average normals, only arrange them with relation to vertices indices using normal indices before sending to a gpu. Then the number of normals = the number of vertices)
  • the number of normals > the number of vertices ( It indicates that I need to average normals (according to smoothing groups if present) and arrange them with relation to vertices indices using normal indices before sending to a gpu. Then the number of normals = the number of vertices)
  • the number of tex coords <= the number of vertices (I need to arrange tex coords with relation to vertices indices using tex coords indices before sending to a gpu. Then the number of tex coords = the number of vertices)
  • the number of tex coords > the number of vertices - In this case I can’t use glDrawElements so I process normals as above and then arrange vertices, normals and tex coords using their indices. Data will be properly duplicated and then I will have to use glDrawArrays.

Please verify my assumptions and let me know whether I’m right or wrong.

The only correct thing in what you said is that you need to convert from multiple indices per-vertex to one index per-vertex. Everything else is wrong. The number of normals relative to the number of positions is irrelevant to whether they’re face normals or not (which is what I assume this “average the normals” stuff is about). After all, a cube has 8 positions, but 6 face normals. Similarly, the number of texture coordinates relative to positions has nothing to do with whether you can use glDrawElements.

This is not a hard problem. The algorithm is quite simple. For each set of indices in the index lists (remember: the index lists will all be the same size):

  • If that set of indices has not been seen before:
    ** Add the vertex attributes referenced by that index to the attribute arrays.
    ** Add the index of the new vertex to the index buffer.
    ** Store the fact that this particular set of indices has been seen before, and which index you used in the index buffer with it.
  • If that set of indices has been seen before:
    ** Retrieve the index for the index buffer that was stored for this set of indices.
    ** Add that index to the index buffer.
    ** Do not add any vertex attributes to the attribute arrays.

See? All very simple. The hard part is using a std::map to keep track of which sets of indices have been seen before. This process results in a single index buffer which you can use with glDrawElements.

My English isn’t perfect so maybe I try to explain what I mean by example and how I understand preparing data for shaders. Please correct me if I’m wrong and post more extensive explanation.

  1. We have only indices for vertices when using glDrawElements, there is no indices for normals and tex coords so before we send normals and tex coords to a shader we have to arrange them with relation to vertices and the number of vertices = the number of normals = the number of tex coords. That concerns a smooth shading which is usually using for models. In general averaging normals means that for every vertex I find all per - face (per - triangle) normals which surround that vertex, sum up them, normalize and assign the result to that vertex. So when in a obj file the number of normals > the number of vertices it is logical for me that we need to average normals beacause for smooth shading the number of vertices = the number of normals.

  2. Drawing a cube is a particular case, because for cube we usually use flat shading: for each vertex we have 3 different normals and then we can only use glDrawElements when we duplicate vertices acccording to normals. In that case more logical is using glDrawArrays.

  3. “Similarly, the number of texture coordinates relative to positions has nothing to do with whether you can use glDrawElements” - I have an obj file which contains 507 vertices, 590 tex coords, 507 normals , 2904 vertex indices, 2904 tex coords indices and 2904 normal indices. Here is a relevant piece of the obj file:

f 317/210/186 76/258/229 77/263/231
f 76/260/229 316/211/187 77/264/231

Vertex 77 using two different tex coords in two different triangles, so now using glDrawElements is impossible without duplicate a vertices data.

First, when I say “vertex”, I mean “a set of data which includes position, normal, texture coordinates, etc.” It’s important to keep separate the geometric position of a vertex and the other elements that comprise a vertex.

So when in a obj file the number of normals > the number of vertices it is logical for me that we need to average normals beacause for smooth shading the number of vertices = the number of normals.

That is not true at all. Just because an object uses “smooth shading” (ie: per-vertex normals, rather than face normals) does not mean that every normal matches with every position. Sometimes, two positions have the same normal. Sometimes, the same position is used with different normals, which creates a sharp corner in a mesh. Meshes are not all smooth or all faceted; some have elements of both.

Most important of all, there is no difference in rendering between the cases of faceted meshes or smooth meshes. They all have per-vertex normals; the only question is how they map to positions.

Let the model decide what is and isn’t faceted. Your job is to render what you’re given.

Drawing a cube is a particular case, because for cube we usually use flat shading: for each vertex we have 3 different normals and then we can only use glDrawElements when we duplicate vertices acccording to normals. In that case more logical is using glDrawArrays.

You’re still duplicating vertices. And you’d have to duplicate more to use glDrawArrays with a cube. Or use 6 rendering calls, one tri-strip/quad for each face. It’s better to use glDrawElements in either case.

Vertex 77 using two different tex coords in two different triangles, so now using glDrawElements is impossible without duplicate a vertices data.

glDrawArrays is impossible without duplicating vertex attribute data too. At least with glDrawElements, you can catch duplicates of actual vertex data, as well as optimize triangle/strip ordering for better performance.

So for a cube when I’m using glDrawArrays in a single rendering call (GL_TRIANGLES) I need duplicate positions, normals and tex coords ( 36 positions, 36 normals, 36 tex coords). For glDrawElements(GL_TRIANGLES) and a single rendering call I will need 24 positions, 24 normals, 24 tex coords, 36 indices, right ?

Finally, how can I take into account smoothing groups in the algorithm which you post at the begining ?