Re-using verts with diff norms/tex coords/colours etc?

Started by
6 comments, last by UselessRob 19 years, 3 months ago
Howdy I'm new to DirectX, so I hope this isn't a stupid question. I'm trying to do a version of my program that uses DX instead of OpenGL. So far, I've found equivalent DX commands for most things, so it's all good. However, I'm struggling to get my head around this vertex/index buffer deal. I get that you make buffers of verts and then indices for drawing your triangles. But how do you re-use verts with different tex coords, colours, normals etc? For example, the model format I use stores the tex, and normal info with the triangles along with the 3 index values in the vertex array for each corner. This way, you can use less verts because they are re-used, and the info that changes is with the triangles. For example you can then make a cube with just 8 verts, but each side has different normals, colours, tex coords etc. Under GL it's no prob cos I just go thru and pass the triangles, but D3D seems to want the tex/norm info to be with the verts. This would mean you need to duplicate any verts that are used more than once, which is a big problem because the modelling program just doesn't do that, and it can't be as efficient anyway, can it? So I'm asking how do I do this? How do I re-use verts with different norms/tex values? I figure with something this powerful there has to be a way, and I just can't see it being new and somewhat clueless. I realise I could go thru and do a call to DrawPrimitive for each triangle, but that's gotta be the worst way to do it. So any help/suggestions would be greatly appreciated. Thanks! Rob.
Advertisement
In short: You can use multiple streams to do this.

What you basically do is create a vertex buffer for each different data piece you want - position, normal, colour etc. then use SetStreamSource() to use them. You'll know better than me what your specific needs are, but that's the basic idea. If you're using an index buffer you'll need to set up appropriate parameters like baseVertexIndex or whatever it is in the DrawIndexedPrimitive() call otherwise your indexing won't match up with the buffers.

All that said, I'd just duplicate the vertices. From the sounds of what you're doing you'll just be duplicating the position, since the colour/normal are different anyway. It'll be faster, I think, because it won't involve the runtime mucking about with the separate streams for you, and you won't have as many DrawIndexedPrimitive() calls to make. This is not really any different to using vertex arrays in OpenGL, but it catches you out if you've been using immediate mode previously.

By the way, it is slightly odd for the model format to store texcoords & normals with the triangle, but then just index into a position array. I'd have expected the entire piece of vertex data to be stored as one (position, texcoord, normal, colour etc.) and the triangle index that data appropriately.

-Mezz
Thanks for the quick reply.

Wouldn't having multiple streams just cause the same problem tho? I mean, if you have the normals in one stream and the pos in another, they still have to line up with the index don't they? So you would still need the positions to be duplicated to match up with the normals? Or am I thinking of it the wrong way?

