Progressive Geometry Streaming Using Chunked glTF

Progressive geometry streaming with chunked glTF binary data allows web applications to render low-detail 3D meshes almost instantly while progressively downloading and refining geometry in the background. This article explains how to partition glTF binary payloads into discrete chunks, transport them over standard network protocols, and dynamically assemble and update GPU buffers client-side to achieve smooth, non-blocking rendering of complex 3D assets.

Understanding the glTF Binary Architecture

Standard glTF assets (.gltf with an external .bin or unified .glb files) describe scene hierarchies, materials, and mesh topologies using JSON, while heavy geometric data—such as vertex positions, normals, and indices—resides in raw binary bufferViews. By default, client runtimes wait for the entire binary buffer to download before initializing the mesh. Progressive streaming modifies this paradigm by decoupling mesh resolution from a single contiguous payload, breaking vertex and index data into ordered, sequential segments.

Geometry Preprocessing and Decomposition

To stream geometry progressively, the base asset must be preprocessed into hierarchical stages:

  1. Base Coarse Mesh: An initial, highly decimated version of the model is generated (e.g., via quadric error metrics). This mesh must contain the minimal topology required to establish bounding volumes and visual presence.
  2. Refinement Deltas or Discrete Chunks: The remaining geometric detail is saved as progressive increments. This can be accomplished through discrete Level of Detail (LOD) chunks or continuous level-of-detail techniques such as edge-collapse splits and vertex position deltas.
  3. Index Sorting: Indices must be ordered spatially or hierarchically so that newly streamed chunks only append to or partially update existing GPU index arrays without requiring a full recalculation of the topology.

Structuring glTF for Chunked Buffers

To represent chunked data within the glTF specification, you can leverage custom extensions or exploit multi-buffer architecture:

  • Multiple External Buffers: The JSON structure defines multiple buffers and corresponding bufferViews. The first buffer points to base_lod.bin, while subsequent buffers point to chunk_01.bin, chunk_02.bin, and so on.
  • Single Fragmented Buffer with HTTP Range Requests: A single .bin file is organized linearly with byte offsets defined in the JSON manifest. The client can request specific byte ranges for each progressive tier without modifying standard glTF indexing logic.
  • Compression Layers: Applying compression tools like Draco or Meshoptimizer to individual chunks rather than the entire file allows each chunk to be decompressed independently upon arrival.

Network Transport and Streaming Pipeline

The client runtime orchestrates the download using the modern Fetch API and Streams API:

  1. Manifest Retrieval: The client downloads and parses the initial glTF JSON metadata.
  2. Base Payload Fetch: The application requests the base geometry buffer first. Once fetched, the mesh is immediately mounted to the scene with placeholder materials or low-resolution textures.
  3. Sequential or Priority Streaming: Subsequent chunks are requested asynchronously. Using the ReadableStream interface or HTTP Range requests (Range: bytes=start-end), chunks are prioritized based on camera distance, screen-space error (SSE), or user viewport orientation.

Client-Side Reconstruction and GPU Updates

Once a chunk arrives at the browser, it must be integrated into the rendering pipeline without causing frame drops:

  • Dynamic Buffer Allocation: During initialization, allocate GPU buffers (WebGLBuffer or GPUBuffer in WebGPU) large enough to accommodate the full-resolution mesh, or reallocate dynamically using growth factors.
  • Sub-buffer Uploads: For WebGL, use gl.bufferSubData() to update specific byte offsets of the active vertex and index buffers with the newly decoded chunk data. In WebGPU, use queue.writeBuffer().
  • Updating Accessors: The runtime updates the draw call parameters (such as the vertex count in gl.drawElements() or gl.drawArrays()) to immediately render the higher-density topology on the next frame.
  • Web Workers for Decompression: Perform network fetching, payload decompression, and vertex delta applications inside dedicated Web Workers. This ensures that the main rendering thread remains locked to 60 or 120 frames per second during intense geometry streaming.