Sharing Bone Rigs Across Characters in glTF

Reusing bone rigs across multiple animated characters in a single glTF file is an effective way to optimize 3D asset workflows and minimize file size. This guide outlines how the glTF 2.0 specification handles skeletal sharing, explaining how multiple meshes bind to a unified joint hierarchy, how data accessors are reused, and how animations interact with shared skeletal nodes.

Understanding the glTF Skinning Architecture

In the glTF specification, skeletal animation relies on three primary components:

  1. Nodes: Represent the transformation hierarchy of the scene, including individual skeleton joints.
  2. Skins: Define collections of joint nodes, an optional skeleton root node, and an accessor pointing to the inverseBindMatrices.
  3. Meshes: Contain primitive geometry with vertex attributes for joint indices (JOINTS_0) and bone weights (WEIGHTS_0).

Because a mesh node explicitly declares its associated skin via a skin property index, multiple nodes can point to the same skin definition or share underlying data buffers.

Method 1: Binding Multiple Meshes to a Single Rig

If the objective is to display multiple characters or modular parts (such as clothing, armor, or swappable body parts) moving in perfect synchronization, multiple mesh nodes can reference the exact same skin instance.

In the glTF JSON structure:

  • Define the joint hierarchy once under the nodes array.
  • Create a single entry in the skins array containing the list of joint node indices.
  • Create multiple character nodes, assign each character its distinct mesh index, and set the skin property on all of them to the same skin index:
"nodes": [
  { "name": "Character_A", "mesh": 0, "skin": 0 },
  { "name": "Character_B", "mesh": 1, "skin": 0 }
]

Every mesh referencing skin: 0 will evaluate its vertex positions against the transforms of that single joint hierarchy. Transforming the bones will deform all attached characters simultaneously.

Method 2: Independent Characters Reusing Rig Data

Because glTF animation channels target specific node indices directly, two independently moving characters cannot share the exact same joint nodes inside a single scene. If character instances must move independently within the same glTF file, each requires its own set of joint nodes.

However, you can still share the structural data across characters to save memory:

  • Shared Inverse Bind Matrices: Multiple skin objects can reference the same accessor index for their inverseBindMatrices. As long as the rest poses and bone counts are identical, this large matrix array does not need duplication.
  • Shared Keyframe Samplers: If characters perform identical animations at different times or offsets, animation.channels can target the separate joint nodes while their sampler properties point to the same input (timestamps) and output (TRS values) accessors.
"skins": [
  {
    "inverseBindMatrices": 4,
    "joints": [10, 11, 12]
  },
  {
    "inverseBindMatrices": 4,
    "joints": [20, 21, 22]
  }
]

Best Practices for Implementation

  • Standardize Joint Indices: Ensure that all shared meshes use the exact same bone index mapping in their JOINTS_0 vertex attributes so they align with the shared joints array order.
  • Root Node Offsets: When binding several full characters to one rig for synchronized display, use local vertex offsets in the mesh primitives rather than node transforms, as skinning overrides normal node-level translation.
  • Runtime Retargeting: For game engines (such as Three.js, Babylon.js, or Godot), the most efficient setup is often exporting the rig and animation once, exporting character meshes separately, and cloning or retargeting the skeleton dynamically at runtime.