Skinning Doom MD5 on the GPU efficiently?

Started by
23 comments, last by Starfox 17 years, 10 months ago
I was planning to use Doom MD5 models (1 2) for animated stuff in my engine, but I can't figure out how to do the skinning on the GPU in some kind of reasonable fashion. Just a quick recap. We have bones with a transformation associated with them. Each vertex is represented by up to 4 weights. The weights each reference a bone and a weight value; the values sum up to 1. So far so good, this is standard. Here's where it gets messed up. Each weight has its own position. Let's look at what I need to send down to the GPU: 4 * 12 byte position vectors, 2 * 12 byte tangent space vectors, 8 byte tex coords, 4 byte blend indices, 16 byte blend weights. That adds up to 100 bytes per vertex. There is no freaking way I'm using 100 bytes for every single vertex. That's insane. Is there some nice way I can collapse this down to the single position per vertex that is considered normal? I don't know if a weighted average computed beforehand will actually give me correct results or not.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Advertisement
sorry this is no help, but im pretty sure in doom3 they would do skinning on the cpu, due to the fact with lighting + shadowing, each mesh perhaps will be rendered multiple times. something to consider if youre gonna do lighting/shadowing yourself.
each weight doesn t have its own position.
Usually you have a UBYTE4 (4 * 1 byte) indexing the 4 bone matrices used for this vertex stored in a matrix array. This bone matrices array will be passed to the vertex shader through uniform parameter.
Then you have 3 floats representing the weight of each bone, the 4th one is given by w3 = (1.0 - w0 - w1 - w2).
This way you have an overhead of 3 * 4 + 4 = 16 byte per vertex.
Gregory Jaegy[Homepage]
That's the conventional way of doing things, yes. The problem is that is not how D3 models are stored! Take a look at this excerpt:
weight 162 3 0.2192307562 ( 10.5320158005 -8.3126058578 6.2153153419 )
The fields are index, joint index, weight value, and position. Each vertex references up to four of these.

Now you begin to see why I'm a little confused? I know D3 does CPU skinning, that's not news to me, but surely this came up at some point?
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
I don't think it did come up. The Doom 3 meshes were *never* meant to be skinned on the GPU, hence the format is layed out in a way that is not friendly to GPUs, but presumably is friendly (or at least useful) to the way they need the info.
I fully agree with rick_appleton. The basic MD5 data layout is ment to be calculated on CPU.
But, there´s also good news. Apparently, Quake4 can do skinning (and shadowvolume extrsion) on the GPU. You can find the corresponding shaders inside the pk4 files. I assume, that in order to be able to do this, the MD5 models are internally converted into a GPU friendly representation (they call it MD5R) at load time. Download the Quake4 demo and see for yourself :)
There are two ways to perform skinning.
One is the way where you only store weights and a bone index per vertex. This is most efficient for GPU skinning.

Another way is to store an offset vector for each weight/influence. This seems to be what Doom 3 does then. It makes sense, as it is faster to perform skinning on the CPU that way. This eliminates some matrix multiplies if I can remember correctly. Has been like 8 years ago since I have done it that way so I can't exactly remember the details anymore.

If I remember correctly, the reason why this fast CPU skinning would be slower on the GPU is because it doesn't require you to have such big per vertex data. And I think it means you don't have to multiply by the inverse bind pose matrix. But rather you could do something like:

for all influences
finalPos += boneMatrix * offset * weight;

Instead of having to basically do:

for all influences
finalPos += bindPosePos * boneMatrix * Inverse( bindPoseMatrix ) * weight;


Of course you can precalc the bonematrix * invbindposematrix before going into the "for all vertices" loop. You could also pass those to the shader, but that would increase the amount of draw calls, so not a good thing.

Cheers,
- John
Sorry, I forgot to reply to your real question.
I think you can just ignore the offset values for each influence.
And just calculate the inverse bind pose matrices from the initial pose of the model (in which it has been exported). Calc the world space matrices of that, then take the inverse of the world space matrix of that bone.

Then take the world space vertex coordinates after skinning as well. Probably they are already exported in this way. And use these as start point for the skinning, and proceed with the regular way of doing skinning on GPU.

- John
Ok, I'm just getting back to this.

So, that seems really helpful, thanks. Unfortunately, I didn't understand a critical part of what you said.
Quote:And just calculate the inverse bind pose matrices from the initial pose of the model
What is the inverse bind pose matrix, and how do I find it?
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
The bind pose is the pose that is stored in the .md5mesh (with arms and legs straight out). Take the bone matrices for this pose and inverse (transpose the rotation matrix) them and you will get the inverse bind pose matrices. I use these matrices for normal transformation.
[size="1"]Perl - Made by Idiots, Java - Made for Idiots, C++ - Envied by Idiots | http://sunray.cplusplus.se

This topic is closed to new replies.

Advertisement