How KHR_mesh_quantization Reduces glTF File Sizes
The KHR_mesh_quantization extension reduces glTF 3D
model buffer sizes and GPU memory footprints by truncating
high-precision vertex attributes into compact, lower-bit formats.
Standard glTF files typically store geometry using 32-bit floating-point
numbers for properties like positions, normals, and texture coordinates.
By compressing these values into 8-bit or 16-bit integers and mapping
them to a normalized range, the extension cuts vertex buffer sizes by up
to 50% to 75% without requiring significant computational overhead or
introducing noticeable visual degradation.
The Problem: 32-Bit Float Overhead
In default glTF 2.0 representations, each vertex attribute uses
single-precision 32-bit floating-point numbers (FLOAT),
which consume 4 bytes per component:
- Positions (\(X, Y, Z\)): 12 bytes per vertex.
- Normals (\(X, Y, Z\)): 12 bytes per vertex.
- Tangents (\(X, Y, Z, W\)): 16 bytes per vertex.
- Texture Coordinates (\(U, V\)): 8 bytes per vertex.
For complex meshes containing hundreds of thousands of vertices, storing raw floating-point buffers creates massive file payloads that slow down network transfers and saturate GPU memory bandwidth.
How Quantization Works
Quantization converts continuous 32-bit floating-point values into
discrete integer values within a bounded domain. Instead of recording
exact arbitrary floating-point numbers,
KHR_mesh_quantization maps the bounding volume of the
geometry to a discrete integer grid.
- Normalization and Scaling: The asset's bounding box is measured to determine the minimum and maximum boundaries of the mesh. Coordinates within this range are normalized, typically mapped to a \([0, 1]\) or \([-1, 1]\) interval.
- Bit-Depth Truncation: Continuous values are
converted to fixed-size integers by multiplying the normalized value by
the maximum integer representation and rounding:
- Positions: Truncated from 32-bit floats to 16-bit
signed or unsigned integers (
SHORTorUNSIGNED_SHORT). This reduces position storage from 12 bytes to 6 bytes per vertex (a 50% reduction). - Normals and Tangents: Because normals represent
unit vectors bounded strictly within \([-1,
1]\), they can be truncated to 8-bit signed integers
(
BYTE). This slashes normal attribute storage from 12 bytes down to 3 or 4 bytes per vertex (a 66% to 75% reduction). - Texture Coordinates: Mapped from 32-bit floats to
16-bit unsigned integers (
UNSIGNED_SHORT), reducing UV data from 8 bytes to 4 bytes per vertex.
- Positions: Truncated from 32-bit floats to 16-bit
signed or unsigned integers (
Hardware-Accelerated Dequantization
A major advantage of KHR_mesh_quantization is that it
avoids costly CPU-side decompression before rendering. Modern graphics
APIs (DirectX, Vulkan, Metal, and OpenGL/WebGL) natively support vertex
buffer formats with lower-precision signed and unsigned normalized types
(SNORM and UNORM).
When the GPU reads quantized attributes:
- The hardware automatically unpacks normalized 8-bit or 16-bit integers back into floats on the fly during vertex attribute fetch.
- For position data requiring non-uniform bounds, standard node transformations (scale and translation matrices) are applied in the vertex shader to scale the integer grid back to the original model dimensions.
Impact on Performance and Quality
By lowering the precision of vertex attributes:
- Payload Size: Binary glTF (
.glb) files shrink substantially, improving load times over web networks. - Cache Efficiency: Smaller vertex structures allow more vertices to fit into GPU L1/L2 caches, improving vertex shading throughput.
- Visual Fidelity: 16-bit quantization divides an axis into 65,536 distinct steps. For most real-time 3D models, this resolution provides sub-millimeter precision, making the truncation invisible to the human eye under standard viewing conditions.