glTF Multiple Buffers in One JSON Descriptor

The glTF (GL Transmission Format) standard uses a flexible, modular architecture that allows a single JSON descriptor file (.gltf) to link to multiple separate binary buffer files (.bin). Instead of embedding all binary payloads into a monolithic container, the JSON file defines each external file as an entry in a top-level array and maps sub-regions of these files to geometric attributes, animations, and skinning data. This structural separation enables efficient asset streaming, level-of-detail management, and modular 3D pipeline workflows.

The Core Hierarchy: Buffers, BufferViews, and Accessors

glTF connects distinct binary files to 3D scene elements using a three-tier indexing system:

  1. buffers: Declares the raw external binary files.
  2. bufferViews: Defines specific slices or windows of data within a given buffer.
  3. accessors: Interprets the raw bytes of a bufferView as structured data types (such as VEC3 floating-point coordinates or SCALAR indices).

Defining Multiple Files in the buffers Array

At the root of the JSON file, the buffers property contains an array of objects. Each object represents an individual binary resource and requires two main properties:

  • uri: The relative file path, external URL, or base64 data URI pointing to the binary file.
  • byteLength: The total size of that binary file in bytes.
{
  "buffers": [
    {
      "uri": "geometry.bin",
      "byteLength": 24000
    },
    {
      "uri": "animations.bin",
      "byteLength": 8192
    }
  ]
}

In this setup, indices reference each buffer based on its position in the array (geometry.bin is index 0, and animations.bin is index 1).

Slicing Files with bufferViews

The bufferViews array segments the data inside the defined buffers. Each bufferView references a specific buffer by its array index using the buffer key:

{
  "bufferViews": [
    {
      "buffer": 0,
      "byteOffset": 0,
      "byteLength": 12000,
      "target": 34962
    },
    {
      "buffer": 0,
      "byteOffset": 12000,
      "byteLength": 12000,
      "target": 34962
    },
    {
      "buffer": 1,
      "byteOffset": 0,
      "byteLength": 8192
    }
  ]
}
  • buffer: The index of the target buffer in the buffers array.
  • byteOffset: The starting position (in bytes) within that specific file (defaults to 0).
  • byteLength: The length of the slice (in bytes).
  • target: An optional OpenGL buffer target (e.g., 34962 for ARRAY_BUFFER, 34963 for ELEMENT_ARRAY_BUFFER).

Because bufferViews explicitly declare which buffer index they target, bufferView 0 and 1 read exclusively from geometry.bin, while bufferView 2 reads from animations.bin.

Reading Data Through accessors

Higher-level glTF components—such as mesh primitives or animation channels—do not read buffers directly. Instead, they reference accessors, which point to the relevant bufferView.

{
  "accessors": [
    {
      "bufferView": 0,
      "byteOffset": 0,
      "componentType": 5126,
      "count": 1000,
      "type": "VEC3",
      "max": [1.0, 1.0, 1.0],
      "min": [-1.0, -1.0, -1.0]
    },
    {
      "bufferView": 2,
      "byteOffset": 0,
      "componentType": 5126,
      "count": 64,
      "type": "SCALAR"
    }
  ]
}
  • The first accessor reads vertex positions from bufferView 0 (located in geometry.bin).
  • The second accessor reads animation keyframe timestamps from bufferView 2 (located in animations.bin).

Why Use Multiple Buffers?

Structuring a glTF asset with multiple binary buffers offers several technical benefits:

  • Progressive Loading: Geometry can be loaded and rendered immediately, while animation or morph target buffers are fetched asynchronously in the background.
  • Asset Sharing and Reuse: Multiple distinct models can share the same animation or skinning buffer file, reducing overall download sizes in web and game engines.
  • Independent Updates: Static geometry and dynamic physics or animation sets can be updated or versioned independently without repacking the entire asset.