How to Package Multiple Animations in glTF

The glTF (GL Transmission Format) standard packages multiple concurrent animation clips by defining discrete animation objects within a single top-level array, allowing 3D engines to isolate, blend, and play them simultaneously. This article explains the underlying structure of glTF animations, how channels and samplers decouple movement data, and how runtimes process these isolated clips concurrently on the same 3D model.

The Top-Level Animation Array

In a glTF file, animations are organized inside a root-level array named animations. Rather than storing one long, continuous timeline containing every action, the format defines each distinct clip as an independent object inside this array.

{
  "animations": [
    { "name": "Walk", "channels": [...], "samplers": [...] },
    { "name": "Shoot", "channels": [...], "samplers": [...] }
  ]
}

Assigning a string to the name property identifies each clip (such as "Idle", "Run", or "Attack"). Because each entry is a self-contained JSON object, the glTF specification establishes strict data boundaries between different actions.

Data Isolation via Channels and Samplers

Each animation object isolates its keyframe tracks through two core components: samplers and channels.

  • Samplers: Define the animation data curves. A sampler references an input accessor (the timeline timestamps in seconds), an output accessor (the transformation values, such as coordinates or quaternions), and an interpolation method (such as LINEAR, STEP, or CUBICSPLINE).
  • Channels: Connect a sampler directly to a specific target. A channel specifies a node index (a bone or mesh in the scene hierarchy) and a path property indicating what is being modified: "translation", "rotation", "scale", or "weights" (for morph targets).

By scoping samplers and channels strictly inside their parent animation object, glTF ensures that the keyframes of one clip do not interfere with or overwrite the keyframes of another inside the file asset.

Sharing and Isolating Buffer Data

Although animation clips are logically distinct, the binary keyframe data stored in the underlying .bin buffers can be structured in two ways:

  1. Dedicated Buffers: Each animation clip points to unique bufferViews and accessors, isolating the raw binary data completely.
  2. Shared Time Accessors: Multiple clips can reference identical timeline accessors (for instance, if two clips share an identical frame duration and rate), reducing file size while keeping channel mappings logically separate.

Concurrent Playback in Runtimes

glTF specifies the data format, while the client runtime (such as Three.js, Babylon.js, Unity, or Unreal Engine) handles concurrency. When a runtime loads a glTF file with multiple clips, it exposes them as distinct animation actions.

Runtimes achieve concurrency through two primary methods:

  • Target Masking / Layering: One animation can control the lower body (nodes assigned to the legs in a "Walk" clip), while another controls the upper body (nodes assigned to the spine and arms in a "Shoot" clip). Because the channels target distinct node indices, the clips evaluate concurrently without conflicts.
  • Transform Blending: When two concurrent animations target the same node (such as blending a "Walk" clip into a "Run" clip), the runtime evaluates both channels at their current timestamps and calculates an interpolated mathematical blend (slerp for rotations, lerp for translations) based on weight parameters defined by the engine.