views:

386

answers:

1

Ok... imagine I have a relatively simple solid that has six distinct normals but actually has close to 48 faces (8 faces per direction) and there are a LOT of shared vertices between faces. What's the most efficient way to render that in OpenGL?

I know I can place the vertices in an array, then use an index array to render them, but I have to keep breaking my rendering steps down to change the normals (i.e. set normal 1... render 8 faces... set normal 2... render 8 faces, etc.) Because of that I have to maintain an array of index arrays... one for each normal! Not good!

The other way I can do it is to use separate normal and vertex arrays (or even interleave them) but that means I need to have a one-to-one ratio for normals to vertices and that means the normals would be duplicated 8 times more than they need to be! On something with a spherical or even curved surface, every normal most likely is different, but for this, it really seems like a waste of memory.

In a perfect world I'd like to have my vertex and normal arrays have different lengths, then when I go to draw my triangles or quads To specify the index to each array for that vertex.

Now the OBJ file format lets you specify exactly that... a vertex array and a normal array of different lengths, then when you specify the face you are rendering, you specify a vertex and a normal index (as well as a UV coord if you are using textures too) which seems like the perfect solution! 48 vertices but only 8 normals, then pairs of indexes defining the shapes' faces. But I'm not sure how to render that in OpenGL ES (again, note the 'ES'.) Currently I have to 'denormalize' (sorry for the SQL pun there) the normals back to a 1-to-1 with the vertex array, then render. Just wastes memory to me.

Anyone help? I hope I'm missing something very simple here.

Mark

+1  A: 

You're not missing anything. This is how the spec works because this is how most hardware works (aka your perfect is not hardware perfect).

I won't go into the complexities of implementing hardware that would support an array of indices, but I will point out one optimization you'd likely lose: the GL potentially uses the single index as an index into a post-vertex transform cache, to not have to re-transform the vertex on the next iteration. You make that optimization significantly more complex with a set of indices.

Regarding memory savings: in your case, you're talking about roughly a cube with each face using 4 quads, 8 triangles. So we're talking about 9*6=54 unique vertices. if you only have position and normals, that's 54 * 4 * 3 * 2 = 1296 B of vertex data + 2 * 48 * 3 = 288 B of index data (assuming 4-byte for the attribute base types and GLushort for indices). Grand total of 1584B. That's assuming a non-optimal data format for positions and normals too. The alternative is roughly 26*4*3(pos)+8*4*3(norm)+2*48*3*2=312+96+576=984B. So you saved about 0.5kB on this contrived case. Pass this to more memory-saving types for the attributes, and you get to: 648+288=936 vs 156+48+576=780... The difference starts to be negligeable.

Why am I bringing this up ? because you should look at your attribute data types if you want memory consumption optimizations.

Last, as you noticed yourself, in practical 3d worlds (i.e. not in the world of boxes), the savings of such a mechanism would be low: very few attributes can get shared.

Bahbar
Ok, simplify the model more. You have a strip of quads that extend like a roll of tape. Add a second strip down the edge like a second piece of tape so all vertices are shared down the 'fold' But because of the normals, you can't share the vertices so you now have a 33% gain in memory of vertex data. (i.e. you have a-b then c-d even though b and c are the exact same vertex, just different normals. And technically, even down the quads of a single strip you'd have the same issue if you didn't want the faces to be smooth but rather flat. (smooth the vertices would share normals of course.)
MarqueIV
Also, you don't lose any vertex transforms because the vertices are still in the same place in 3d space. What I'm doing with the normals or how I'm choosing to draw them with which commands doesn't affect them. And isn't that exactly what the DrawElements command does? ..via indexes? After all, if I have one index that I use in ten operations that I reference by index, isn't that just one calculation/optimization on that vertex as opposed to ten, being the exact opposite of your statement above?
MarqueIV
What you're describing sounds like a terrain. For most purposes, you _want_ to share the normals on terrain tape rolls. Wanting "flat" is rather the exception is what I was saying in my message. As for the post-vertex transform: what if your vertex position output depends on the normal ? It's not necessarily computed based solely on vertex input position. And I'm merely describing an optimization that I _know_ is currently in use (its size is usually small, by the way. You don't always hit it, especially if your terrain strips are long).
Bahbar
That would be the exception if terrain was what I was talking about. But what about origami? What about again, shapes based on simple geometries like say, a cube that have corners and edges? Terrain by its definition is supposed to be smooth. I am talking about shapes with corners.
MarqueIV
And by the way, how do you arrive at 9*6=54 unique vertices when even in your own words above, the edges of the face would share their middle vertices with the adjacent face, meaning 12 less there, then each of the 8 corners would be shared three times with each of their adjoining faces, meaning a savings of 16 there so you're actually eliminating 28 of the 54 meaning there is really 26 unique vertices or less than half the memory for them. And 54 normals need to be defined, one per each vertex of a triangle would mean 54 normals whereas again, you only need 6 or one 9th of the memory needed.
MarqueIV
If it were a simple cube with two triangles per face, you go from 24 vertices down to eight or a reduction in 66%. As for normals, you'd go from again, 24 down to 4 or roughly 1/6th. I'm sorry, but in extremely complex scenes with tons of such objects, I do not refer to that as 'negligible'.
MarqueIV
Im going to have my vertices in one array, my normals in a second array, then I'll create a set of arrays that defines the triangles via indexes with one set per normal. I'll then pair those arrays with the normal's index into a staggered array and use that for my rendering, manually set the normal, then render the triangles. Finally, for faces that have different normals per vertex (a beveled edge to look round) I'll just have to move those vertices to the front of the original array, then create a second 'normals' array and draw them as one normally would with DrawElements.
MarqueIV
Well, sounds like you found what you'll do. 9*6 is the case when you don't share (check both maths, it's the second case you're talking about. you'll notice a 26 in there. I did mess up the normal count -8 instead of 6). But you're missing the point: indices cost memory too (that's what my math was trying to point at). As to "extremely complex scenes" made of flat shaded cubes, I'm sorry but are you serious ? Curious though... how many triangles will you target for your origami ? how many do you expect will get shared ?
Bahbar