#4461: Inside a GLB File: Binary Format, Textures, and PBR

What's actually inside a GLB file? We hex dump one and walk through every byte — JSON chunks, binary buffers, textures, and PBR materials.

Featuring
Listen
0:00
0:00
Episode Details
Episode ID
MWP-4640
Published
Duration
24:27
Audio
Direct link
Pipeline
V5
TTS Engine
chatterbox-regular
Script Writing Agent
deepseek-v4-pro

AI-Generated Content: This podcast is created using AI personas. Please verify any important information independently.

GLB is the binary container for glTF 2.0, the self-contained portable 3D asset format released by the Khronos Group in June 2017. The format is deceptively simple: a twelve-byte header (magic number "glTF", version integer, total file length) followed by two required chunks. The JSON chunk comes first — human-readable text describing the entire scene graph, nodes, meshes, accessors, buffer views, and materials. The binary buffer chunk follows, holding raw vertex positions, normals, UV coordinates, and indices. The JSON is the recipe; the binary is the ingredients.

The key to understanding file size differences lies in what's in the binary chunk. A canonical vertex with position, normal, and one UV set takes 32 bytes (eight floats). A hundred thousand triangles means roughly 3.8 MB of uncompressed vertex and index data. But the real size driver is textures. There are four levels of visual information a GLB can carry: embedded images with UV maps (the full photoreal pipeline), material-only PBR values with no images, baked vertex colors on the mesh, or bare geometry that falls back to a default grey PBR material.

The standard PBR metallic-roughness model has five texture slots: base color (albedo), metallic-roughness (packed), normal map, ambient occlusion, and emissive. Each missing texture has a specific visual consequence — missing normal maps make surfaces look flat and plastic, missing occlusion removes contact shadows that ground objects physically. Draco and Meshopt compression reduce geometry size dramatically, and polygon count, texture resolution, and compression level are the three dials that explain almost any file size difference between two GLBs of the same object.

Downloads

Episode Audio

Download the full episode as an MP3 file

Download MP3
Transcript (TXT)

Plain text transcript file

Transcript (PDF)

Formatted PDF with styling

#4461: Inside a GLB File: Binary Format, Textures, and PBR

