three.js glTF 加载
加载 .gltf / .glb 模型,包括压缩几何体(DRACO、Meshopt)和纹理(KTX2),并播放其动画片段。代码模式针对 r187;现有项目将保持其固定的 three.js 版本,除非用户要求升级。glTF 是默认的首选运行时格式:OBJ 没有场景图或动画,而 FBX 在浏览器中解析较慢。
使用时机
- 导入模型或角色、检查其节点层次、播放或混合片段、生成多个动画敌人副本,或修复无法显示的模型。
- 文件是
.gltf/.glb,或者代码从three/addons/loaders/...导入GLTFLoader、DRACOLoader或KTX2Loader。 - 不适用于渲染器、相机和循环(
threejs-scene-setup)、模型的材质和光照(threejs-materials-lighting),或 DCC 工具中的建模和导出设置。
工作流程
- 在编写加载代码之前检查文件(当
gltf-transform可用时):npx @gltf-transform/cli inspect model.glb列出网格、扩展(例如KHR_draco_mesh_compression、EXT_meshopt_compression、KHR_texture_basisu)、片段和纹理尺寸。每个列出的压缩扩展都需要附加其解码器。 - 使用
GLTFLoader加载(在异步代码中使用loadAsync)。结果为gltf.scene(根Object3D)和gltf.animations(一个AnimationClip[])。 - 添加
gltf.scene并为其定界。glTF 单位是米;记录名称和边界以找到代码将控制的部分。 - 每个动画根一个
AnimationMixer,从mixer.clipAction(clip)创建动作,并每帧调用mixer.update(seconds)。 - 附加解码器,用于步骤 1 中找到的扩展。自 r185 起,
DRACOLoader和KTX2Loader会自动找到其捆绑的解码器文件,无需指定路径。 - 验证,参考 验证 部分。
模式
1. 加载并定界模型
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const gltf = await new GLTFLoader().loadAsync('assets/robot.glb');
const root = gltf.scene;
scene.add(root);
const box = new THREE.Box3().setFromObject(root);
console.log('尺寸(米):', box.getSize(new THREE.Vector3()).toArray());
console.log('剪辑:', gltf.animations.map((c) => c.name));
2. 播放片段
const timer = new THREE.Timer(); // THREE.Clock 自 r183 起弃用
const mixer = new THREE.AnimationMixer(gltf.scene);
const run = THREE.AnimationClip.findByName(gltf.animations, 'Run') ?? gltf.animations[0];
mixer.clipAction(run).play();
renderer.setAnimationLoop(() => {
timer.update();
mixer.update(timer.getDelta()); // 秒,不是毫秒
renderer.render(scene, camera);
});
3. 片段间交叉淡入淡出
const actions = Object.fromEntries(gltf.animations.map((c) => [c.name, mixer.clipAction(c)]));
let current = actions.Idle;
current.play();
function transitionTo(name, duration = 0.3) {
const next = actions[name];
if (!next || next === current) return;
next.reset().play(); // 倒带并清除旧的淡出效果
current.crossFadeTo(next, duration, false);
current = next;
}
4. 压缩几何体和纹理
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
const loader = new GLTFLoader()
.setDRACOLoader(new DRACOLoader().setDecoderPath(DRACO_GLTF_CONFIG)) // 仅 glTF 的 WASM 构建(r185+)
.setKTX2Loader(new KTX2Loader().detectSupport(renderer)) // 需要渲染器
.setMeshoptDecoder(MeshoptDecoder);
// 所有模型共享此加载器,以便解码器和工作线程仅创建一次。
在 r185 之前的版本,或当主机不提供捆绑文件时,请从已安装的 three 复制 examples/jsm/libs/draco/ 和 libs/basis/ 文件夹,并将这些文件夹路径传递给 setDecoderPath / setTranscoderPath。
5. 查找指定名称的部分
const wheels = [];
gltf.scene.traverse((node) => {
if (node.name.startsWith('Wheel')) wheels.push(node);
});
// 在循环中:for (const w of wheels) w.rotation.x += dt * 4;
6. 在不丢失代码所需部分的情况下缩小模型
# Meshopt 压缩,WebP 纹理最大 1024 像素,保留命名节点和层次结构。
npx @gltf-transform/cli optimize in.glb out.glb \
--compress meshopt --texture-compress webp --texture-size 1024 \
--flatten false --join-named false
npx @gltf-transform/cli validate out.glb
optimize 默认会展平层次结构并合并网格(包括命名的)。这减少了绘制调用,但查找 Wheel_FL 或动画化门节点的代码将找不到任何内容。对于代码需要访问的任何模型,请保留上述两个标志。输出需要 setMeshoptDecoder(模式 4)。
验证
- 文件检查:对每个新的或更改的文件执行
npx @gltf-transform/cli validate model.glb;在调试加载器代码之前修复报告的错误。 - 构建:
npm run build(或npx tsc --noEmit)通过。 - 运行时探测:将
scene和 mixer 暴露在window上(Object.assign(window, { scene, mixer }))。将此表达式作为字符串传递给 Playwright 的page.evaluate,以便在页面中而非 Node 中运行(烟雾脚本在threejs-scene-setup中有效),或要求用户将其粘贴到浏览器控制台中,前面加上await并报告输出:
(async () => {
const before = window.mixer?.time ?? null;
await new Promise((r) => setTimeout(r, 500));
const meshes = [];
window.scene.traverse((o) => { if (o.isMesh) meshes.push(o.name || o.type); });
return { meshes: meshes.length, mixerAdvanced: before !== null && window.mixer.time > before };
})();
期望网格计数非零且 mixerAdvanced: true,然后检查屏幕截图以获得正确的缩放和方向。
注意事项
- 模型加载成功但未显示 → 可能是 PBR 材质没有光照或环境(参见
threejs-materials-lighting),或比例偏差 100 倍(以厘米为单位导出)。记录模式 1 中的包围盒。 - 回调外部
gltf未定义 →load是异步的。使用loadAsync,或在回调内部分配共享引用。 - 动画从不移动 → 未每帧调用
mixer.update,或者传递了毫秒而非秒,或者未调用play()。 draco_*或basis_*文件返回 404,或解码错误 → 解码器文件未提供,或来自另一个 three.js 版本。提供来自已安装three的副本,并将加载器指向它们。setKTX2Loader must be called before loading KTX2 textures或setMeshoptDecoder must be called before loading compressed files→ 文件使用了KHR_texture_basisu或 meshopt 压缩;附加解码器(模式 4)。.hasFeature() called before the backend is initialized→KTX2Loader.detectSupport在await renderer.init()完成之前在WebGPURenderer上运行。- 两次添加同一模型导致其移动 → 一个
Object3D只能有一个父级。克隆它,对于蒙皮网格使用SkeletonUtils.clone(参见参考资料)。 - 克隆同时动画或仅一个移动 → 简单的
.clone()共享骨骼;使用SkeletonUtils.clone并为每个克隆创建一个单独的 mixer。 - 命名部分在
gltf-transform optimize后消失 → 默认的展平和合并合并了它们。使用--flatten false --join-named false重新运行。 - 烘焙的偏移与运行时控制冲突 → 导出器可能将缩放或旋转烘焙到子节点。首先转储名称和局部变换;将部分在新
Object3D下重新父级以获得干净的轴点,或要求使用应用了变换的重新导出。
参考资料
- 阅读
references/loaders-and-animation.md以了解:用于多个加载器的LoadingManager进度条、使用SkeletonUtils.clone生成多个动画敌人、AnimationAction循环和混合细节、解码器托管说明,以及交给艺术家的导出检查清单。
相关技能
threejs-scene-setup:渲染器、相机和循环。threejs-materials-lighting:光照和环境,使 PBR 模型看起来正确。create-game-assets:简报、采购和检查 3D 资产本身。fps-shooter:一个 3D 游戏类型,建立在这些技能之上。
版本说明
最后验证:2026-11-11,针对 three r187(npm [email protected],2026-10-30 发布)和 @gltf-transform/cli 4.5.1。
- r185:
DRACOLoader和KTX2Loader默认使用其捆绑的解码器文件夹;添加了DRACO_GLTF_CONFIG;DRACOLoader.setDecoderConfig()已弃用(计划在 r194 中移除)。r183:KHR_meshopt_compression与EXT_meshopt_compression一同受支持。r181:KTX2Loader.detectSupportAsync()已弃用。 - gltf-transform 4.5.1
optimize默认值:--compress meshopt、--flatten true、--join true、--join-named true、--simplify true、--texture-compress auto。--texture-compress ktx2需要PATH中有 KTX-Software 4.4.0 或更新版本的ktx命令。 - 来源:r187 发布说明、r187 DRACOLoader.js、r187 GLTFLoader.js、glTF Transform CLI、
npx @gltf-transform/cli optimize --help。