Load gltfpack models in Three.js
An optimized GLB can need more than GLTFLoader alone. This guide covers the loader setup for this tool’s Meshopt and KTX2 output. It does not choose compression settings; use the result panel to see which extensions your exported file requires.
Install matching dependencies
Use this example in an npm project with an ES-module bundler, such as an existing Vite application. The versions below match this site’s viewer. Bare imports do not work by simply opening the JavaScript file in a browser.
npm install [email protected] [email protected]
Copy the Basis transcoder files from the installed Three.js package into your public directory. With a conventional public directory served at the site root, the files below will be available under /basis/. Keep them from the same Three.js version as KTX2Loader.
node -e "require('fs').cpSync('node_modules/three/examples/jsm/libs/basis', 'public/basis', {recursive:true})"
Connect the decoders before loading
Put the optimized file at public/models/optimized.glb and create a canvas with id model. Use the following as your application entry module. It fits the camera to the loaded bounds and plays the first animation when present.
<canvas id="model"></canvas>
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
import { MeshoptDecoder } from 'meshoptimizer';
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#model'), antialias: true,
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xeeeeee);
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 3));
const light = new THREE.DirectionalLight(0xffffff, 3);
light.position.set(3, 5, 4);
scene.add(light);
const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 1000);
const ktx2 = new KTX2Loader()
.setTranscoderPath('/basis/')
.detectSupport(renderer);
const loader = new GLTFLoader()
.setMeshoptDecoder(MeshoptDecoder)
.setKTX2Loader(ktx2);
function resize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
window.addEventListener('resize', resize);
resize();
try {
await MeshoptDecoder.ready;
const gltf = await loader.loadAsync('/models/optimized.glb');
scene.add(gltf.scene);
const bounds = new THREE.Box3().setFromObject(gltf.scene);
const center = bounds.getCenter(new THREE.Vector3());
const radius = Math.max(bounds.getBoundingSphere(new THREE.Sphere()).radius, 0.01);
const verticalFov = THREE.MathUtils.degToRad(camera.fov);
const horizontalFov = 2 * Math.atan(Math.tan(verticalFov / 2) * camera.aspect);
const distance = 1.2 * radius / Math.sin(Math.min(verticalFov, horizontalFov) / 2);
camera.position.copy(center).add(new THREE.Vector3(1, 0.6, 1).normalize().multiplyScalar(distance));
camera.near = radius / 100;
camera.far = distance + radius * 10;
camera.lookAt(center);
camera.updateProjectionMatrix();
const mixer = new THREE.AnimationMixer(gltf.scene);
if (gltf.animations.length) mixer.clipAction(gltf.animations[0]).play();
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
mixer.update(Math.min(clock.getDelta(), 0.1));
renderer.render(scene, camera);
});
} catch (error) {
console.error('Model loading failed:', error);
const message = document.createElement('p');
message.textContent = 'Model loading failed. Check the browser console and network requests.';
document.body.prepend(message);
}
Check the extension and the failed request
| Symptom | Check |
|---|---|
| Meshopt decoder error | Call setMeshoptDecoder before loadAsync, and keep the meshoptimizer package in the bundle. |
| KTX2 textures do not load | Set KTX2Loader on GLTFLoader, call detectSupport with the renderer, and check the transcoder URL. |
| HTML returned instead of a model or WASM file | A missing file may be falling through to your application page. Check HTTP status, response content and deployment base paths. |
On a site with a strict Content Security Policy, check the console for blocked Web Workers or WebAssembly. Configure your application’s worker and WASM delivery deliberately; do not remove the policy just to hide an error. A successful network response alone does not confirm that decoding succeeded.
The example installs both decoders so it can load either output. Files that keep PNG or JPEG textures do not need KTX2Loader. WebP needs browser and loader support but does not use the Basis transcoder. In an application with repeated model changes, dispose of old geometry, materials and textures; release the KTX2 loader when it is no longer needed.
Three.js GLTFLoader · Three.js KTX2Loader
Check other output compatibility requirements →
Engine reference: gltfpack
Open the optimizer ↗