Skinning

The process of attaching the vertices of a 3D mesh to a posed skeleton is known as "Skinning".

Each vertex can be bound to one or more joints.

The vertex's positions is a weighted average of the positions it would have assumed if it had been bound to each joint independently.

At each vertex, you ned to have the following information:

  • The index or indices of the Joints to which it is bound,
  • The weighting factor describing how much influence that joint should have on the final vertex position.

#![allow(unused)]
fn main() {
struct SkinnedVertex {
    position: Vector3,
    normal: Vector3,
    texture_coordinates: Vector2,
    joint_indices: [u8; 4], // max of 4 joints
    joint_weight: [u8; 3], // you don't need 4 weights since the last can be calculated on the fly.
}
}

Tracking mesh vertices to joints

A skinning matrix is a matrix that can transform the vertices of a mesh from bind pose into new positions that correspond to the current pose of the skeleton.

The position of a skinned vertex is specified in model space, like all mesh vertices.

Unlike other matrix transforms, a skinning matrix is NOT a change of basis transform: it goes from model space (bind pose) to model space (actual pose)

Given the bind pose of the joint \(j\) in model space, \(B_{j \rightarrow M}\), this matrix transforms a point whose coordinates expressed in \(j\)'s space into an equivalent set of model-space coordinates.

Then, given a vertex whose coordinates are expressed in model-space, if you want to express it in joint-space, you can do so with the inverse of the above bind pose matrix.

\[ \vec{v_j} = \vec{v_M^B} B_{M \rightarrow j} = \vec{v_M^B} \left( B_{j \rightarrow M} \right)^{-1} \]

Given a joint's current pose, similar to the bind pose matrix, it defines vertices in joint space in model space, i.e., \(C_{j \rightarrow M}\).

\[ \begin{align} \vec{v_M^C} &= v_j C_{j \rightarrow M} \\ &= \vec{v_M^B} \left( B_{j \rightarrow M} > \right)^{-1} C_{j \rightarrow M} \\ &= \vec{v_M^B} K_j \end{align} \]

Where

  • \(K_j = \vec{v_M^B} \left( B_{j \rightarrow M} > \right)^{-1} C_{j \rightarrow M} \)
  • This \(K_j\) is known as the skinning matrix.

When you have multiple joints, it can be useful to create an array of skinning matrices \(K_j\) for every joint.

\(B_{j \rightarrow M}\) matrices never change so can be calculated once per vertex. The current poses \(C_{j \rightarrow M}\) change on each frame and will therefore need to be calculated on the fly.

Don't forget that a single matrix from joint space to model space represents a global pose, meaning for a given joint, you have to walk up its local pose matrices to the root joint to create either \(B_{j \rightarrow M}\) or \(C_{j \rightarrow M}\).

When multiple joints per vertex are involved

Calculate a new vector for each joint individually, then average all the vectors together.

\[ \vec{v_M^C} = \sum_{i=0}^{N-1} w_i \vec{v_M^B} K_{ji} \]

Where

  • \(N\) is the number of joints associated with vertex \(\vec{v}\).
  • \(w_i\) is the weighting factor of joint \(i\).
  • \(K_{ji}\) is the skinning matrix for joint \(j_i\).