Skeletal animation in Direct3D

Started by
5 comments, last by OklyDokly 20 years, 1 month ago
Hi, I''m trying to set up a skeletal animation system using Direct3D and Half-Life MDL files. For educational purposes I want to avoid using the DirectX SkinnedMesh functionality. What I''m finding however is that if I want to manipulate the vertices in the model, in relation to the bone, (bearing in mind that the models have already been stripped), it seems that I''ll have to manipulate each vertex in the vertex buffer directly, multiplying by their corresponding bone matrices. I''m concerned that this might be rather slow. The model that I''m testing with at the moment already has 1077 non-unique vertices. Does anyone have a better solution to this problem, or is this just the way skeletal animation is?
Advertisement
Kind of, yes.

Either calculate on the CPU and store in a dynamic vertex buffer, or...

Put the data in a static vertex buffer, and use a vertex shader to apply bone transformations. The bone matrices are put into constant registers (each matrix using 3 constant regs), and the bone numbers and weights are put into the vertex structure.

You''ll probably want to use the second technique. All modern hardware will be faster using the second technique, and older hardware, doing software processing with shader emulation, will be the same speed as using the first technique, or faster, as Intel and AMD have both written optimized SSE and 3DNow shader emulators.
I did think of using vertex shaders, but the problem I came across was that there are only 96 constant registers.

The model I am using at the moment I think has 44 bones, which amounts to 132 required registers.

It also seems a little wasteful to use so many constant registers which could readily be used for other stuff.

There may be a way round this problem by not putting all the bones in the constant registers at once. Because the vertices are split into strips and fans, it would probably be the case that I only need a few bones at a time. However this does seem a bit of a hassle, and I still run the risk that I oberload the vertex shader constants :S
We break our models vertices into groups by material. We then break each of those into sections that use a maximum of about 20 bones... that leaves space for world, view, projection, texture transform, light, fog, and other effect constants.

Do you think it''s more wasteful to send a few constants to the card, or to use the CPU, AGP bandwidth, etc. to transfer a whole model everytime it''s needed?
You can generally get away with about 26 bones in a vs_1_1 shader, with room for other stuff. For vs_2_0, I use upwards of 35 (about what comes out of a 3DSMax Physique model). The nice thing about D3DX''s SkinInfo stuff is it''ll break up a mesh based on the matrix palette size, to give you sane subsets.

I like pie.
[sub]My spoon is too big.[/sub]
quote:Either calculate on the CPU and store in a dynamic vertex buffer, or...

Put the data in a static vertex buffer, and use a vertex shader to apply bone transformations. The bone matrices are put into constant registers (each matrix using 3 constant regs), and the bone numbers and weights are put into the vertex structure.

- I''ve done some research into the MDL format sometime back, and I found the number of bones rather large to fit in a shader (though it''s possible if stored as quats+translations, but the quat-to-matrix conversion isn''t short, and few constant registers are left).
Of course, one can always split the model into groups. However, that requires an artist. And if you do have an artist, then the MDL format is a bad one to begin with. It''s designed with the hardware of the late nineties in mind.

- I stumbled upon a bug in ATi''s drivers, regarding the Radeon8500 where you couldn''t use zero-weights indexed vertex blending (caused a total lockup), and that ruled out the hardware FFP path too.

- I ended up using CPU skinning + streaming with a dynamic buffer.

quote:The model I am using at the moment I think has 44 bones, which amounts to 132 required registers.

- MDL has a translation per-bone as well, if I recall correctly.
- 44 bones + 44 translations can be stored in 88 constants if stored as (quats+translations). But that''s still a bad idea.

quote:Because the vertices are split into strips and fans, it would probably be the case that I only need a few bones at a time. However this does seem a bit of a hassle, and I still run the risk that I oberload the vertex shader constants :S

Beware of this strip-fan thing. MDL is extremely unoptimized for today''s hardware and all versions of DirectX. I''ve done some sort of a scan on the batching of some models, and I''ve found that there are cases when the *maximum* number of triangles per batch was 7. Not to mention that there were loads of 1 triangle batches.

What I did was write a simple application that converted MDL to something more useful:
- Removed all useless data from file (things like transitional sequences, sequence groups don''t exist in the models that are out there, or that are exported by MilkShape. Body parts are rarely if ever used, ...etc)
- Use indexed triangle lists

Muhammad Haggag
Bitwise account: MHaggag -

quote:Original post by Coder
- I''ve done some research into the MDL format sometime back, and I found the number of bones rather large to fit in a shader (though it''s possible if stored as quats+translations, but the quat-to-matrix conversion isn''t short, and few constant registers are left).
Of course, one can always split the model into groups. However, that requires an artist.

You want your shaders fast, so compute the matrix and put it in 3 constants per bone. The effort involved in trying to drop that to 2 makes it not worth it.

You don''t need an artist to split your model. I wrote our bone-splitting code in a day or so. It tries to get all polys with low boneids first.

This is going from memory/logic, so I might have left something out...

maxboneid = 3numbonesleft = 20while(facesremain){  begin new mesh section  while(maxboneid < bonecountinmesh)  {     for each face    {      each bone on each vertex is < maxboneid?      {        Are the number of newboneids less than numbonesleft?        {          numbonesleft--          add new boneids to already accepted array          Add face to accepted face list        }      }    }    maxboneid++ // try higher boneids next pass  }  Accumulate vertices used in accepted faces  Remap vertices from 0 to numverts  Remap indices in faces  Remap morph target vertices  Remap boneids in vertices to accepted boneid subset  Stripify faces}

This topic is closed to new replies.

Advertisement