Foundations

What Is a GLB File?

A practical guide to the binary glTF container: its byte layout, contents, loading behavior, inspection tools, and limits.

A GLB file is the binary container form of glTF 2.0. It can package a complete 3D scene—its hierarchy, meshes, materials, animation data, and texture images—into one file that is convenient to upload, cache, and move between tools.

The important qualifier is can. A GLB is allowed to reference external files, so the extension alone does not prove that it is self-contained. It also does not prove that the model is small, optimized, valid, or supported by a particular viewer. GLB describes packaging; the glTF data inside describes the scene.

The short answer#

Think of a GLB as a two-part binary envelope:

  1. A JSON chunk describes what the scene contains and how its objects refer to one another.
  2. An optional binary chunk stores raw buffers such as positions, normals, UVs, indices, animation values, and embedded image bytes.

The glTF 2.0 specification defines the layout. A GLB starts with a 12-byte header, followed by one JSON chunk and, normally, one BIN chunk. Every chunk begins with its own 8-byte header. Chunk boundaries are padded to multiples of 4 bytes.

That gives a typical file this shape:

12-byte GLB header
  4 bytes: magic number 0x46546C67 (ASCII "glTF")
  4 bytes: container version (2 for glTF 2.0)
  4 bytes: total file length

8-byte JSON chunk header
  4 bytes: JSON data length
  4 bytes: chunk type 0x4E4F534A (ASCII "JSON")
JSON bytes, padded with spaces to 4-byte alignment

8-byte BIN chunk header (optional)
  4 bytes: binary data length
  4 bytes: chunk type 0x004E4942 (ASCII "BIN")
binary bytes, padded with zeroes to 4-byte alignment

This overhead is tiny. A conventional two-chunk GLB adds 28 bytes of headers plus at most 6 padding bytes. If a 20 MB model is too large, the container is not the reason; the geometry, images, animation, or other payload is.

What is actually inside a GLB?#

The JSON chunk contains arrays of glTF objects. The most common are:

  • scenes: entry points listing root nodes;
  • nodes: hierarchy objects with transforms and optional references to meshes, cameras, skins, or lights;
  • meshes: collections of primitives;
  • primitives: draw-ready geometry associated with a material;
  • accessors: typed descriptions of data such as 50,000 three-component floating-point positions;
  • bufferViews: byte ranges within a buffer;
  • buffers: raw binary storage;
  • materials, textures, images, and samplers: surface appearance and texture sampling;
  • animations and skins: keyframes, joints, and inverse-bind matrices;
  • extensionsUsed and extensionsRequired: features beyond the core specification.

The BIN chunk is deliberately unstructured. JSON accessors and buffer views give those bytes meaning. For example, an accessor might say that a byte range contains 65,000 values, each a three-component 32-bit float. A mesh primitive then assigns that accessor to the POSITION attribute. Another accessor can interpret a different range as unsigned 16-bit triangle indices.

Images may also live in the BIN chunk. In that case an image object points to a buffer view and declares a MIME type such as image/png or image/jpeg. The binary chunk can therefore contain both GPU-oriented numeric arrays and already-compressed image bitstreams.

A concrete size breakdown#

Suppose a GLB is 15.4 MB. An inspection tool reports:

  • 42 KB of JSON;
  • 3.8 MB of mesh and animation buffers;
  • 11.6 MB of embedded PNG and JPEG images;
  • less than 40 bytes of GLB headers and alignment padding.

Reducing triangle count by 50% can only act on part of the 3.8 MB buffer allocation. Even if that geometry portion also shrank by exactly 50%—which is not guaranteed—the file would still be roughly 13.5 MB because images dominate it. The useful first action is to inspect the texture dimensions, semantic roles, and compression, not to choose a more aggressive decimation ratio.

The reverse case also occurs. A scan with one 1 MB JPEG and 18 MB of dense positions, normals, UVs, and indices is geometry-heavy. Resizing its image will not reach a 5 MB target. A component breakdown turns “make the GLB smaller” into a solvable problem.

How an application loads a GLB#

