在 Three.js 中加载 gltfpack 模型
优化后的 GLB 有时不能只靠 GLTFLoader 加载。本文说明本站 Meshopt 与 KTX2 输出所需的加载配置,不讨论压缩参数选择。请先查看结果面板,确认文件使用了哪些扩展。
安装匹配的依赖
示例用于支持 ES 模块打包的 npm 项目,例如已有的 Vite 应用。下面的版本与本站查看器一致,不能把带裸模块导入的 JavaScript 文件直接在浏览器中打开。
npm install [email protected] [email protected]
将已安装的 Three.js 包中的 Basis 转码文件复制到公共目录。如果 public 目录映射到网站根路径,这些文件会通过 /basis/ 提供。转码文件应与 KTX2Loader 来自同一 Three.js 版本。
node -e "require('fs').cpSync('node_modules/three/examples/jsm/libs/basis', 'public/basis', {recursive:true})"
加载前配置解码器
把优化后的文件放到 public/models/optimized.glb,并创建 id 为 model 的 canvas。下面的代码可作为应用入口模块,按模型边界设置相机,存在动画时播放第一段动画。
<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);
}
根据扩展和失败请求排查
| 现象 | 检查项 |
|---|---|
| Meshopt 解码器报错 | 在 loadAsync 前调用 setMeshoptDecoder,并确保打包时包含 meshoptimizer。 |
| KTX2 纹理无法加载 | 将 KTX2Loader 设置给 GLTFLoader,用 renderer 调用 detectSupport,并检查转码器地址。 |
| 模型或 WASM 请求返回 HTML | 文件缺失时,请求可能回退到了应用首页。检查 HTTP 状态、响应内容和部署路径。 |
如果网站使用严格的 Content Security Policy,检查控制台是否拦截了 Web Worker 或 WebAssembly。应针对应用配置 worker 和 WASM 的提供方式,不要为了消除报错直接移除策略。请求成功也不表示解码一定成功。
示例同时配置两种解码器,便于加载不同输出。保留 PNG 或 JPEG 的文件不需要 KTX2Loader;WebP 需要浏览器和加载器支持,但不使用 Basis 转码器。应用反复更换模型时,还应释放旧几何、材质和纹理,不再使用 KTX2 加载器时也应释放。
Three.js GLTFLoader · Three.js KTX2Loader
引擎参考: gltfpack
打开优化工具 ↗