Proper order of operations for skinning

Started by
4 comments, last by bobhowdy 11 years, 6 months ago
I'm using a .x (direct x) model file exported from Blender. It contains the mesh, armature and animation data for my model. I can get the model to render properly in its bind pose. I'm trying to animate it now. However, no matter what I try, I can't get the model to transform properly when rendering an animation pose. It's all deformed.

I created a simple object that is simply two bones and a cuboid. It looks like an elbow joint of an arm sticking straight up in the air. I have the following matrices:


Root (Contains everything) = rootMatrix
Cube (Contains the entire model) = modelMatrix
Armature (Contains the two bones) = armatureMatrix
Armature_RootBase (Root "upper arm" bone)
SkinMatrix_RootBase (The skin matrix for the root bone)
Armature_TipTop (is the "forearm" bone)
SkinMatrix_TipTop (The skin matrix for the second bone)

When loading the model in, I transform all the vertices by the bind matrix to have the bind pose as the default. I get the bind matrix with:


Matrix4f bindMatrix = new Matrix4f();
Matrix4f.mul(bindMatrix, rootMatrix, bindMatrix);
Matrix4f.mul(bindMatrix, modelMatrix, bindMatrix);




That works great. The bind pose is exactly as it should be, how it's shown in Blender.

Now when I try to animate is where the problems come in. I'm using GLSL to apply the bone matrices to the vertices. When I send identity matrices for all the bones it works fine. I've done tests and I know the GLSL is working and sending the matrices and applying the bones to the correct vertices. The main problem is I don't know how to properly get the matrices.

I have a bind pose model and I need to take a rotation, translation and scale and create the proper matrix to transform the model.

I'll explain my current non-working process, because it might just need a little tweak to make it work.

I have structures to mimic the structure of the armature. At the moment I'm exporting full animations from Blender (I'll work out interpolation later), so I have a key frame for every frame. I can apply an animation to a model and I'm able to lookup the animation rotation, translation and scale for any bone.

Basically I go through and calculate the new bone matrices, then send them to my shader to be applied to the bones. So the update process goes like this:


Starting with the root bone

UpdateFinalTransform(Bone)
calculateAnimationTransform()
foreach child bone
UpdateFinalTransform(child)

Where calculateAnimationTransform() looks like this:


transformMatrix.setIdentity();
combinedMatrix.setIdentity(); //clear the transform and animation matrices

Matrix4f bindMatrix = new Matrix4f(getBindMatrix()); //get the bind matrix for this model
Matrix4f.mul(transformMatrix, (Matrix4f)bindMatrix.invert(), transformMatrix); //multiply by the inverted bind matrix
Matrix4f skinMatrix = getSkinWeightMatrix(); //get the skin matrix for this bone

Matrix4f animationTransform = new Matrix4f();
//rotate, scale, translate

animationTransform.applyQuaternion(new Quaternion(rotation));
tmpMatrix.setIdentity();
tmpMatrix.m00 = scale.x;
tmpMatrix.m11 = scale.y;
tmpMatrix.m22 = scale.z;
Matrix4f.mul(animationTransform, tmpMatrix, animationTransform);
tmpMatrix.setIdentity();
tmpMatrix.m30 = position.x;
tmpMatrix.m31 = position.y;
tmpMatrix.m32 = position.z;
Matrix4f.mul(animationTransform, tmpMatrix, animationTransform);
setCombinedMatrix(animationTransform); //set the animation transform for this bone, so that children bones can access it

combinedMatrix = getTransformMatrix(); //get the combined transform of this animation transform * parent animation transform
Matrix4f finalMatrix = Matrix4f.mul(skinMatrix, combinedMatrix, null); //skin the combined matrix
Matrix4f.mul(finalMatrix, transformMatrix, transformMatrix); //apply the inverted bind pose
Matrix4f.mul((Matrix4f)bindMatrix.invert(), transformMatrix, transformMatrix); //undo the inverted bind pose



each bone has that getTransformMatrix() method that looks like this:

if (parent == null) {
return Matrix4f.mul(combinedMatrix, parentArmature.getArmatureRootMatrix(), null);
} else {
return Matrix4f.mul(combinedMatrix, parent.getCombinedMatrix(), null);
}


combinedMatrix is set to its default armature matrix if an animation matrix is not defined.


So my questions.

1. Am I creating the animationTransform properly?
2. Is this the correct order of transforms?
3. What am I missing?
4. How do I do it properly?
Advertisement
Edit as I missed a section of your post and ended up misreading the question as a result.

I'm not sure why you have a seperate "skinMatrix" and "transformMatrix" being applied to the animation:

[indent=1]Export your vertex data post-transformed, so that the model looks correct when skinned with identity matricies.

Export the inverse of the worldspace bind pose for each joint.
This matrix is used to bring the vertex back into local space as necessary.

At runtime, the skinning matrix is the animated worldspace matrix multiplied by the inverse-bind-pose matrix.
If the worldspace matrices match the original bind pose, you get identity matrices and so your model is correct. If you move the joints around, you will see the mesh move correctly.

The local-space animation matrix is built by composing a rotation matrix from the quaternion (which should just end up being the top left 3x3), multiplying the topleft 3x3 by the scale components (no need to multiply the bottom row or right column) then setting the translation row (or column depending on whether your matrices are transposed) You are better off writing the matrix manually instead of multiplying in the data:



On the note of inverse bind pose matrices, the big reason to stick with storing the inverse bind pose and leaving the mesh data in transformed space on export is for multiple influences per vertex. If you were to transform the mesh into local space, it would be a unique local space per bone influence.

