Bone animation and skinning

Started by
1 comment, last by iedoc 12 years ago
Hi everyone,

I'm not exactly a beginner (I do quite a bit of OpenGL coding), but I'd like some advice on skeletal animation systems and how they work.
This is not a post about the artistry involved - I have no questions about how Maya/3DS Max or other packages work.

My question is really, how do I use the following data to calculate the correct position of a vertex?

for each bone I have:
- a bind pose matrix.
- a function to get the local bone transform at a specific time.

for each vertex I have:
- a position (in model space).
- an array of bone ids.
- an array of bone weights (corresponding to bone ids).

How do I go about transforming the vertex using that info?
Could someone please also explain what exactly the bind pose is used for? I've read a few documents about this, but none are very clear.

I've been stuck on this for a few days - so if I could finally understand it, it would be great.

Any help is much appreciated.

Thanks in advance!
Advertisement
Bind pose is the skeletal pose, that corresponds to untransformed skin. I.e. for normal biped characters it is T-pose. I assume it is the global transformation - if not, you have to calculate the global bind matrix yourself.
The vertex transformation is done the following way:

Let matrix B2S be the rest (bind) global transformation of bone.
Let matrix P2S be the rest (bind) global transformation of the parent of given bone
Let matrix AB2P be the animated local transformation of bone
Let V be untransformed vertex and V' animated vertex

Then

V' = (P2S * AB2P * B2S(-1))

The trick is to precalculate everything you can - B2S(-1) can be calculated once for bone, P2S * AB2P can be calculated once for animation frame

Now, if you have more than one bone transforming given vertex you just have to calculate weighted average of all transformations (i.e. weighted average of transformed vectors).
How to best do that in vertex shader depends on shader model used. If you do not want to use integer attributes and dynamic loops, you will use matrix palettes. Basically each vertex is always influenced by all bones. If in reality given bone does not transform given vertex, it's weight will be 0 for that bone.
Simple matrix palette implementation can kill performance because you have very long list of matrix operations for each vertex, most with weight 0. Depending on model and skeleton complexity it may be beneficial (or necessary) to break model into fragments so that each fragment is influenced by only subset of all bones.
Lauris Kaplinski

First technology demo of my game Shinya is out: http://lauris.kaplinski.com/shinya
Khayyam 3D - a freeware poser and scene builder application: http://khayyam.kaplinski.com/
The bind pose is basically how your bones are first oriented

to find the position of a vertex, you will first find the vertex's weights position in bone space, then add the bones position to the weight making the weight now in "model" space, then you will average all the vertex's weights by using the bias factor to get the vertex's final position

To use the bone orientation quaternions to find the weights position, you first find the quaternions conjugate, which is:

orientationConjugate = (-orientation.x, -orientation.y, -orientation.z, orientation.w); // Negate the xyz axis' of the original orientation quaternion, and leave the w component alone

then you do a quaternion/quaternion multiplication with the orientation quaternion and the weights position:

finalWeightPos = orientation * weightPos;

you then multiply the finalWeightPos with the orientations conjugate:

finalWeightPos = finalWeightPos * orientationConjugate

You must do the multiplication in that order because quaternions are "noncomunitive" meaning the order in which you multiply them together DOES matter.

Finally, to get the vertex's final position, you will add the bones position to the weight, then multiply each weights position with it's bias factor (all the weights bias factor for each vertex adds up to 1), then add the resulting positions together to get the final vertex position:

finalWeightPos = finalWeightPos * weightBias;

finalWeightPos += bonePos;

finalVertexPos += finalWeightPos;


Some links that might be helpfull:

http://www.braynzars...php?p=D3D11MD51
http://www.braynzars...php?p=D3D11MD52
http://www.cprogramm...uaternions.html
http://content.gpwik...ic_Bones_System

so all together maybe something like this:

float3 finalVertexPos = float3(0,0,0);

for(int i = 0; i < numWeights; ++i)
{
Weight currWeight = modelWeights[currVertex.WeightStart + i];

float4 orientation = bones[currWeight.BoneID].Orientation;

float4 orientationConjugate = float4(-orientation.x, -orientation.y, -orientation.z, -orientation.w);

float4 finalWeightPos = (orientation * currWeight.Pos) * orientationConjugate;

finalWeightPos *= currWeight.Bias;

finalWeightPos += bones[currWeight.BoneID].Pos;

finalVertexPos += float3(finalWeightPos.x, finalWeightPos.y, finalWeightPos.z);
}


The "Weight" structure will need to have the weights bias factor, position, and bone id

Also, multiplying two quaternions together is not as simple as just "quat1 * quat2". opengl probably has a function to multiply them together, i know directx does (XMQuaternionMultiply).

Otherwise, to multiply two quaternions together: (copied from one of the links i mentioned above)

[color=#000000][font=verdana, sans-serif]

(Q[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

* Q[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

).w = (w[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

w[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

- x[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

x[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

- y[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

y[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

- z[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

z[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

)[/font]
[color=#000000][font=verdana, sans-serif]

(Q[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

* Q[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

).x = (w[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

x[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

+ x[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

w[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

+ y[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

z[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

- z[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

y[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

)[/font]
[color=#000000][font=verdana, sans-serif]

(Q[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

* Q[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

).y = (w[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

y[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

- x[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

z[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

+ y[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

w[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

+ z[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

x[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

)[/font]
[color=#000000][font=verdana, sans-serif]

(Q[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

* Q[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

).z = (w[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

z[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

+ x[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

y[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

- y[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

x[/font][sub]2[/sub][color=#000000][font=verdana, sans-serif]

+ z[/font][sub]1[/sub][color=#000000][font=verdana, sans-serif]

w[/font][sub]2)[/sub]

[sub]The "w" component of the weights position is just zero (w = 0)[/sub]

This topic is closed to new replies.

Advertisement