Three.js From Zero · Article s6-04
S6-04 Meshopt
Meshopt — SIMD-fast mesh optimization
Draco gives you 10× at the cost of a slow WASM decoder. Meshopt gives you 3× with SIMD-speed decode AND cache-friendly index reordering. Two wins: smaller AND faster rendering.
1. What Meshopt actually does
Three independent optimizations:
- Vertex cache optimization: reorder triangles so subsequent tris share GPU-cached vertices. 30-50% vertex shader savings.
- Overdraw optimization: reorder triangles front-to-back. Early-Z rejects hidden pixels, fragment shader saves.
- Compression: zigzag encoding on quantized attributes + zstd-like entropy coding. 3-5× smaller.
Runtime decoder: 1-3 orders of magnitude faster than Draco. SIMD in WASM.
2. The three stages, separately
# Optimize only (no compression)
meshoptimizer -i mesh.obj -o opt.obj
# gltf-transform: combined pipeline
gltf-transform meshopt model.glb compressed.glb --compression medium
--compression: low (just optimize, no compress), medium (balanced), high (smaller, more quantization).
3. Three.js consumer
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
const loader = new GLTFLoader();
loader.setMeshoptDecoder(MeshoptDecoder);
loader.load('model.meshopt.glb', gltf => scene.add(gltf.scene));
One line wired. No worker dance, decoder is tiny.
4. When to pick Meshopt
| Your need | Pick |
|---|---|
| Absolute minimum bytes | Draco |
| Fast load time | Meshopt |
| Many small meshes streamed | Meshopt |
| One big model loaded once | Draco |
| Animations with lots of frames | Meshopt (stream-friendly) |
| Mobile, limited CPU | Meshopt (SIMD, cheap) |
5. Mix them
# Compress geometry with Draco, textures with KTX2, animation with Meshopt
gltf-transform draco model.glb out.glb
gltf-transform uastc out.glb out2.glb
gltf-transform meshopt out2.glb out3.glb --compression medium
Different attributes pick their best compressor. gltf-transform orchestrates.
6. The non-compression win
7. Under the hood — zigzag encoding
Quantized attributes have small deltas between adjacent vertices.
// Raw positions : 10245, 10247, 10243, 10251, ...
// Deltas : 10245, +2, -4, +8, ...
// Zigzag encoded : 20490, 4, 7, 16, ... // maps small negatives next to small positives
// Byte-aligned : 1-byte shorts for most
// Entropy code : final squeeze
8. Index buffer compression
Triangle index list has runs like (0, 1, 2, 1, 2, 3, 2, 3, 4). Meshopt detects these as "triangle strips in disguise" and stores tiny deltas.
Result: 1-2 bytes per index instead of 2-4.
9. Gotchas
- Requires the
EXT_meshopt_compressionglTF extension declared. - Blender glTF exporter doesn't write Meshopt directly — run gltf-transform after.
- Animations must be stored as accessors (standard glTF); animation-specific Meshopt pass needs
--quantize-animations.
10. Takeaways
- Meshopt = vertex cache reorder + overdraw reorder + zigzag+zstd compression.
- 3-5× smaller with 10-50× faster decode than Draco.
- Use
gltf-transform meshopt. Load withsetMeshoptDecoder. - Stack with Draco + KTX2 for every-attribute-its-best-compressor.
- Even the optimizer alone (no compression) boosts render speed.
This article is concept-heavy; Meshopt demo requires a model pre-encoded. See S6-05 for the CLI workflow demo.