Vertex Skinning, Coordinate Systems

Started by
7 comments, last by gentlesea 16 years, 1 month ago
Hello everyone. I am currently working on a program that is able to display a mesh generated by Maya and exported using a modified version of CometSkinWeight which allows me to save a mesh using the following (relevant) attributs: <Vertex> <VertexX>-0.5</VertexX> <VertexY>-0.5</VertexY> <VertexZ>0.5</VertexZ> <VertexWeight>joint1 0.678</VertexWeight> <VertexWeight>joint2 0.3</VertexWeight> <VertexWeight>joint3 0.022</VertexWeight> </Vertex> <Joint> <JointName>joint1</JointName> <JointWorldMatrix>1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1</JointWorldMatrix> </Joint> Now the idea of vertex skinning is to blend the vertices using skin weights and references to the corresponding bones with a CG shader like the one from http://www.gamasutra.com/features/20030325/fernando_05.shtml My shader is working and the data is loaded properly. If I am using an identity matrix my program shows my the bind pose (the skeleton remains unmoved) correctly. But if I start modifying my <JointWorldMatrix> attributs by translating the joints in Maya for example the are not correctly shown in my program. The question to all those Maya gurus out there is: Which coordinate system is used for the bones? Do I have to transform them into world coordinate system first? Any help is appreciated
Advertisement
As far as I know, you have to transform the bones from bind space to bone space (which is done by the offset transform, which is loaded from the file), and then to character space (which is done by the bone's combined transform, which is computed at runtime by interpolating the keyframes of the animation sequence).

There's an article here that gives a fairly detailed description of the process.
Thank you for the reply. The paper was quite useful but I still can't figure out which matrix in maya corresponds to the matrix in their terminology.

I figured out, that we have (at least) 3 kinds of different matrices:

The matrix that transform the vertex (given in world coordinates) into the local bone coordinates (different for each bone). Let me call this one

World2BoneMatrix.

Then there is the transform matrix in bone coordinates (usually only the rotation). This matrix changes only if the current bone is rotated. If a parent bone is rotated, it remains constant. This is valid for the .matrix attribute of a joint in Maya. Can anyone confirm that this is the matrix I am looking for?

I call it RotationBoneMatrix.

The third matrix transforms the rotated coordinates back to the world coordinates. I call it therefore

Bone2WorldMatrix.

This is the inverse of my World2BoneMatrix.

I tried using a combination of multipling

joint2.inverseWorldMatrix * joint2.matrix * joint2.worldMatrix * vertex

It gives me results which look a little bit like the deformed geometry, but only a little bit.

Anyone has a clue if I have to work with other attributes? There is for example a .worldMatrix attribute of the bindPose and also a xformMatrix. But there is too much attribute for trying them out and I prefer to understand the process properly.

I am grateful for every hint.
Quote:joint2.inverseWorldMatrix * joint2.matrix * joint2.worldMatrix * vertex


1. your matrix mults are the wrong way around.
2. you are doing:

CurrentInverseWorld * CurrentLocal * CurrentWorld * vertex

Which doesn't make any sense to me. To replicate the Maya way of doing things you'll be wanting to do:

// this will be a constant arrayWorld2BoneLocalMatrix == WsMeshMatrixInBindPose * InverseWorldJointBindPose;// grab skin TM for each boneSkinTM = World2BoneLocalMatrix * WorldJointMatrix * InverseMeshTM;// will be in local space relative to the meshes transformMPoint pdeformed = SkinTM * originalPoint;


Be aware that in Maya 2008 you can now have multiple bind poses. So things get a little trickier.....
The famous RobTheBloke, can't believe it. I read your MEL-Tutorials a while ago. Thanks for them. Back to the topic:

You introduced 1 new matrix i was not really aware of. What is the WsMeshMatrixInBindPose? Is it the worldMatrix of my bindPose?
If that is the case, I would get the bindPose using

dagPose -r -g -bp;
getAttr bindPose1.worldMatrix[1];

would give me WsMeshMatrixInBindPose then, right? Let's assume we only have one bindPose.

By InverseWorldJointBindPose you mean joint1.inverseWorldMatrix I assume? So
World2BoneLocalMatrix would be bindPose1.worldMatrix * joint1.inverseWorldMatrix which makes sense to me.

But what about the skin? What is InverseMeshTM? Can't be joint1.inverseWorldMatrix because then SkinTM would be the same as World2BoneLocalMatrix because joint1.worldMatrix * joint1.inverseWorldMatrix multiply to the identity matrix.

I am using MEL for exporting btw.
Quote:Original post by gentlesea
You introduced 1 new matrix i was not really aware of. What is the WsMeshMatrixInBindPose?


Your mesh data is in local space (unless you read the verts back with xform), but when it is bound, the mesh will have a transformation associated with it. You need to take into account the world space matrix of the mesh when it was bound.

Quote:Original post by gentlesea
Is it the worldMatrix of my bindPose?
If that is the case, I would get the bindPose using

dagPose -r -g -bp;
getAttr bindPose1.worldMatrix[1];


Just slap the character into the bind pose, then use the xform command to grab the world space matrix of the mesh that is being skinned.

Quote:Original post by gentlesea
Let's assert we only have one bindPose.


Fixed ;) Never assume anything when dealing with art assets (especially maya). A quick mel script to verify that data in the model is correct will be a huge confidence booster for your artists (I used to disable the save menu item and replaced it with a script that would prevent you saving the file if something was going to break the game data. Artists hated me for a week or two, but they soon got to know what they could and couldn't do).

Quote:Original post by gentlesea
By InverseWorldJointBindPose you mean joint1.inverseWorldMatrix I assume?


When the joint is in the bind pose, yes.

Quote:Original post by gentlesea
So World2BoneLocalMatrix would be bindPose1.worldMatrix * joint1.inverseWorldMatrix which makes sense to me.


yup

Quote:Original post by gentlesea
But what about the skin? What is InverseMeshTM?


skinnedMeshTransform.inverseWorldMatrix

If you wan't to obey maya's deformation system, then you really want to leave all mesh data in local space after the skinning deformation (rather than world space). InverseMeshTM therefore transforms the WS mesh data into the local space of it's transform (which might be the hip bone for example). This lets you do any order of any type of deformation, e.g:

Blend Shape -> Skin
Skin -> Blend Shape

(Blend shapes work in local space for example. It's a minor cost of a matrix mult for a much more generic system).

This also makes it much easier to instance the skinned data into various locations - you just have to apply a world space (hip) matrix to the mesh for each instance you want....

Quote:Original post by gentlesea
Can't be joint1.inverseWorldMatrix


skinnedMesh.inverseWorldMatrix, but this may be animated, therefore it may also be different to that of the bind pose (infact, almost always). Normally this transform will be extracted as one of your animated bones in your model.

Quote:Original post by gentlesea
I am using MEL for exporting btw.


The API is a lot easier for this kind of thing - infact it's probably the only thing in Maya that's easier in the API than it is in mel. I'd suggest using MDagPath's to extract the animation data, and manually decompose those into the relevent local space Quat's and vec3's. (Otherwise you'll have some very nasty problems with pivots, joint orients, rotation axes etc). After that, everything else is pretty trivial. (unless you start using scale anywhere in your character rig - which requires a bit of fiddling to bake out the results).
This morning I reread and rechecked everything we wrote again and most things I do correctly now. The result is not quite correct though.

I still don't understand what InverseMeshTM is. You said it is not bindPose1.worldInverseMatrix. You said:

Quote:Normally this transform will be extracted as one of your animated bones in your model.


I do not understand what is meant by that. I also have problems understanding "transform". I used to have to correct mesh using your formulas but it was undeformed.

	int numberOfJoints = m_listJointMatrices.count();	for (int i = 0; i < numberOfJoints; i++)	{		CMatrix World2BoneLocalMatrix = BindPoseWorldMatrix * m_listJointWorldInverseMatricesBindPose.at(i); // correct		CMatrix SkinTMCurrent = World2BoneLocalMatrix * m_listJointWorldMatrices.at(i) * m_listJointWorldInverseMatrices.at(i); // not correct


SkinTMCurrent is stored one after the other and passed to a CG-Shader which displays the stuff. On Monday I will discuss the thing again with me prof, would be good if I understood the missing matrix then.

Thank you.
Quote:Original post by gentlesea
I still don't understand what InverseMeshTM is. You said it is not bindPose1.worldInverseMatrix.


It is

theSkinnedMeshTransformNode.inverseWorldMatrix

If you do not know that this is, select the skinned mesh and type

print `ls -sl`;

this will provide you with a node THAT IS NOT THE MESH, but the transform node it is parented under. i.e. pSphere1 vs pSphereShape1. The former is the transform, the latter is the mesh.

this transform can be animated (it may also just be an identity matrix), however some people parent it underneath something else, for example the hips. As such, it is a transform node that you need to export along with the rest of your bones.

Quote:I also have problems understanding "transform".


I can't believe you have got this far exporting transform nodes without understanding that you are exporting transforms?!?!?! read this

Quote:
CMatrix SkinTMCurrent = World2BoneLocalMatrix * m_listJointWorldMatrices.at(i) * m_listJointWorldInverseMatrices.at(i); // not correct


why are you multiplying a matrix by it's inverse?

should be:

Quote:
CMatrix SkinTMCurrent = World2BoneLocalMatrix * m_listJointWorldMatrices.at(i) * InverseMeshTM;

Thank you for all your help. It is working now as expected. Here are fragments of the code, in case someone needs them:

void QTetraVolumeWindow::prepareMatrices()
{
m_pTetraGrid->BindPoseWorldMatrix.transpose();

int index = 0;

int numberOfJoints = m_pTetraGrid->m_listJointMatrices.count();
for (int i = 0; i < numberOfJoints; i++)
{
CMatrix tempJointWorldInverseMatricesBindPose = m_pTetraGrid->m_listJointWorldInverseMatricesBindPose.at(i);
tempJointWorldInverseMatricesBindPose.transpose();

CMatrix tempJointWorldMatrices = m_pTetraGrid->m_listJointWorldMatrices.at(i);
tempJointWorldMatrices.transpose();

CMatrix World2BoneLocalMatrix = m_pTetraGrid->BindPoseWorldMatrix * tempJointWorldInverseMatricesBindPose;
CMatrix SkinTMCurrent = World2BoneLocalMatrix * tempJointWorldMatrices;

CMatrix tempX = m_pTetraGrid->m_listJointRotationMatricesX.at(i);
CMatrix tempY = m_pTetraGrid->m_listJointRotationMatricesY.at(i);
CMatrix tempZ = m_pTetraGrid->m_listJointRotationMatricesZ.at(i);
tempX.transpose();
tempY.transpose();
tempZ.transpose();
SkinTMCurrent = SkinTMCurrent * tempX * tempY * tempZ;

[...]
}
}

Thanks and greetings to Oxford.

This topic is closed to new replies.

Advertisement