Basically, I'm still using the Milkshape file format. I was hoping to use my same skeleton animation system and all that, but just change the way it's drawn so it's using directx. The ms3d format gives you a list of verts, then a list of triangles which include the array positions for each vert pos for the 3 corners, then it has 3 sets of tex coords, 3 normals etc. This means the same positions are re-used by different triangles. The advantage of this, and the reason I don't want to duplicate them (besides the fact that ms3d doesn't actually let you do that unless you go thru and try and split stuff manually) is that when the model is animation each vert is moved, then the triangles are drawn with the moved points. If I duplicated them I would have to animate points more than once, or go thru and build new vert buffers for each frame. Neither sounds like the way to go.

I did try converting the models to .x, but besides the fact that it means I need to re-code half the project, it also left me with big gaps in the models at each joint, so I'm guessing the conversion isn't actually giving a .x that's exactly the same as the .ms3d.

Geez that was a long one. I hope it made sense, and I'll still open to any help/suggestions.

Many thanks!

Rob.
Quote:Original post by UselessRob
Wouldn't having multiple streams just cause the same problem tho?

Yes, streams won't help with this problem. You need to create duplicate vertices if ANY vertex data changes (position, UV, normal, color, etc). This only occurs on sharp corners where you don't have smooth normals, and on material seams. It's a very low percentage of a mesh that will be affected, especially as we get more detailed geometry.

What I do is this:
Gather faces and vertices from input formatCreate a sub-mesh for each materialFor each submesh{  Copy faces and vertices of each material to it's sub-mesh,  updating morph info, and reindexing the faces to use the new  subset of vertices.  If using bones, and we have more bones than we can program  into constant registers, break the sub-mesh into sub-sub-meshes.  Mix face and vertex data.  On first use, just place face normal and uvs  into the vertex.  On subsequent use, if there is no matching vertex,  create a duplicate.  I keep an index to the duplicates to quickly scan  them as I process more faces.  Update morphs to include duplicates  Sort faces to be optimized for post-T&L cache  Sort vertices for pre T&L cache.  Update morphs and faces to use new vertex numbers.}Export processed data to something the engine would like.
Quote:Original post by UselessRob
Wouldn't having multiple streams just cause the same problem tho? I mean, if you have the normals in one stream and the pos in another, they still have to line up with the index don't they? So you would still need the positions to be duplicated to match up with the normals? Or am I thinking of it the wrong way?


Yes, it was my mistake for trying to think about code before 9am. My suggestion is going with Namethatnobodyelsetook's algorithm and duplicate those verts ;)

-Mezz
Thanks for the explanation Namethatnobodyelsetook, I think I get what you mean, but you kind of lost me on the subsequent use bit...

So for each material make a new set of verts that include the stuff in the triangles. But you've lost me with the morphing. After I do any anim/morph then I'll have to go thru and make sure each mesh is up to date. Isn't that a bit slow? Or is there a good way to do it?

I can see how this'll work, but I'm not thinking straight. I keep thinking it's more work that should be necessary or something. :)

I'm also not familiar with the sorted for the T&L. How does it want them sorted (what order)?

I'm wondering with the duplicate vert positions, is it possible to just use pointers? For example, make a mesh of verts that point to the pos in the main vert array, then pass those pointers (which will each point to the position this vert needs) to DrawIndexedPrimitive? This would mean no duplicating vert, or having to re-duplicate/update for each frame of animation. Or is that just not possible?

Thanks again. You've got me thinking, and it's appreciated.

Rob.
Quote:Thanks for the explanation Namethatnobodyelsetook, I think I get what you mean, but you kind of lost me on the subsequent use bit...

At first you have vertex data and face data. What each contains depends on what your modelling package/import format supports. Our vertices contain position, diffuse color, specular color, boneids, and bone weights. Our faces contain 3 vertex indices, a normal per vertex, and n sets of UVs (usually 1 2D UV) per vertex. You can ignore the morphing parts until/if ever you support morphing of your mesh. My description assumes you've got seperate morph data from your vertex data, which means you don't have to supply a full morph mesh, just the vertices that morph. Again, ignore it for now.

When we split by materials, copy over the faces and vertex data. The data is NOT yet combined.

Now we start to mix the face and vertex data. The first time a face uses a vertex, it can just put the UV(s) and normal from the face into the vertex. The second (and third, and fourth...) time a face uses a vertex you check if the UV(s) and normal matches the vertex (from the first face using the vertex, and the second, and the third, etc...). If you find a match, you can just update the face to point to the correct vertex. If you don't find a match, you must add a new vertex. It will share all the regular exported per vertex information, but will have it's own data that was exported per face. You can then update the face index to point to the new vertex.

Consider a cube. When processing the front face, you'll use two of the vertices twice, but they'll be exact matches. They both have the same normal direction and UV coordinates, so you can share those vertices. Now when you process the bottom face, you'll have to create duplicates for the two vertices that the front face used, as the normals will differ. However, you'll still be able to share two vertices in the bottom face, as it will also have two triangles with the same normals.

Quote:So for each material make a new set of verts that include the stuff in the triangles. But you've lost me with the morphing. After I do any anim/morph then I'll have to go thru and make sure each mesh is up to date. Isn't that a bit slow? Or is there a good way to do it?

This is all preprocessing. You can do this in an exporter, in a preprocessing step that create a PAK or WAD or whatever you call your file. You can do this at mesh load time though it will likely add a few seconds to your load time. The morph comments are just about keeping your morphing data upto date and in sync. If you add a duplicate of vertex 42, and vertex 42 was morphing, then you'll want to also morph the new duplicated vertex.

Quote:I'm also not familiar with the sorted for the T&L. How does it want them sorted (what order)?

When your card runs a vertex through a vertex shader (or fixed pipeline T&L), it caches the result in an small (18-24 vertex) fifo queue. If you have a second face which indexes the same vertex before it leaves the fifo, it doesn't have to run through the vertex shader again. You don't need this, it's just an optimization. Basically you reorder your faces such that you reuse as many vertices as soon as possible. Sorting for pre-T&L is just reordering the vertices such that you use them in order. Instead of face 0 refering to vertices 42, 98, 164, you move the vertices around so that it's using vertex 0, 1, 2. This helps the card fetch data quicker. If your card's cache line is 128 bytes wide, and your vertex is 32 bytes, the sorted data can fetch all three needed vertices at once, instead of requiring 3 seperate fetches.

Quote:I'm wondering with the duplicate vert positions, is it possible to just use pointers? For example, make a mesh of verts that point to the pos in the main vert array, then pass those pointers (which will each point to the position this vert needs) to DrawIndexedPrimitive? This would mean no duplicating vert, or having to re-duplicate/update for each frame of animation. Or is that just not possible?

Not possible. Vertices can only contain data, not pointers. It really only affects a small percentage of data in real meshes. So small, that it's not worth the trouble of trying to fix it. Just accept that you'll have a small amount of duplicate data.
Ahhhh ok. Now I think I understand. That's a very cool explanation, and it sounds like a great solution. Thanks a lot!

For some reason I was thinking of it all wrong. Not only with the separate meshes, but also worrying that I'd have to fiddle with duplicates each frame and all that, which is stupid because the duplicates would ofcourse know what mesh/bone/whatever they belong too anyway. So that was just my own stupidity.

But that clears a whole lot of stuff up. Thanks again! I'll try it out.

This topic is closed to new replies.

Advertisement