# How to Decode Meshoptimizer-Compressed glTF in Three.js or Babylon.js

> Easily decode meshoptimizer compressed glTF models in Three.js or Babylon.js. Learn how to integrate the meshopt_decoder module and accelerate your 3D workflows.

- Repository: [Arseny Kapoulkine/meshoptimizer](https://github.com/zeux/meshoptimizer)
- Tags: how-to-guide
- Published: 2026-07-11

---

**To decode meshoptimizer-compressed glTF files, import the `meshopt_decoder.mjs` WebAssembly module and register it with your loader—Three.js requires explicit registration via `loader.setMeshoptDecoder(MeshoptDecoder)` while Babylon.js automatically detects the global `MeshoptDecoder` object to decompress `EXT_meshopt_compression` buffers at runtime.**

The `zeux/meshoptimizer` repository provides the reference implementation for the `EXT_meshopt_compression` glTF extension, which reduces geometry size through quantization and reordering. Assets compressed using the `gltfpack` CLI tool with the `-c` flag require the project's JavaScript WebAssembly decoder to restore vertex and index buffers before they can be used by WebGL engines. Both Three.js and Babylon.js integrate this decoder, exposing it through slightly different APIs that affect how you initialize your scene loader.

## What the Meshopt Decoder Does

The decoder module at `js/meshopt_decoder.mjs` ships two WebAssembly builds—scalar and SIMD—and automatically selects the fastest implementation supported by the browser. It implements the decompression algorithms defined in the `EXT_meshopt_compression` specification by exposing functions that mirror the C API declared in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h).

The decoder exposes four key functions that the glTF loaders call internally:

- `meshopt_decodeVertexBuffer`: Decompresses attribute buffers such as positions, normals, and UVs.
- `meshopt_decodeIndexBuffer`: Reconstructs triangle index buffers.
- `meshopt_decodeIndexSequence`: Handles arbitrary index sequences for non-triangle data.
- `meshopt_decodeGltfBuffer`: A wrapper that selects the appropriate routine and applies filters (octahedral, quaternion, etc.).

Three.js specifically uses the asynchronous variant `decodeGltfBufferAsync`, which allocates temporary memory in the WebAssembly heap, copies the compressed data, runs the native C implementation, and returns the uncompressed result as a `Uint8Array`.

## Three.js Integration (Version r122+)

Three.js added native support for the extension in r122 and later. You must explicitly provide the decoder instance to the `GLTFLoader` before calling `load()`.

```javascript
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { MeshoptDecoder } from './meshopt_decoder.mjs';

const loader = new GLTFLoader();

// Register the decoder
loader.setMeshoptDecoder(MeshoptDecoder);

loader.load(
    'models/compressed.glb',
    (gltf) => {
        scene.add(gltf.scene);
    },
    undefined,
    (error) => console.error('Failed to load glTF:', error)
);

```

When the loader encounters a `bufferView` containing the `EXT_meshopt_compression` extension, it internally calls `MeshoptDecoder.decodeGltfBufferAsync` to decompress the data before uploading it to the GPU.

## Babylon.js Integration

Babylon.js ships the decoder integration as part of its loaders package. The engine checks for a global `MeshoptDecoder` object automatically, so you only need to import the module before invoking `SceneLoader`.

```javascript
import { MeshoptDecoder } from './meshopt_decoder.mjs';

const canvas = document.getElementById('renderCanvas');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);

// Babylon automatically detects MeshoptDecoder
BABYLON.SceneLoader.Append(
    '',
    'models/compressed.glb',
    scene,
    () => {
        engine.runRenderLoop(() => scene.render());
    }
);

```

