Constructing the transformation matrix of a corrective shape

I started getting involved with vector and matrix math and wanted to understand the math involved in creating a delta shape for a corrective pose.
I think I understand the problem and what needs to be done. I just can’t seem to get it right.
For simplicity, I skinned up a plane to symbiolize an arm with an edge at the elbow. I’m only concerned with one vertex at the elbow for the moment. This vertex is bound to the lower arm with a value of 1. Only the elbow joint is rotated.

I’m not sure I have it all down, but my reasoning is that I need to get that joint’s transformation matrix and the vertex local position. Then also the weight value. I’ve made it easy since I have it bound only to one joint for now. But the actual math still eludes me. If someone could point me in the right direction, I’d be very grateful.

The idea here is that I want to get the matrix to transform the locator from the first position to the one where the arm is posed. Then I’m thinking I should get the inverse of that and move all the vertices back in bind pose for the corrective shape that has been made.


maya file

Here’s one way to think of how regular old smooth skinning works (Let’s do it on vertices without matrices first)

Say you’ve got 2 joints, A and B
First store the matrix of where each joint starts out. (That’s setting the bind pose)

Now say we’ve got some animation on our joints.
And let’s pick a random point that’s weighted 70% to A and 30% to B.

Treat that vertex as a child of A, and calculate where it would go.
Treat that vertex as a child of B, and calculate where it would go.
Now just interpolate between those calculated vertices, 70% of A and 30% of B.


“Treating a vertex as a child of the joint” is just multiplying the inverse of the bind matrix of that joint, by the current matrix of that joint and the original position of the vertex. (I can never remember which order. Shouldn’t be hard to work out)


Now, to get the matrix for that vertex in the corrective shape, you follow LITERALLY the same steps. Instead of multiplying in a vertex, you just multiply in a matrix that’s positioned at that vertex.
And where you would do the weighted average of vertices, you just do the naiive weighted average of matrices.

1 Like

Thank you. I haven’t fully understood. I think what trips me up is the hierarchy of joint chains. But I suppose that shouldn’t be a cocern. When creating these corrective shapes you are only working with one joint’s orientation so hiearchy shouldn’t matter. Maybe I’m confusing myself here. To clarify, I believe that if I would rotate joint1 and then joint2 I have to multiply these 2 transformation matrices to move a point that is supposed to follow joint2.

For a refresher in matrix transformations, I created a plane and placed a sphere at one of its corner. I then rotate the plane and create a transformation matrix.
I save out my sphere’s initial position which I then use to multiply my transformation matrix with. It works fine for rotation and scale, but when I translate the plane it has no effect on the sphere’s translation. What am I missing here? I feel silly.

################################################################
plane1 = pm.PyNode('pPlane1')
sphere = pm.PyNode('pSphere1')
#sphere_bind_pos = dt.Vector(pm.xform(sphere, query=True, ws=True, rp=True)) # run once

################################################################
# plane matrix
plane_transform_matrix = dt.Matrix(pm.xform(plane1, query=True, ws=True, matrix=True))
print(plane_transform_matrix)

# sphere transformation
new_pos = sphere_bind_pos*plane_transform_matrix
print(new_pos)
pm.xform(sphere, ws=1, t=new_pos)

# set sphere's translation values to that of the plane
planeTrans = plane.getAttr('translate')
sphere.setAttr('translate', sphere.getAttr('translate')+planeTrans)

Well. I feel kinda silly. I should understand this better.

I was under the impression that I could take a vector which is my position and multiply that with the matrix to get the new position of the sphere. I see how that’s not going to work.

Here is a revised version:

plane1 = pm.PyNode('pPlane1')
sphere = pm.PyNode('pSphere1')
#sphere_bind_transform_matrix = dt.Matrix(pm.xform(sphere, query=True, ws=True, matrix=True))

################################################################
# plane matrix
plane_transform_matrix = dt.Matrix(pm.xform(plane1, query=True, ws=True, matrix=True))
print(plane_transform_matrix)

# sphere transformation
new_matrix = sphere_bind_transform_matrix*plane_transform_matrix
sphere.setMatrix(new_matrix)

Follow along with this.

Take 2 objects in worldspace and arbitrarily translate them (don’t worry about rotations and scales for now). Now make one of those objects the child of the other. The .t values of the child object changed, right? How much did they change? They changed by subtracting the .t values of the parent object.
But there’s really no such thing as “subtracting” in normal matrix math. But you can invert a matrix. So what that means is you multiply by the inverse of the parent object to get those values.

That’s step 1

Now translate the parent object around. The .t values of the child don’t change, but the object moves (that’s just a description of what parenting does). The new worldspace position of the object is now just the sum of the .t values of the parent and the .t values of the child.
But there’s really no such thing as “addition” in normal matrix math. So what that means is you’re just multiplying the two local matrices.

That’s step 2


So in total, to “treat one object as the child of another”, starting with the worldspace matrices of both objects, and the “worldspace bind matrix” of the parent
You would multiply the child by the INVERSE of the worldspace bind matrix. Then multiply that by the current worldspace matrix of the parent.

3 Likes

Maya takes care of all of that for you! For this use-case, as long as you’re getting the initial matrices with ws=True, you don’t have to worry about that at all! :slight_smile:

1 Like

Thank you. It was a while ago I worked with matrices. And I did it by creating lists(matrices) myself and by creating functions of my own to calculate the dot product and then multiplying the matrices together in appropriate order for the cause of learning how vectors are transposed. Like you would on pen and paper.
But now I wanted to use the vector and matrix classes of PyMEL and because it was so long ago I completely forgot that I couldn’t multiply a vector of 3 element with a 4x4 matrix…
When adjusting for that, everything works just as expected.

So just to reiterate. When we skin a mesh to a skeleton. We are storing the position of the vertex and also storing the matrix for the joint as is the bind pose. With the joint later rotated, we query its present matrix and use this together with the vector and bind matrix that were stored, to transpose this vertex to its new position.

That is correct. Now you can move on to inverting corrective shapes :slight_smile:

1 Like

Ok, Got it!
Thanks a ton for your help. It works perfectly. Really interesting.
Now I just need to incorporate the weighting.