Three.js From Zero · Article s6-04

S6-04 Meshopt

Season 6 · Article 04

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:

  1. Vertex cache optimization: reorder triangles so subsequent tris share GPU-cached vertices. 30-50% vertex shader savings.
  2. Overdraw optimization: reorder triangles front-to-back. Early-Z rejects hidden pixels, fragment shader saves.
  3. 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 needPick
Absolute minimum bytesDraco
Fast load timeMeshopt
Many small meshes streamedMeshopt
One big model loaded onceDraco
Animations with lots of framesMeshopt (stream-friendly)
Mobile, limited CPUMeshopt (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

Even if you skip compression entirely, run Meshopt's optimizer. Vertex cache + overdraw reorder = 20-40% rendering speedup on older GPUs. Free perf.

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_compression glTF 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 with setMeshoptDecoder.
  • 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.