Search code examples
gltfskeletal-animation

Understand glTF jointMatrix


I read the Skins section in glTF's github tutorial, but failed to understand how jointMatrix works.

In the tutorial, it's defined as

jointMatrix(j) =
  globalTransformOfNodeThatTheMeshIsAttachedTo^-1 *
  globalTransformOfJointNode(j) *
  inverseBindMatrixForJoint(j);

I'm confused by the globalTransform and cancellations. In my mind, jointMatrix is something like

jointMatrix(j) =
  jointSpaceToModelSpace(j) *
  jointTransform(j) *
  modelSpaceToJointSpace(j);

And in vertex shader

// assume 'joint' is a matrix computed from weighted aggregation of all jointMatrix(j)
vertex_world_space = model-view-proj * joint * vertex_model_space;

My question is

  • Why jointMatrix is defined as such ?
  • What's wrong with my version ?

Solution

  • One thing to keep in mind is that the vertex shader is executing within the context of the skinned mesh node, not the skeleton root or joints. This means that the model view matrix is potentially in a "meaningless" place: This skinned node may have been given a transformation, but it won't be affected by that as all of its vertices are controlled by joints instead. The glTF Validator will even warn if a skinned node is NOT a root node, for this reason, as transforms applied to the skin itself are no-ops.

    So the steps needed for any particular vertex in the skin then are:

    1. Undo the skin's world-to-node transformations, if any.
    2. Apply the joint's world-to-joint transformations.
    3. Undo the "rest" position of the joint.

    These three steps correspond to the three you listed from the tutorial.

    Let's talk about the third one. Each joint has a "rest" position. For example an arm bone might be at rest in the left arm of the skin's original shape (bind pose), and a leg bone might be in the right leg for example. These bones don't sit at the origin, they are sprinkled all around the model. But we don't want them pulling vertices around when they are in the rest position, for example the arm bone might be above and to the left of the origin but shouldn't pull any vertices up and to the left when it's at rest.

    So each joint has an "inverse bind matrix" that undoes the effects that the joint would naturally have in its own rest position. When the joint moves, it moves relative to its own rest position, and that delta (the difference between steps 2 and 3) is what gets applied to the target vertex.

    If the skin itself tries to move, nothing happens. Such a transform is applied to u_modelViewMatrix at the end of the tutorial's shader, and the inverse of that transform is applied in step 1, so they cancel out. If you wanted to move the entire skinned mesh, of course the correct way to do that is by repositioning the whole skeleton, not the skinned node. Moving the root joint would apply new transformations in step 2 for all vertices in the mesh.