Comparison

Draco vs Meshopt: Which glTF Geometry Compression Should You Use?

· 8 min read

Draco vs Meshopt compared

Draco (KHR_draco_mesh_compression) is built to squeeze static mesh geometry as small as possible, at the cost of a larger decoder and slower decoding. Meshopt (EXT_meshopt_compression, from the meshoptimizer library) decodes much faster, ships a decoder under 30 KB, also compresses animation, morph targets and instance data, and is designed to be served with gzip or Brotli on top. Pick Draco for geometry-heavy static models where download size matters most; pick Meshopt for animated scenes, many small models, or when load-time CPU on phones matters.

This guide covers how each one works, how they compare on size, speed and features, which engines and tools support them, and the exact commands to apply either one.

What Draco and Meshopt are

Draco (KHR_draco_mesh_compression)

Draco is Google's open-source mesh compression library. Its glTF extension, ratified by Khronos, replaces the vertex attributes and indices of each mesh primitive with a single Draco-encoded blob. The encoder quantizes the attributes, encodes the triangle connectivity (gltf-transform defaults to the edgebreaker method) and entropy-codes the result.

Two details from the spec matter in practice: the extension only applies to triangle and triangle-strip primitives, and Draco may change the order and number of vertices. For Draco's internals and settings, see our Draco compression guide.

Meshopt (EXT_meshopt_compression)

meshoptimizer is Arseny Kapoulkine's MIT-licensed library for making meshes smaller and faster to render: vertex cache and overdraw optimization, simplification, quantization and a set of compression codecs. Those codecs are exposed to glTF as EXT_meshopt_compression, which Khronos has also ratified.

Instead of working per primitive, Meshopt compresses individual buffer views, so it handles any binary data in the file: vertex attributes, indices, points and lines, morph target deltas, animation keyframes, and instance transforms for EXT_mesh_gpu_instancing. Optional filters (octahedral for normals, quaternion for rotations, exponential for floats) trade precision for a smaller result.

Where KHR_mesh_quantization fits

Meshopt expects its input to be quantized first. KHR_mesh_quantization lets glTF store positions, normals, UVs and tangents as 8- or 16-bit integers instead of 32-bit floats; the spec's own example shrinks a typical PBR vertex from 48 to 20 bytes. Both gltfpack and the gltf-transform meshopt command quantize automatically before compressing.

A side benefit: quantized attributes stay 8- or 16-bit on the GPU, so they also use less video memory. Draco quantizes internally too, but decoders usually expand the data back to floats. three.js's DRACOLoader, for example, outputs Float32Array attributes by default.

KHR_meshopt_compression, the next version

Khronos is standardizing Meshopt as KHR_meshopt_compression, which was a release candidate at the time of writing. It keeps the EXT bitstream as "version 0" and adds a version 1 format that, according to the spec, compresses vertex data better at no extra runtime cost, plus a new color filter. three.js (r183 and later), gltfpack and Blender 5.2 already read it, while Babylon.js and gltf-transform currently handle only the EXT version. Stick with EXT unless you control the loader.

Draco vs Meshopt at a glance

DracoMeshopt
glTF extensionKHR_draco_mesh_compressionEXT_meshopt_compression (KHR_meshopt_compression in progress)
Works onMesh primitives (triangles only)Any buffer view
Morph targets, animation, instancing dataNot compressedCompressed
Size before gzip/BrotliUsually smaller; designed for maximum ratioLarger; designed to be compressed further
Extra gain from gzip/BrotliLittleSignificant
Decode speedSlower (runs in Web Workers in three.js)Around 1 GB/s with WebAssembly SIMD, per the spec
Decoder size (three.js r186)About 345 KB, 100 KB gzippedAbout 29 KB, 8 KB gzipped
Lossy?Yes: quantization, may reorder verticesCodec is lossless; quantization and filters are lossy
GPU memory after decodingUsually 32-bit floatsCan stay 8- or 16-bit

