How glTF Skin References the Skeletal Hierarchy

In glTF (GL Transmission Format), skeletal animation relies on the skin object to bridge renderable mesh geometry with hierarchical transformation trees. Rather than introducing a specialized data type for bones, glTF repurposes standard scene graph nodes to act as joints. The skin property references these nodes through indexed arrays, maps initial rest poses using inverse bind matrices, and establishes the relationship between vertex weighting data and dynamic skeletal transforms.

Joints as Standard Nodes

In glTF, joints do not exist as distinct structural entities; they are ordinary elements in the top-level nodes array. The hierarchical relationship—where parent bones control child bones—is created strictly using the children arrays of those nodes. A joint's position, rotation, and scale in the scene graph define its local transform relative to its parent joint.

The skin.joints Array

The core mechanism linking a skeleton to a skin is the joints array within the skin object. This array contains integer indices that point directly to entries in the root-level nodes array:

{
  "skins": [
    {
      "name": "ArmatureSkin",
      "inverseBindMatrices": 0,
      "joints": [10, 11, 12, 13]
    }
  ]
}

In this example, nodes 10, 11, 12, and 13 function as joints for this specific skin. The position of a node index within the joints array dictates its local "joint index" (e.g., node 10 is joint 0, node 11 is joint 1).

This local joint index is critical because mesh vertex attributes (JOINTS_0, JOINTS_1) do not reference global node indices; instead, they reference the index within this skin.joints list.

The inverseBindMatrices Property

To correctly deform a mesh, the engine must know the mesh's position relative to each joint in the neutral "bind pose." The skin.inverseBindMatrices property points to an accessor containing an array of 4x4 floating-point matrices.

  • There is a strict 1:1 mapping: the number of matrices matches the length of the joints array.
  • Matrix i transforms vertex coordinates from the mesh's local space into the local coordinate space of joint i at the time of binding.
  • During runtime animation, each vertex is transformed by its inverse bind matrix, followed by the joint node's current global transform, scaled by the vertex weight.

The skeleton Property

The skin object includes an optional skeleton property, which contains a single integer referencing the root node of the joint hierarchy:

{
  "skins": [
    {
      "skeleton": 9,
      "joints": [10, 11, 12, 13]
    }
  ]
}

The node specified by skeleton is often the direct parent of the root joint (or the root joint itself). While not required for mathematically calculating the joint transforms, it serves as a hint to runtime engines about where the skeletal hierarchy starts, optimizing scene graph traversals and bounding box calculations.

Connecting the Mesh to the Skin

A visual node connects geometry to the skeletal system by defining both a mesh index and a skin index simultaneously:

{
  "nodes": [
    {
      "name": "SkinnedCharacter",
      "mesh": 0,
      "skin": 0
    }
  ]
}

When this node is evaluated, the renderer takes the geometry from mesh: 0, reads the skinning attributes (JOINTS_0 and WEIGHTS_0) from each vertex primitive, and evaluates them against the runtime transforms of the nodes declared in skin: 0.