A loader generally performs these stages:

  1. Fetch the bytes. On the web this is usually one HTTP response with the model/gltf-binary content type.
  2. Read and verify the header. The magic value, version, and declared total length catch obvious corruption or wrong file types.
  3. Parse the JSON chunk. This produces the object graph and all byte-range descriptions.
  4. Resolve resources. Embedded buffer views are read from the BIN chunk; external URIs, if present, require additional requests.
  5. Decode compressed data. Draco, meshopt, KTX2/Basis, or other extensions may require separate decoder code and CPU work.
  6. Create runtime resources. The application creates vertex/index buffers, decodes images, creates GPU textures, compiles material shaders, and builds its scene graph.
  7. Render. The cost now depends on primitives, vertices, triangles, fragments, materials, lights, and the rest of the scene—not the .glb extension.

This explains why a 2 MB GLB can still be troublesome. A highly compressed 4096 × 4096 image may occupy only a few megabytes in the file but decode to 67,108,864 bytes as an uncompressed 8-bit RGBA texture: 4096 × 4096 × 4. A full mip chain adds about one third more, bringing the rough allocation to 85.3 MiB before runtime-specific alignment or duplication. Packaging and runtime memory are different budgets.

GLB versus .gltf with separate resources#

Both forms carry the same glTF scene model. Choose based on delivery requirements.

Use GLB when:

  • users upload or download a single artifact;
  • atomic caching and versioning are valuable;
  • the model is delivered as one logical unit;
  • you want to avoid base64 data URIs, which add roughly 33% before transport compression;
  • the target platform expects a .glb file.

Use .gltf plus external .bin and image files when:

  • several models can share the same texture or buffer URL;
  • the application wants to cache large images separately;
  • resources need different cache headers or CDN transforms;
  • a pipeline benefits from inspecting JSON directly;
  • the application will fetch selected resources on demand.

One file is operationally simpler, but it is not inherently faster in every system. A single GLB prevents resource-level cache reuse and may require fetching the entire container before a simple loader exposes any of it. Conversely, dozens of tiny external resources can add request and coordination overhead. Measure the actual delivery path.

GLB is not a ZIP archive#

A frequent misconception is that converting .gltf to .glb “compresses” the model. GLB removes the approximately 33% base64 expansion of data-URI resources and combines files, but it does not apply general-purpose archive compression to the JSON and BIN chunks. Geometry is raw unless a geometry compression extension is used. PNG and JPEG images retain their own compression. A server may additionally apply HTTP compression where appropriate, but that is transport behavior, not GLB structure.

The distinction matters when comparing tools. If one converter produces a much smaller GLB, inspect what else it changed: it may have quantized attributes, run meshopt or Draco, resized images, changed PNG to JPEG or WebP, removed unused data, or deduplicated accessors. The container conversion alone does not explain a large reduction.

Can a GLB be streamed?#

glTF 2.0 does not define a complete streaming system, and a typical GLB loader downloads the file as one response before constructing the scene. The layout nevertheless has useful properties for range-aware systems. JSON is the first chunk, so a client can fetch the 12-byte file header, the 8-byte JSON chunk header, and then the declared JSON range before deciding which binary ranges it needs. Accessors and buffer views provide offsets and lengths for geometry. Embedded images also occupy known buffer views.

That does not make arbitrary range loading automatic. The HTTP server must support byte ranges; the application must schedule and assemble them; compressed extensions may group data in ways that require a complete compressed buffer view; and a single interleaved buffer view may contain attributes for many primitives. Many off-the-shelf loaders intentionally favor a simpler full fetch.

If progressive display is a product requirement, design for it instead of assuming the container will provide it. Useful strategies include separate coarse and detailed assets, external textures with independent caching, scene partitioning, engine-specific LOD or streaming systems, and an initial poster image. Record time to first useful view, not only the time at which every byte has arrived. A 6 MB first-stage model followed by 40 MB of optional detail can feel faster than one 20 MB GLB even though the complete experience transfers more data.

How to inspect a GLB before changing it#

Use at least three layers of inspection.

1. Validate conformance#

Drop the file into the official Khronos glTF Validator or use its CLI/library. Treat errors as blockers. Review warnings individually: an unused object may be harmless, while invalid accessor bounds, non-unit normals, or extension problems can produce viewer-specific failures.

