glTF Skeletal Nodes and Inverse Bind Matrices
In glTF 2.0 asset pipelines, character rigging and vertex skinning depend on the mathematical coordination between the node hierarchy and skin definitions. This article explains how glTF skeletal nodes (joints) and inverse bind pose matrices function together to transform vertex positions from static model space into dynamic, animated poses.
The Role of Skeletal Nodes
In a glTF file, a skeleton is represented as a hierarchy of standard
glTF nodes referenced in a skin.joints array. Each node
represents a single joint or bone. These nodes possess their own local
transformations—translation, rotation, and scale (TRS)—which are updated
per frame during an animation. By traversing the node hierarchy from the
root, a runtime engine computes each joint's current global model-space
transform, typically represented as \(M_{\text{current}}\).
The Role of Inverse Bind Matrices
The original vertex positions of a skinned mesh are authored relative to the mesh’s local bind space (the bind pose). When the skeleton moves, vertices cannot simply be multiplied by \(M_{\text{current}}\), because that transform represents movement relative to the scene or skeleton root, rather than relative to the joint itself.
To resolve this, glTF skins define an
inverseBindMatrices accessor. This accessor provides a 4x4
matrix for every joint in the joints array. Each inverse
bind matrix (\(IBM\)) represents the
inverse of that joint's global transform at the exact moment the mesh
was bound to the skeleton:
\[IBM = (M_{\text{bind}})^{-1}\]
Multiplying a vertex position by a joint's \(IBM\) translates that vertex from mesh bind space into the local coordinate space of the joint.
The Mathematical Relationship
The relationship between a skeletal node and its corresponding inverse bind matrix forms the core of the vertex skinning equation. To calculate how a joint affects a vertex at any given frame, the engine combines the current joint node transform with the stored inverse bind matrix to create a joint matrix (\(J\)):
\[J_i = M_{\text{current, } i} \times IBM_i\]
During vertex shading, this combined matrix operates in two sequential phases:
- \(IBM_i\) transforms the vertex: It moves the vertex from the initial mesh bind pose into joint \(i\)'s local reference frame, measuring where the vertex sits relative to that bone.
- \(M_{\text{current, } i}\) transforms the vertex: It moves the vertex from the joint's local frame back into the animated model space based on the node's current animation transform.
In Linear Blend Skinning (LBS), each vertex calculates its final animated position by summing the contributions of its assigned joints, scaled by vertex weights:
\[v' = \sum_{k} (w_k \cdot J_k) \cdot v\]
Key Implementation Details
- Array Alignment: The
inverseBindMatricesbuffer must strictly match the order and length of theskin.jointsarray. The matrix at index \(n\) corresponds directly to the node indexed atjoints[n]. - Identity Fallback: If the
inverseBindMatricesproperty is omitted in a glTF skin, the runtime must assume each joint's inverse bind matrix is an identity matrix, implying that all joints were located at the origin during the bind pose.