glTF BufferView Byte Offsets and Stride Explained

In a glTF asset, the bufferView component bridges raw binary blobs and high-level 3D model data by carving a contiguous chunk out of a buffer. It establishes structural boundaries using properties such as byteOffset to designate the starting point of data within a file, and byteStride to dictate the byte distance between consecutive vertex attributes. Together, these properties tell rendering engines exactly where to read and how to unpack geometry and animation streams in memory.

The Role of bufferView in glTF Memory Architecture

A standard glTF file organizes binary data in three tiers:

  1. buffer: The raw, unformatted binary array (often an external .bin file or base64-encoded URI).
  2. bufferView: A defined window or segment of a buffer that specifies layout, intended target use (such as an array buffer or element array buffer), and optional byte spacing.
  3. accessor: The typed interpretation layer (e.g., VEC3 of FLOAT) that points into a bufferView.

How byteOffset Establishes Data Starting Points

The byteOffset property inside a bufferView defines the zero-indexed byte index where the slice begins within the parent buffer.

  • Default Value: If omitted, byteOffset defaults to 0.
  • Alignment Requirements: The offset must align to multiples of the component types stored within it. For standard WebGL/OpenGL compatibility, glTF specifications recommend aligning offsets to 4-byte boundaries.
  • Offset Stacking: An individual accessor also has an optional byteOffset. The true memory location of a given attribute is calculated by summing both offsets: totalOffset = bufferView.byteOffset + accessor.byteOffset.

This separation allows one bufferView to represent a collection of attributes (such as all data for a single mesh) while individual accessors pinpoint where specific components (like positions or normals) reside within that view.

How byteStride Governs Interleaved Data

The byteStride property sets the byte distance from the start of one attribute element to the start of the next. It primarily determines whether vertex data is stored sequentially or interleaved.

  • Tightly Packed Data (Omitted byteStride): When byteStride is omitted or left undefined, data elements are assumed to be contiguous. For example, if an accessor reads an array of 12-byte VEC3 floats, each new vector immediately follows the previous one.
  • Interleaved Data (Explicit byteStride): When multiple vertex attributes share the same bufferView in an interleaved format (e.g., [Position, Normal, UV, Position, Normal, UV]), byteStride specifies the full byte size of one complete vertex record.
  • Constraints:
    • byteStride must be a multiple of 4.
    • It must fall within the range of 4 to 252 bytes.
    • It can only be defined when the bufferView is referenced by vertex attribute accessors; it is strictly prohibited for index data (ELEMENT_ARRAY_BUFFER) or animation keyframe data.

Practical Layout Example

Consider a vertex definition containing a 3D position (VEC3 floats = 12 bytes) and a 2D texture coordinate (VEC2 floats = 8 bytes) interleaved per vertex:

  • A single vertex takes up 20 bytes total.
  • The bufferView defines byteStride: 20 and starts at byteOffset: 0.
  • The Position accessor points to this bufferView with byteOffset: 0.
  • The Texture Coordinate accessor points to the same bufferView with byteOffset: 12.

During rendering, the graphics pipeline jumps 20 bytes forward for every vertex, reading 12 bytes for position starting at offset 0, and 8 bytes for UV coordinates starting at offset 12, successfully reconstructing the complete mesh geometry from a single continuous stream.