glTF Interleaved vs Non-Interleaved Vertex Buffers

The glTF 2.0 specification natively supports both interleaved and non-interleaved vertex buffer layouts through its hierarchical data storage architecture consisting of buffers, buffer views, and accessors. In glTF, the choice between structuring vertex attributes separately or packaging them into a single interleaved array is managed by setting the byteStride property in a bufferView and adjusting the byteOffset inside corresponding accessors. This design allows graphics pipelines to optimize for cache locality, memory bandwidth, or dynamic data updates without altering the underlying mesh definition.

The glTF Data Architecture

To understand how glTF formats vertex layouts, it is necessary to examine three core entities in the glTF JSON structure:

  • buffers: Raw binary blobs containing geometry data.
  • bufferViews: Slices of a buffer defining a byte offset, a byte length, and an optional byteStride.
  • accessors: Definitions that describe how to read typed data (e.g., VEC3 of FLOAT) from a bufferView, defining component types, counts, and sub-offsets (byteOffset).

The handling of interleaved versus non-interleaved configurations depends directly on how bufferViews and accessors interact.

Non-Interleaved (Separate) Vertex Buffers

In a non-interleaved (or planar) layout, each vertex attribute—such as position, normal, and texture coordinate—is stored in its own distinct, contiguous memory block.

  • Structure: Vertex positions are grouped together (P0, P1, P2...), followed by all normals (N0, N1, N2...), and so forth.
  • glTF Implementation: Each attribute typically points to its own distinct bufferView (or non-overlapping regions within the same bufferView). Because the data is tightly packed, the bufferView either omits the byteStride property or sets it to the size of a single element (e.g., 12 bytes for a VEC3 of 32-bit floats). Each accessor simply references the start of its respective attribute array.

Use Case: Non-interleaved layouts are ideal when certain vertex attributes are updated frequently (such as software skinning or morph targets affecting only positions) or when rendering passes only require a subset of attributes (such as a depth-only shadow pass that only needs positions).

Interleaved Vertex Buffers

In an interleaved layout, all attributes for a single vertex are stored together sequentially in memory (P0, N0, UV0, P1, N1, UV1...).

  • Structure: A single array of structures where each element represents the complete set of attributes for a single vertex.
  • glTF Implementation:
    1. A single bufferView encompasses the entire interleaved array.
    2. The bufferView defines a byteStride property, which represents the total size in bytes of one complete vertex (e.g., 12 bytes for position + 12 bytes for normal + 8 bytes for UV = 32-byte stride). According to the glTF specification, byteStride must be a multiple of 4 and between 4 and 252 bytes.
    3. Multiple accessors reference this identical bufferView. Each accessor uses its own byteOffset to point to the start of its specific attribute within the first vertex (e.g., position at offset 0, normal at offset 12, UV at offset 24).

Use Case: Interleaved layouts maximize GPU cache efficiency during standard rendering passes where all vertex attributes are consumed simultaneously by the vertex shader.

Key Rules and Restrictions in the Specification

  • When byteStride is defined on a bufferView, it applies to all accessors referencing that view.
  • An accessor.byteOffset within an interleaved bufferView must be aligned to the component type size (e.g., an attribute using FLOAT components must have an offset that is a multiple of 4).
  • glTF allows a hybrid approach within the same model: static attributes (like normals and UVs) can be interleaved in one bufferView, while dynamically modified attributes (like positions) remain non-interleaved in another.