When dealing with multiple influences you can either run the vertex through each of the matrices separately, then blend the results, or blend the matrices together then multiply the vertex through it. There are other ways of doing this blending that give nicer results, less 'candy wrapping' etc, and are worth looking at once you have your core steps up and running.
Thanks for the response!

I'm not sure why you have a seperate "skinMatrix" and "transformMatrix" being applied to the animation:[/quote]

The .x file has a skinWeights section, in that it has a matrix, that's the skinMatrix I'm referring to. The transformMatrix is where I'm storing the result of all the matrix operations.

Export your vertex data post-transformed, so that the model looks correct when skinned with identity matricies.[/quote]
I'm doing that. If I set my bone matrices to identity, I get the model as it looks in Blender.

Export the inverse of the worldspace bind pose for each joint.[/quote]
Is the worldspace bind pose the same as the bind pose used to transform the vertices to get the "post-transformed vertex data"? i.e. The matrix obtained by rootMatrix*modelMatrix? If so the bind pose is the same for each joint... so I don't understand.

So you're saying the animation matrix should be created like the following?

Matrix4f animationTransform = new Quaternion(rotation).toMatrix();
animationTransform.m00 *= scale.x;
animationTransform.m01 *= scale.x;
animationTransform.m02 *= scale.x;

animationTransform.m10 *= scale.y;
animationTransform.m11 *= scale.y;
animationTransform.m12 *= scale.y;

animationTransform.m20 *= scale.z;
animationTransform.m21 *= scale.z;
animationTransform.m22 *= scale.z;

animationTransform.m30 = position.x;
animationTransform.m31 = position.y;
animationTransform.m32 = position.z;



Where does the armatureMatrix and parent animation transforms come into play?

I know that


Matrix4f.mul(transformMatrix, boneMatrix, transformMatrix);
Matrix4f.mul(transformMatrix, skinMatrix, transformMatrix);


Results in the identity matrix. But if I multiply in the animationTransform from above, things get wonky. I made an animation that's just the bind pose. But the animationTransform is not the identity matrix when the model is in the bind pose, so what's the deal?

Export the inverse of the worldspace bind pose for each joint.

Is the worldspace bind pose the same as the bind pose used to transform the vertices to get the "post-transformed vertex data"? i.e. The matrix obtained by rootMatrix*modelMatrix? If so the bind pose is the same for each joint... so I don't understand.
[/quote]
The worldspace bindpose matrix for a bone is the the world space matrix for that bone itself in blender. If you only have local space matrices for the bones in blender, then you have to work up the tree from the root node to generate them.


So you're saying the animation matrix should be created like the following?

Matrix4f animationTransform = new Quaternion(rotation).toMatrix();
animationTransform.m00 *= scale.x;
animationTransform.m01 *= scale.x;
animationTransform.m02 *= scale.x;

animationTransform.m10 *= scale.y;
animationTransform.m11 *= scale.y;
animationTransform.m12 *= scale.y;

animationTransform.m20 *= scale.z;
animationTransform.m21 *= scale.z;
animationTransform.m22 *= scale.z;

animationTransform.m30 = position.x;
animationTransform.m31 = position.y;
animationTransform.m32 = position.z;



Where does the armatureMatrix and parent animation transforms come into play?

That animationTransform matrix is the local-space matrix generated by a single frame of animation, relative to the space of the parent.
The parent animation transforms come into play when generating the world transform for the parent, as its the world transform of the parent you need to pass down to the children in the tree.

The armatureMatrix in your case might be a pre-concatenation matrix for the animation transform?
I've never needed anything beyond: parentBoneWorldspace * childAnimatedLocalspace * inverseBindPose


I know that


Matrix4f.mul(transformMatrix, boneMatrix, transformMatrix);
Matrix4f.mul(transformMatrix, skinMatrix, transformMatrix);


Results in the identity matrix. But if I multiply in the animationTransform from above, things get wonky. I made an animation that's just the bind pose. But the animationTransform is not the identity matrix when the model is in the bind pose, so what's the deal?

animationTransform is a local space matrix. When your animationTransforms are converted to world space by walking the tree and concatenating the transforms, the resulting matricies should look like the boneMatrix values. Unless your boneMatrix (and corresponding skinMatrix) are in a different coordinate space.

I suggest writing some debug rendering code that draws the matrices as axis-markers connected by lines showing the hierarchy.
I'm still not getting this sad.png

Lets try with some matrices. This is what I have to work with for the root bone:

root
1.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0
0.0 1.0 -0.0 0.0
0.0 0.0 0.0 1.0

model
0.5 0.0 0.0 0.0
0.0 0.5 0.0 0.0
0.0 0.0 2.0 2.0
0.0 0.0 0.0 1.0

armature
2.0 0.0 0.0 0.0
0.0 2.0 0.0 0.0
0.0 0.0 0.5 -1.0
0.0 0.0 0.0 1.0

rootbone
1.0 0.0 0.0 0.0
0.0 0.0 -1.0 0.0
0.0 1.0 0.0 0.0
0.0 0.0 0.0 1.0

skin
0.5 0.0 0.0 0.0
0.0 0.0 2.0 2.0
0.0 -0.5 0.0 0.0
0.0 0.0 0.0 1.0

animation
0.0 -1.0 0.0 0.0
-1.0 0.0 0.0 0.0
0.0 0.0 -1.0 0.0
0.0 0.0 0.0 1.0

I know that root*model*armature*rootBone*skin results in the identity matrix. But I don't see anything here that would turn my animation matrix back into the identity matrix. It seems like it should be turned back into the identity matrix or the bind pose matrix.

My model is sort of a test model. It's the shape of two bones, the two bones that animate it. So I can see where the bones are easily.
Figured it out. The animation matrix was being calculated wrong.

This topic is closed to new replies.

Advertisement