Corn
Daniel's been poking around in GLB files and wants to know what's actually inside them. Here's what he sent us.
Corn
Two GLBs of the same object — one's two hundred kilobytes and looks like grey clay, the other's eighty megabytes and looks photoreal. Same format, wildly different contents. He wants us to walk through the binary structure: the JSON chunk that describes the scene graph, nodes, meshes, and materials, versus the binary buffer chunk holding the actual vertex positions, normals, UV coordinates, and indices. Then the big one — textured versus untextured. What does textured actually mean? Embedded image data plus a UV map telling the renderer how to wrap that image onto the geometry, versus a model with only material definitions, or vertex colors baked into the mesh, or nothing but geometry with a default grey PBR material. He wants the full PBR material set: base color slash albedo, metallic-roughness, normal map, ambient occlusion, emissive — and what happens visually when each one is missing. Why a GLB can reference textures externally instead of embedding them, and how that breaks when you hand the file to someone else. What Draco and Meshopt compression do to the geometry. And why polygon count, texture resolution, and compression are the three dials that explain almost any file size difference. Then practical advice: how do you inspect an unknown GLB before you drop it into a project.
Corn
That's a full syllabus.
Herman
It is, and I love it. So let's open the box. Literally — we're going to hex dump a GLB and walk through every byte. But first, the big picture, because the format is deceptively simple.
Herman
GLB is the binary container for glTF two point oh — the so-called JPEG of three D. The Khronos Group released glTF two point oh in June twenty seventeen, and the whole idea was a self-contained, portable three D asset format. Something you can drop into any engine, any viewer, and it just works. The GLB is the single-file version of that.
Corn
And the single-file part is what makes it interesting, because it's not actually a single blob of data. It's a container.
Herman
No, wait, I said I wasn't going to do that. It's a container with at least two chunks stuffed inside. A JSON chunk and a binary buffer chunk. The JSON chunk is the scene graph — it's human-readable text describing every node, mesh, material, and how they connect. The binary chunk is raw vertex data, index buffers, and optionally embedded images. The split is the key to understanding everything about file size.
Corn
So one chunk you can open in a text editor and read. The other is opaque bytes.
Herman
Right. And here's why that matters: if you want to know why a GLB is big, you look at the binary chunk. If you want to know what's in it, you look at the JSON. The JSON is like a recipe — it says there's a mesh with four hundred triangles, it uses material number three, the position data starts at byte offset zero in buffer view zero. The binary chunk is the ingredients — here are twelve hundred floats for those positions.
Corn
Recipe and ingredients. I'll allow it.
Herman
Thank you. Alright, let's crack open the actual binary format. If you hex dump a GLB, the first thing you see is the twelve-byte header. Four bytes for the magic number, four bytes for the version, four bytes for the total file length.
Corn
And the magic number is...
Herman
Zero x four six five four six C six seven. Which is the ASCII string glTF. Every GLB starts with those four bytes. It's how parsers know what they're looking at. Then the version — for glTF two point oh it's the integer two, stored as a little-endian unsigned int. Then the total length of the entire file in bytes.
Corn
So the header alone tells you this is a GLB, it's version two, and it's this big.
Herman
That's it. Twelve bytes and you already know the basics. Right after the header comes the first chunk. Every chunk has an eight-byte header of its own: four bytes for the chunk length, four bytes for the chunk type. The JSON chunk always comes first, and its type is zero x four E four F five three four A — that's the ASCII string JSON.
Corn
And the chunk length tells you exactly how many bytes of JSON to read.
Herman
So you read those eight bytes, then you read chunk-length bytes of JSON text. That text is the entire scene description. Nodes, meshes, accessors, buffer views, materials, textures, images, samplers, animations, skins — all of it in one JSON object. Then, if there's a binary buffer, the next chunk has type zero x zero zero four E four nine four two — that's B I N null in ASCII, four bytes with a null terminator.
Corn
Wait, the type field is four bytes and B I N is only three letters, so they null-pad it?
Herman
Yeah, it's B I N backslash zero. The chunk type field is always exactly four ASCII characters or null-padded. JSON is exactly four. B I N is three plus a null. It's tidy.
Corn
So you've got header, JSON chunk, binary chunk. That's a minimal valid GLB.
Herman
And the JSON chunk is where the detective work happens. Let me walk through what's actually in there. The top-level JSON object has arrays: nodes, meshes, accessors, buffer views, materials, textures, images. The nodes array defines the transform hierarchy — each node has a name, maybe a mesh reference, maybe children, and a matrix or translation-rotation-scale. The meshes array defines the actual drawable geometry — each mesh has primitives, and each primitive references an accessor for its indices and attributes.
Corn
So a node says where something is in space, a mesh says what to draw.
Herman
Right. And the accessor is the bridge between the JSON and the binary. An accessor says: I point to buffer view zero, starting at byte offset twelve, I contain three hundred unsigned shorts, they represent triangle indices. Or: I contain twelve hundred floats, they represent vertex positions, three per vertex. The accessor also stores min and max bounds — the bounding box of the positions, for example, which is incredibly useful for culling.
Corn
And the buffer view is the slice of the binary chunk.
Herman
A buffer view says: I cover bytes zero through fourteen thousand four hundred of the binary buffer, and I'm meant to be used as an index buffer or vertex attribute buffer. It can specify a byte stride too — if your vertex data is interleaved, the stride tells you how many bytes to skip between vertices.
Corn
Interleaved meaning positions, normals, and UVs packed together for each vertex, rather than all positions in one block, all normals in another.
Herman
Yes. Interleaved is: position, normal, UV, position, normal, UV, position, normal, UV. Non-interleaved is: all positions, then all normals, then all UVs. Interleaved is better for GPU cache coherence, but it means the buffer view stride is thirty-two bytes — eight floats at four bytes each.
Corn
That's the number Daniel was asking about. Thirty-two bytes per vertex.
Herman
That's the canonical number for a vertex with position, normal, and one UV set. Three floats for position, three for normal, two for UV. Eight floats times four bytes each. Thirty-two bytes. If you have tangents, that's another four floats — sixteen more bytes. Two UV sets, four more floats. Vertex colors, four more. It adds up fast.
Corn
So a hundred thousand vertices at thirty-two bytes each is three point two megabytes just for the vertex data, before you even count indices.
Herman
And indices are typically two bytes each for unsigned short, or four bytes for unsigned int if you have more than sixty-five thousand vertices. A hundred thousand triangles at three indices each is three hundred thousand indices. At two bytes per index, that's six hundred kilobytes. So a hundred thousand triangle mesh with positions, normals, and UVs is roughly three point eight megabytes uncompressed. That's your baseline.
Corn
And that's before we even talk about textures.
Herman
Which brings us to the big one. Textured versus untextured. Daniel asked what textured actually means, and it's more specific than most people think.
Herman
Textured means the material references a texture object. That texture object references an image. That image references either a buffer view — meaning the PNG or JPEG is embedded right in the binary chunk — or an external URI, meaning the image is a separate file on disk. And the material also defines how that texture maps onto the geometry via UV coordinates stored in the vertex data.
Corn
So textured equals image data plus UV map plus material reference. All three have to be there.
Herman
All three. And if any one is missing, you don't get a textured model. But here's where it gets interesting — there are actually four levels of visual information a GLB can carry. Level one: embedded images with UV maps. The full pipeline. This is your eighty megabyte photoreal model. Level two: material definitions only. The JSON has PBR values — base color as a float array, metallic factor, roughness factor — but no image data at all. The renderer applies those values uniformly across the whole surface. Level three: baked vertex colors. The mesh has a COLOR underscore zero attribute — every vertex carries its own RGB value. No textures needed, but you can get surprisingly detailed surface appearance. Level four: geometry only. No materials, no vertex colors. The renderer falls back to a default grey PBR material.
Corn
Level four is the grey clay Daniel mentioned.
Herman
That's the one. And here's what's counterintuitive: level two and level three can look really good. A model with well-tuned PBR values — the right base color, the right roughness, the right metalness — can look like polished copper or rough concrete without a single texture pixel. Add vertex colors and you can paint detail right onto the mesh. It's not photoreal, but it's not grey clay either.
Corn
And it'll be tiny compared to the textured version.
Herman
Two hundred kilobytes versus eighty megabytes tiny. All the visual weight is in the textures.
Corn
So let's go through the PBR material set. What are the maps, and what breaks when they're missing?
Herman
The standard PBR metallic-roughness model has five texture slots. Base color — that's your albedo or diffuse map, the surface color ignoring lighting. Metallic-roughness — this is a packed texture where the blue channel is the metalness value and the green channel is the roughness. The red channel is unused, or sometimes holds occlusion. Normal map — tangent-space surface normals that fake small-scale geometric detail. Occlusion — ambient occlusion in the red channel, which darkens crevices and corners. And emissive — parts of the surface that glow independently of scene lighting.
Corn
And each one missing has a specific visual consequence.
Herman
Missing base color and the surface is white or whatever the material's base color factor defaults to. Missing metallic-roughness and everything looks uniformly shiny or uniformly matte — you lose the variation that makes leather look different from chrome on the same object. Missing the normal map and the surface goes flat — all those little bumps and scratches disappear, and it looks plastic and smooth. Missing occlusion and you lose contact shadows — the darkening where two surfaces meet, under bolts, in crevices. The model looks like it's floating, ungrounded.
Corn
The occlusion one is subtle but it's what makes things feel physically present.
Herman
It really is. Ambient occlusion is the unsung hero of PBR. You don't notice it when it's there, but you definitely notice when it's gone. Everything looks like a video game from twenty years ago.
Corn
And emissive?
Herman
Emissive is the most optional. It's for things like LED panels, glowing eyes, headlights. If it's missing, those surfaces just don't glow. For most models it's not even included.
Corn
That's the material side. Now the other big piece Daniel asked about: external texture references. Why would you not embed the textures?
Herman
Three reasons. Development speed, version control, and CDN delivery. During development, if you're exporting from Blender every five minutes, re-encoding all your four K textures into the GLB on every export is painfully slow. Keeping them external means the GLB export is instant — it just writes URIs. For version control, you can track the geometry changes separately from the texture changes. And for web delivery, external textures can be cached independently by the browser — change the geometry, the cached textures still load instantly.
Corn
The breakage happens when you hand someone the GLB without the textures folder.
Herman
Classic scenario. You export from Blender, textures go to a folder called textures next to the GLB. The URIs in the JSON are relative paths: textures slash albedo dot png. You zip up the GLB and email it. The recipient opens it and gets a grey blob because the URIs point to files that don't exist on their machine. There's no standard fallback, no embedded thumbnail, nothing. The GLB is technically valid — the JSON is fine, the geometry is fine — but it's visually broken.
Corn
There's no warning. The file just opens grey.
Herman
No warning at all. Unless your viewer logs missing resources, you might not even know it's supposed to be textured. That's why the inspection step Daniel asked about is so important — you need to check for external URIs before you assume the file is self-contained.
Corn
Alright, let's talk compression. Draco and Meshopt. What are they actually doing?
Herman
Both compress the geometry — the vertex data and index buffers in the binary chunk. They don't touch textures at all. Draco is Google's library, and it's a bit-level compressor. It uses Edgebreaker connectivity compression on the triangle indices — that's an algorithm that walks the mesh surface and encodes the triangle connectivity in about one to two bits per triangle. Then it quantizes vertex attributes — positions, normals, UVs — to a specified bit depth and entropy-encodes them.
Corn
It's lossy compression on the vertex data.
Herman
Lossy in a controlled way. You specify the quantization bits — typically eight to twelve bits per component. At ten bits, positions are quantized to about a thousand steps per axis, which for most models is visually indistinguishable from the original thirty-two-bit floats. The compression ratios are dramatic: ten to one to fifteen to one on geometry data is typical. A fifteen megabyte geometry buffer becomes one to one and a half megabytes.
Corn
The cost is decompression time.
Herman
CPU cost at load time, yes. Draco decoding is not trivial — for a large mesh it can take a few hundred milliseconds. And the extension is KHR underscore draco underscore mesh underscore compression. It's a Khronos extension, widely supported. But it's all-or-nothing for the mesh — if your runtime doesn't support Draco, the mesh simply won't load.
Corn
Versus Meshopt.
Herman
Meshopt is different. It's a byte-level compressor using the meshoptimizer library by Arseny Kapoulkine. Instead of bit-level entropy encoding, it uses filter-based compression. It applies a filter to reorder the bytes for better compressibility, then runs a standard LZ compressor. The filters are the clever part.
Corn
There are three filter modes.
Herman
Three. Octahedral for normals — that encodes a unit vector in just two bytes by mapping the octahedron to a square, and it's remarkably precise. Quaternion for rotations — encodes rotation quaternions in a compressed form. Exponential for positions — encodes floating point values with a shared exponent, good when positions have a limited dynamic range. After filtering, the data is compressed with a standard LZ compressor.
Corn
It tends to beat Draco on ratio and speed.
Herman
Often, yes. On the same mesh, Draco might get a fifteen megabyte geometry buffer down to one point two megabytes, while Meshopt gets it to nine hundred kilobytes. And Meshopt decompression is generally faster — it's simpler, byte-aligned operations rather than bit-unpacking. The extension is EXT underscore meshopt underscore compression — a vendor extension, but it's supported in three dot js, Babylon, and most web engines.
Corn
If you control the runtime, Meshopt is usually the better choice.
Herman
If you control the runtime and you're optimizing for web delivery, Meshopt is fantastic. If you need maximum compatibility — say you're uploading to a platform that might use different engines — Draco has broader support because it's a Khronos extension.
Corn
Which brings us to Daniel's three dials. Polygon count, texture resolution, compression.
Herman
This is the framework that explains almost any GLB file size difference. Dial one: polygon count. Each triangle costs roughly fifty to eighty bytes uncompressed — three indices plus three positions plus three normals plus two UVs. A five hundred thousand triangle model is around thirty to forty megabytes of geometry before compression. Dial two: texture resolution. A four K PNG is fifteen to twenty megabytes. A one K PNG is one to two megabytes. A five twelve PNG is about three hundred kilobytes. If your model has five PBR maps at four K, that's seventy-five to a hundred megabytes right there. Dial three: compression. Draco or Meshopt on the geometry gets you ten to fifteen to one. JPEG or PNG compression on textures — or better, KTX two slash Basis Universal for GPU-compressed textures — can get you another five to ten to one.
Corn
That eighty megabyte GLB Daniel mentioned — break it down.
Herman
Seventy megabytes of four K textures plus ten megabytes of five hundred thousand triangle geometry. Compress the textures to two K and switch to JPEG — that's about five megabytes of textures. Enable Draco on the geometry — that's about one megabyte. Total: six megabytes. Same visual quality at normal viewing distances, ninety-two percent smaller.
Corn
If someone just sees the eighty megabyte file and the six megabyte file side by side, they assume the six megabyte one is lower quality.
Herman
That's the misconception. Smaller doesn't mean worse. It means someone turned the dials intelligently. The three dials let you trade off size versus quality systematically, and most exporters default to the safest, least optimized settings — maximum resolution, no compression — because they'd rather give you a huge file that definitely works than a small file that might have compatibility issues.
Corn
How do you actually inspect an unknown GLB? Daniel wanted the practical workflow.
Herman
Four methods, from quickest to deepest. Method one: drag it into glTF Transform Inspect. That's a command-line tool — gltf-transform inspect model dot glb. It prints chunk sizes, vertex counts, texture dimensions, compression extensions, everything. You get a one-page summary of exactly what's inside.
Corn
If you don't want to install a command-line tool?
Herman
Method two: the glTF Viewer at gltf-viewer dot don mccurdy dot com. Drag your GLB in, it renders it and shows the full scene graph in a panel. You can click through every node, every mesh, every texture, see the UV maps, see the material values. It's the easiest way to visually explore a file.
Corn
If you want to get your hands dirty?
Herman
Method three: open it in a text editor. A GLB is binary, but if you rename it to dot gltf, many editors will show you the JSON chunk. It won't be pretty — there's binary garbage around it — but you can find the JSON and read the materials, the URIs, the accessor counts. Or use a hex editor and look at the magic number, the chunk headers, the total length. Method four: if you're in a three dot js project, use GLTF Loader with manager dot set URL modifier to log every resource the loader requests. That catches external texture URIs in real time.
Corn
The checklist before dropping a GLB into a project.
Herman
Five questions. One: is it self-contained? Check for external URIs in the images array. Two: what's the texture resolution? Four K is overkill for web — resize to two K or one K. Three: what's the triangle count? Over a hundred thousand needs level-of-detail variants. Four: is it compressed? If not, run it through gltf-transform with Draco or Meshopt. Five: does it use compression extensions your runtime supports? Check for KHR underscore draco or EXT underscore meshopt and make sure your engine can decode them.
Corn
And the golden rule?
Herman
File size equals polygons times bytes per vertex, plus texture pixels times bytes per pixel times compression ratio. Everything else is noise. Understand those three dials and you can predict and control any GLB's size.
Corn
Next time someone sends you a GLB, you're not just getting a file — you're getting a JSON scene graph, a binary geometry buffer, possibly some compressed textures, and a whole lot of decisions about what to embed and what to reference.
Herman
Now you know how to read those decisions. What about animated GLBs, though? That's a whole other can of worms — skinning, morph targets, animation samplers. The binary chunk gets a lot more complicated when you add vertex joints and weights.
Corn
Maybe we'll open that box another time.

Hilbert: If I'm downloading random GLBs from Sketchfab and half of them show up grey, is that broken files or missing textures?
Herman
Almost certainly missing textures. The geometry is fine, the JSON is fine, but the images array has URIs pointing to files that aren't on your machine. Check for external references — if they're there, the file isn't self-contained. You need the textures folder next to the GLB, or you need to find a version with embedded images.
Corn
Good question, Hilbert. Thanks.
Herman
Thanks, Hilbert.
Corn
Alright. If you've got a GLB sitting on your desktop right now, go inspect it. You'll see the format completely differently once you know what the chunks are doing.
Herman
If you've got a weird prompt of your own — technical, philosophical, whatever — send it in. Show at my weird prompts dot com. We read everything.
Corn
This has been My Weird Prompts. I'm Corn.
Herman
I'm Herman Poppleberry. We'll be back soon.

This episode was generated with AI assistance. Hosts Herman and Corn are AI personalities.