Compressed size, and why gzip and Brotli change the answer

Draco's output is already entropy-coded, so gzip or Brotli on top gains little. The gltf-transform docs say it directly: when a model contains "JPEG textures or Draco geometry, gzip is unlikely to add much further benefit."

Meshopt does the opposite. The EXT_meshopt_compression spec says the bitstream is built so that "general-purpose compressor can compress it further", instead of being as small as possible on its own. Before transport compression, the Draco file is typically the smaller of the two. Meshopt is designed to win back much of that difference once gzip or Brotli is applied.

What that means in practice:

  • Compare sizes after compression. Run gzip -9 -k or brotli on both outputs and compare those numbers, because that is what your users download.
  • Check that your server compresses GLB files. In DevTools, the response for your .glb should have a Content-Encoding: br or gzip header. If it doesn't, add model/gltf-binary to your server or CDN compression settings. If you can't, Draco is the safer choice.
  • Mind the decoder on small models. gltf-transform's docs note that for geometry under about 1 MB, the size of Draco's WASM decoder "may outweigh size savings."

Decode speed and decoder size

Both formats are decoded on the CPU before the data reaches the GPU, so neither makes rendering itself faster. What differs is how long decoding takes and how much code the browser downloads first.

  • Meshopt: the spec cites around 1 GB/s with WebAssembly SIMD on modern desktop hardware. In three.js r186 the decoder is one 29 KB JavaScript file with the WebAssembly embedded. It decodes on the main thread unless you call MeshoptDecoder.useWorkers(n).
  • Draco: decoding is slower (gltf-transform's docs call Meshopt decoding "considerably faster"). three.js's DRACOLoader runs it in up to four Web Workers by default, which keeps the main thread free, but the decoder (WASM plus JS wrapper) is about 345 KB and has to arrive before the first model can be decoded. The glTF-specific build in libs/draco/gltf/ is smaller, about 250 KB.

With one model on a fast desktop, you will rarely notice the difference. It shows up on low-end phones, with many or large models, and when time to first render matters.

Streaming and progressive loading

Neither extension is progressive on its own: each compressed primitive or buffer view is decoded as a whole, and three.js's GLTFLoader downloads the entire GLB before parsing it. Meshopt's per-buffer-view design does let loaders decode straight into GPU-ready buffers, and it works with uncompressed fallback buffers (gltfpack's -cf flag) for loaders that lack the extension. The Draco spec also allows an uncompressed fallback. If you need a model to appear coarse first and refine as it streams, you need separate LOD files or a plugin such as NEEDLE_progressive, which three.js lists as a third-party GLTFLoader plugin.

Engine and tool support

Support as of September 2026, checked against each project's source or docs:

Engine or toolDracoMeshopt
three.jsYes, via DRACOLoaderYes, via setMeshoptDecoder (EXT since r122, KHR since r183)
React Three Fiber (drei useGLTF)Yes, on by defaultYes, on by default
Babylon.jsYes, decoder loaded from the Babylon CDNEXT only, since 5.0, no setup needed
<model-viewer>Yes, decoder loaded on demandYes
Unity (glTFast)Import and export with the Draco for Unity packageImport with com.unity.meshopt.decompress
Godot 4Not built inNot built in
BlenderImport and exportImport and export since 5.2 LTS (EXT and KHR)
gltf-transformdraco commandmeshopt command (EXT); the default for optimize
gltfpackNot supported, including as input-c, -cc (EXT), -cz (KHR)
compress-glb.comYes, with adjustable level and quantizationNo

Godot refuses files that list an unsupported extension as required, so ship uncompressed geometry to Godot unless a plugin adds support. Blender 5.1 and earlier refuse files that require Meshopt with "Extension EXT_meshopt_compression is not available on this addon version". To see which extensions a file uses before you pick a loader setup, run it through the free glTF validator.

How to apply each one

gltf-transform

gltf-transform supports both, and can convert a Draco file to Meshopt because it decodes Draco on read:

# Draco (defaults: edgebreaker, 14-bit positions, 10-bit normals, 12-bit UVs)
npx @gltf-transform/cli draco input.glb output.glb --quantize-position 14

# Meshopt (EXT_meshopt_compression); --level is "high" (default) or "medium"
npx @gltf-transform/cli meshopt input.glb output.glb --level high

# Full optimization pass; --compress defaults to meshopt
npx @gltf-transform/cli optimize input.glb output.glb --compress draco --texture-compress webp

Both are lossy, so compress once, as the last step, from your source file. Our gltf-transform optimize guide explains the other flags.

gltfpack

gltfpack ships with meshoptimizer and compresses with Meshopt only, never Draco. The native binaries from the releases page handle larger files and texture compression; the npm package is fine for quick runs.

# EXT_meshopt_compression with extra compression
gltfpack -i input.glb -o output.glb -cc

# KHR_meshopt_compression at a higher level (needs a newer loader, e.g. three.js r183+)
gltfpack -i input.glb -o output.glb -cz

# Keep uncompressed fallback buffers for environments without WebAssembly
gltfpack -i input.glb -o output.glb -c -cf

gltfpack also quantizes, merges meshes and prunes nodes by default. Add -kn to keep named nodes and -km to keep named materials if your code looks them up by name.

Loading both in three.js

One loader can handle either format:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';

const loader = new GLTFLoader()
  .setDRACOLoader(new DRACOLoader().setDecoderPath('/draco/'))
  .setMeshoptDecoder(MeshoptDecoder);

const gltf = await loader.loadAsync('/models/scene.glb');

Decoder paths, KTX2 textures and React Three Fiber are covered in how to load GLB models in three.js.

Which one should you use?

  • Static, geometry-heavy models (CAD, scans, photogrammetry, product models): Draco. It usually gives the best compression ratio, and one decoder download is easily paid back.
  • Hosting without gzip or Brotli (for example files served straight from object storage): Draco, since Meshopt counts on transport compression.
  • Animated characters, morph targets, instanced scenes: Meshopt. Draco leaves animation and morph data uncompressed, and that data can be a large part of the file.
  • Many small models, or mobile-first pages: Meshopt, for its tiny decoder and fast decoding.
  • Files that go back into Blender or Unity: Draco works in all recent Blender versions, while Meshopt needs Blender 5.2 or later. In Unity, glTFast imports both through its optional Draco and meshopt packages.
  • Godot: neither, unless you add a plugin.

Either way, geometry is only half the story. Textures are often the larger part of a GLB, and neither format touches them. See how to reduce GLB file size for the full checklist.

FAQ

Is Meshopt better than Draco?

Neither is better overall. Draco usually gives smaller geometry before transport compression; Meshopt decodes much faster, has a far smaller decoder, compresses animation and morph targets, and closes much of the size gap once the file is served with gzip or Brotli.

What is meshoptimizer?

meshoptimizer is an open-source C/C++ library by Arseny Kapoulkine that optimizes meshes for GPU rendering and compresses vertex and index data. Its codecs power the EXT_meshopt_compression glTF extension, and its companion tool gltfpack optimizes whole glTF files.

Can I convert a Draco GLB to Meshopt?

Yes. Run npx @gltf-transform/cli meshopt draco.glb meshopt.glb; gltf-transform decodes the Draco data and re-encodes it. gltfpack cannot read Draco files. Because both formats are lossy, converting from the original uncompressed model gives slightly better quality.

Does Draco or Meshopt make my model render faster?

No. Both are decoded before the data reaches the GPU, so they cut download size, not frame time. To render faster, reduce draw calls and triangle counts by merging meshes, instancing and simplifying.

What is the difference between EXT_meshopt_compression and KHR_meshopt_compression?

KHR is the Khronos-standardized successor. It accepts the EXT bitstream as "version 0" and adds a version 1 format with better vertex compression and a color filter. Fewer loaders support it so far, so EXT remains the safer default.

Related articles