? Texturing many triangles - 3DS loader question ?

This is probably a question for Jerome J. regarding 3DS rendering since he has lots of experience - and anyone else is welcome to chime in too.

I posted this question on the OpenGL forums at: http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=302424#Post302424

Basically my issue is that I recently realized that a 3DS model can have multiple textures assigned to one “object” and the way it tells the object which textures to use is by listing the texture id for each triangle. I was using the JOGLUTILS code for rendering 3DS models and that code assumed you only had a single texture per object…meaning that when I was rendering things, the triangles were all getting the same texture and that was wrong.

I’m trying to modify the code to work correctly but I’m having trouble rendering. I initially tried to brute force render each triangle with a bind call to the appropriate texture but this was really slow and I was told that I should not have a BIND call within a glBegin/glEnd block (ie I used the glBegin(GL_TRIANGLES)). Also it was suggested that I sort the triangles to group the ones that share a texture (or material) together and hence only have as many bind calls as I have actual textures - rather than having as many bind calls as I have triangles. This will probably speed things up.

I was wondering if that was reasonable and is this what you’d recommend…this is for Immediate mode rendering.

My next question has to do with Vertex Arrays and VBOs. Because of the assumption of one texture per object, I had written VBO rendering code which renders all of the VBO buffer with the one texture. I guess I will now have to create separate VBO buffers for the textures, correct? Much like using the separate buckets for the Immediate mode?

I agree with the advice that you should group the triangles by “material” or texture and render them in buckets. When it comes to VBOs, you can consider the VBO to be rendering a single bucket. There is a possible optimization you could do with VBOs.

Instead of having the vertices, textures, normals and indices in VBOs for each bucket, you can have 1 set of VBOs store all of the vertex attributes for the model (vertices, normals, etc) and then have 1 element array VBO per bucket that has indices for just a single group.

That way you can bind the vertex attributes, and then iterate through a number of glDrawElement calls and bind each index VBO as needed. A further improvement would be to have a single index VBO, but sorted by texture, and then use the byte offset parameter to set the offset into the index VBO when rendering. Then you would still only do one VBO bind for indices, too.

Pseudo code might look like:


FloatBuffer vertices = readVertices(...); // or however else you get them...
FloatBuffer normals = readNormals(...);
FloatBuffer texCoods = readTextureCoords(...);

List<Triangle> tris = readTriangles(...); // or whatever format the 3DS loader gives them to you in
sortByMaterial(tris);
IntBuffer indices = convertToIndices(tris); // basically iterate and put vertex indices into buffer

// find and store index offsets for 1st element of each texture group
// you will compute the length of each group (both stored in simple object OffsetAndLength)
List<OffsetAndLength> groups = findGroups(tris); 

// then create, bind, and store the above buffer data into 4 VBOs:
// vertexVBOId, normalVBOId, textureVBOId, and indexVBOId

// to draw
glBindBuffer(GL_ARRAY_BUFFER, vertexVBOId);
glEnableClientState(GL_VERTEX_POINTER);
glVertexPointer(...);
glBindBuffer(GL_ARRAY_BUFFER, normalVBOId);
glEnableClientState(GL_NORMAL_POINTER);
glNormalPointer(...);
glBindBuffer(GL_ARRAY_BUFFER, textureVBOId);
glEnableClientState(GL_TEXTURE_COORD_POINTER);
glTexCoordPointer(...);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVBOId);
for (OffsetAndLength g: groups) {
   // I don't remember off hand the order of arguments here, but it's meant to render g.length tris
   // The *4 is because the offset is in bytes, and I assumed the indices would be ints (4 bytes).
   glDrawElement(GL_TRIANGLES, 0, g.length, GL_UNSIGNED_INT, g.offset * 4);
}

// Then next object ...

Thank you very much, I’ll try to see what I can do. Thanks for the pseudocode.

I’d really like to get this 3DS loader to a good level so I can finally return it to the wild (i.e. to JOGLUTILS). I’ve improved my version of it for the past couple of years until I realized this issue recently. It would be nice for everyone to use it.

So far the Immediate mode rendering seems to work correctly. I need to make sure it renders as fast as the naive case ran before (ie when I only had one material) - it will tell me if I need more optimization or not.

Also, I found out that some 3DS files also have bump maps for materials!!! LOL…yet another thing to try to figure out - but that will have to come much later.

Thanks.

I have a follow-up question, lhkbob…it relates to the colors.

I have a cross post of this question at the Opengl Forums: http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=302948#Post302948

In the case of 3DS files you can read in all of the vertices in a list but to get the colors for each vertex you have to loop through the faces and get the face material/color id and then apply it to the vertices of the face. In doing so you will get cases where a neighboring face will have a different material and so two of the vertices that you previously set to have a particular color will now be overwritten with the color of the second face. As a result you can end up having face #1 have only one vertex set to the original color and the other two vertices correspond to the color of face #2. When you draw them using those colors, the phong modeling (I believe) will then cause the colors to blend across the triangle and so for triangles along edges between two different materials/colors you will not have a crisp line but can get “bleed” through.

This is the picture showing the bleeding:

So, I was wondering what I could do to avoid this issue when using the vertex arrays and having a colors buffer that is a one-to-one correspondence with the original vertex buffer. And the original vertex buffer only contains unique vertices and hence no duplicates.

The suggestion I got from the opengl people is to duplicate vertices when I loop through the faces so that I can have multiple coincident vertices that have different materials/colors but they will be correctly aligned with the faces/triangles when they are rendered. I’m not convinced this is the optimal solution but I’m not exactly sure what else to do. I tried to instead get rid of my current glPointerCall and instead explicitly set the colors via glColor3d() inside the group-loop - it still had some bleed through, though it was in a black color and so I’m not sure if I have something else that could be the cause or not.

Their proposed solution is the best one you have available to you. In OpenGL a “vertex” is not just a position but it’s the set of all attributes needed to render the polygon at the given location. The problem you’re experiencing with colors also happens when you consider normal vectors and texture coordinates.

The simplest illustration of this is rendering a cube. Just considering positions, it has 8 vertices but each “vertex” needs to have 3 different normal vectors, depending on the polygon that is being rendered. When using VBOs, this forces you to use 24 vertices where the combination of position/normal is unique. If you wanted to color each face of the cube differently, you wouldn’t need to go beyond the 24 vertices, though, because the colors match up with the normal vectors.

I’m not sure how you’re handling the normal vectors from the 3DS files, and it may be that the 3DS file already has unique position/normals or it may be that you’ve managed to avoid this problem. When rendering “smooth” models from a lighting-perspective, each vertex will likely only have 1 normal and every polygon that uses it will use the same normal and relies on Gouraud shading to smooth out the shading. Once you need to have a crease or facet in your model (e.g. the cube), you’ll run into the above problem when combinding normals with positions.

The good news is that the solution to both your color problem and the position/normal problem are the same because both problems are just variations of the same base problem. When you generate the VBOs, you will want to create a map from (position,color,normal,…) to index. Then every time you encounter a new combination of attributes, you create a new index and store in the VBO and the map, and use that index value in your indices VBO instead of the index from the 3DS model. If you’ve already seen the attribute combination, you just look it up in the map and use that index value in your indices VBO.

For the majority of vertices in your models that aren’t on color/texture/normal boundaries, there will not be any duplicated positions stored in your VBOs so you won’t really be wasting memory.

Very good info, thank you. FYI, currently I was summing the normals from all coincident faces for the vertices. Then normalizing all normals.

It seems like the correct way is as you say and as other suggest so I might have to look into it.

Thanks again.