The loader implementation in [`packages/loaders/src/glTF/2.0/extensions/extensionMeshoptCompression.ts`](https://github.com/zeux/meshoptimizer/blob/main/packages/loaders/src/glTF/2.0/extensions/extensionMeshoptCompression.ts) checks for the existence of `MeshoptDecoder` and calls `decodeGltfBuffer` to decompress vertex and index data as the glTF file is parsed.

## Complete Working Example

The following HTML module demonstrates how to load a compressed glTF file in either engine. Uncomment the block for the engine you wish to use.

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Meshopt-Compressed glTF Demo</title>
    <script type="module">
        // ------------------------------------------------
        // Three.js Implementation
        // ------------------------------------------------
        import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js';
        import { GLTFLoader } from 'https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/loaders/GLTFLoader.js';
        import { MeshoptDecoder } from './js/meshopt_decoder.mjs';

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        document.body.appendChild(renderer.domElement);
        
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
        camera.position.set(0, 1, 3);

        const loader = new GLTFLoader();
        loader.setMeshoptDecoder(MeshoptDecoder);
        loader.load('model.glb', (gltf) => {
            scene.add(gltf.scene);
            animate();
        });

        function animate() {
            requestAnimationFrame(animate);
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.render(scene, camera);
        }

        // ------------------------------------------------
        // Babylon.js Implementation (comment out Three.js block above to use)
        // ------------------------------------------------
        /*
        import { MeshoptDecoder } from './js/meshopt_decoder.mjs';
        
        const canvas = document.getElementById('renderCanvas');
        const engine = new BABYLON.Engine(canvas, true);
        const scene = new BABYLON.Scene(engine);
        
        // Camera and light setup omitted for brevity
        
        BABYLON.SceneLoader.Append('', 'model.glb', scene, () => {
            engine.runRenderLoop(() => scene.render());
        });
        */
    </script>
</head>
<body style="margin: 0; overflow: hidden;"></body>
</html>

```

## Key Source Files in the Repository

Understanding where the implementation lives helps debug integration issues:

- `js/meshopt_decoder.mjs`: The public JavaScript entry point that loads the WebAssembly module and exposes the `decode*` functions.
- `js/meshopt_decoder.cjs`: CommonJS wrapper for Node.js or bundler environments.
- [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h): C API header used by the WebAssembly build; defines the native functions that perform the actual decompression.
- [`gltf/README.md`](https://github.com/zeux/meshoptimizer/blob/main/gltf/README.md): Official documentation for generating compressed glTF files using `gltfpack -c` and decoding them in browsers.
- [`gltf/gltfpack.cpp`](https://github.com/zeux/meshoptimizer/blob/main/gltf/gltfpack.cpp): Source of the command-line tool where the `KHR_meshopt_compression` extension data is written into the glTF file.
- [`packages/loaders/src/glTF/2.0/extensions/extensionMeshoptCompression.ts`](https://github.com/zeux/meshoptimizer/blob/main/packages/loaders/src/glTF/2.0/extensions/extensionMeshoptCompression.ts) (Babylon.js): Shows how the Babylon loader detects the global decoder and invokes `decodeGltfBuffer`.

## Summary

- Generate compressed assets using `gltfpack -c` (or `-cc` for higher compression) to create `EXT_meshopt_compression` glTF files.
- Load the `meshopt_decoder.mjs` module before loading any compressed scenes; it requires a modern browser with WebAssembly support.
- **Three.js**: Call `loader.setMeshoptDecoder(MeshoptDecoder)` on your `GLTFLoader` instance before calling `load()`.
- **Babylon.js**: Ensure `MeshoptDecoder` is available as a global import or script; the engine automatically uses it when parsing compressed buffer views.
- The decoder allocates temporary WASM memory during decompression, but the resulting buffers are standard `Uint8Array` objects suitable for WebGL upload.

## Frequently Asked Questions

### How do I know if a glTF file uses meshoptimizer compression?

Check the `extensionsUsed` array in the glTF JSON for `EXT_meshopt_compression`. If present, the file contains compressed buffer views that require the decoder. Standard glTF loaders will fail to load these files without the decoder registered.

### Can I use the decoder without a module bundler?

Yes. You can load the decoder via a standard `<script type="module">` tag that imports `meshopt_decoder.mjs`, or use a UMD build if available from a CDN. The key requirement is that the `MeshoptDecoder` object must be available in the scope where the glTF loader executes, either as a global variable or imported module.

### What is the performance cost of decoding at runtime?

The WebAssembly decoder is highly optimized and typically decompresses geometry faster than the network time saved by downloading smaller files. The operation runs synchronously (or asynchronously via `decodeGltfBufferAsync` in Three.js) on the main thread, but for most assets the time is negligible compared to shader compilation and texture upload.

### Does this work with legacy browsers?

No. The decoder requires WebAssembly support, which is not available in older browsers like Internet Explorer. To support legacy environments, generate uncompressed glTF files using `gltfpack` without the `-c` flag, or provide fallback assets that exclude the `EXT_meshopt_compression` extension.