Validation answers “does this follow the format rules?” It does not answer “will this meet our mobile memory budget?”

2. Generate a component report#

The glTF-Transform CLI can report scenes, meshes, primitives, vertices, indices, materials, textures, animation, and extension use:

npx @gltf-transform/cli inspect model.glb

Record at minimum:

  • file bytes;
  • triangle and rendered vertex counts;
  • mesh primitive and material counts;
  • image formats, dimensions, and encoded bytes;
  • animation and morph-target counts;
  • extensions used and required;
  • scene bounds and physical dimensions.

3. Test the shipping loader#

Open the file through the exact decoder and renderer configuration used in production. A conforming GLB can require KHR_draco_mesh_compression, EXT_meshopt_compression, KHR_texture_basisu, or material extensions that your loader has not enabled. If an extension appears in extensionsRequired, a client that does not support it cannot correctly render the asset by simply ignoring it.

Reading the header yourself#

When an upload fails before a normal inspector can open it, the first 12 bytes provide a useful diagnostic:

import { readFile } from 'node:fs/promises';

const data = await readFile('model.glb');
if (data.length < 12) throw new Error('Too short to be a GLB');

const magic = data.readUInt32LE(0);
const version = data.readUInt32LE(4);
const declaredLength = data.readUInt32LE(8);

console.log({
  magic: `0x${magic.toString(16)}`,
  version,
  declaredLength,
  actualLength: data.length,
});

For glTF 2.0, magic should be 0x46546c67 and version should be 2. The declared length should equal the actual byte length. Passing these checks does not validate the chunks or scene, but failing one gives a precise reason to reject the upload.

Common GLB problems and what they mean#

  • The file starts with PK instead of glTF. It is probably a ZIP archive renamed to .glb.
  • Textures are missing even though it is a GLB. The JSON may still reference external image URIs, or the exporter did not include the images.
  • The model is rotated or the wrong size. The container is fine; inspect node transforms, exporter axis conversion, and units.
  • The file validates but renders gray. The loader may not support a required material extension, image format, or texture compression extension.
  • The download is small but opening is slow. Measure decoder time, image decode, shader compilation, and GPU upload.
  • The GLB is huge after “packing.” Embedded resources were consolidated, not optimized. Inspect which resources dominate.
  • One viewer works and another fails. Compare extension support, color-space handling, tangent generation, alpha modes, and implementation limits.

A practical acceptance checklist#

Before publishing a GLB, verify:

  • The Khronos Validator reports no errors.
  • All required extensions are supported by the production loader.
  • No unintended external URIs remain.
  • File size and component sizes meet written budgets.
  • Texture dimensions make sense for the closest intended view.
  • Triangle, rendered vertex, primitive, and material counts are recorded.
  • Bounds, scale, orientation, and pivot are correct in the target application.
  • Materials match under representative environment lighting.
  • Front, rear, side, top, and oblique views show no missing geometry.
  • Cold-load time and peak memory were measured on the lowest supported device.
  • The editable source file remains archived separately from the delivery GLB.

Sources and further reading#

A GLB is best understood as a reliable delivery envelope around a glTF scene. Once you separate the envelope from its payload, optimization becomes much more direct: inspect which bytes and runtime resources dominate, change those, and validate the result in the loader that will actually ship.

Keep learning

Related guides

Foundations14 min

Skeletal Rigging Basics: How Bones and Skin Weights Drive a 3D Character

Learn how joints, bone hierarchies, and skin weights make a 3D character move, and how a rigged GLB actually deforms in web and game engines.

riggingskeletal animationskin weights
Foundations10 min

Quad Retopology for Beginners: Build a Clean Mesh That Keeps the Shape

Learn what quad retopology is, why artists use it, and how to rebuild a dense sculpt or scan as a practical low-resolution mesh.

quad retopologyretopologytopology
Foundations10 min

Levels of Detail (LOD): Designing Models That Scale With Distance

Design, generate, switch, and validate mesh LODs using screen-space evidence, stable transitions, and platform-aware performance measurements.

LODlevel of detailmesh optimization