How glTF Serializes Bone Hierarchies with Node Indices

This article provides an overview of how the glTF (GL Transmission Format) 2.0 specification serializes skeletal bone hierarchies using zero-based node indices. It details the structural relationship between the global node graph and skeletal skins, explains how parent-child bone relationships are encoded, clarifies the difference between global node indices and local joint indices, and outlines how inverse bind matrices and mesh vertex attributes interact to compute final skinned vertex positions.

The Role of Nodes as Bones

In glTF, there is no distinct "bone" data type. Instead, bones are represented as standard scene graph elements within the top-level nodes array. Each entry in the nodes array is identified implicitly by its zero-based index (e.g., node 0, node 1, node 2).

A node acting as a bone contains:

  • Transform properties: translation, rotation, and scale (or a single matrix), defining its local transform relative to its parent.
  • A children array: A list of integer indices referencing other nodes in the global nodes array that act as direct child bones.

Because children are stored as indices, the skeletal hierarchy is formed as a directed acyclic graph directly inside the global nodes array.

The skin Object Definition

To bind a skeletal hierarchy to a deformable mesh, glTF uses a skins array. A skin object aggregates the relevant nodes into a cohesive skeleton through the following properties:

  • joints: An array of integer indices referencing nodes in the top-level nodes array. Every node acting as a bone in that particular skeleton must be listed here.
  • skeleton (optional): An integer index pointing to the root node of the skeletal hierarchy. If omitted, the root is determined by finding the topmost joint in the hierarchy that is not a child of another joint.
  • inverseBindMatrices (optional): An accessor index pointing to a buffer of \(4 \times 4\) floating-point matrices. Each matrix corresponds directly to a joint in the joints array at the matching index.

Local Joint Indices vs. Global Node Indices

A crucial distinction in glTF skinning is the difference between global node indices and local joint indices:

  1. Global Node Indices: The actual index of an element within the root-level nodes array. The bone hierarchy (parent-to-child links via children) is defined strictly using global node indices.
  2. Local Joint Indices: The index of a joint within a specific skin.joints array.

When a mesh uses skinning, its geometry defines vertex attributes such as JOINTS_0 and WEIGHTS_0:

  • WEIGHTS_0 contains floating-point weights indicating how much each joint influences a vertex.
  • JOINTS_0 contains integer values that index into the skin.joints array, not the global nodes array.

For example, if a vertex references joint index 0 via JOINTS_0, the runtime looks at skin.joints[0] to retrieve the actual node index in the scene graph.

Hierarchy Traversal and Skinning Resolution

To evaluate a skeletal hierarchy during runtime, an engine processes the serialization as follows:

  1. Evaluate Local Transforms: Read each bone node's translation, rotation, and scale to construct local transformation matrices.
  2. Compute Global Transforms: Traverse the hierarchy from the root node (using the indices defined in the children arrays) to calculate each bone's current global model-space matrix.
  3. Skin Matrix Calculation: For each joint \(i\) in skin.joints: \[\text{JointMatrix}_i = \text{GlobalTransform}(\text{joints}[i]) \times \text{InverseBindMatrix}_i\]
  4. Vertex Deformation: During rendering, each vertex is deformed by summing the products of its skinning weights and the corresponding joint matrices resolved from JOINTS_0.