关于 threejs-gltf-loading
面向 three.js 开发者的 glTF 与 GLB 模型加载技能,涵盖将 3D 资源加入场景、检查节点层级和播放动画,并说明压缩几何体与纹理的解码流程。
主要功能
- 01加载 glTF 和 GLB 模型
- 02检查模型节点层级
- 03播放并混合动画片段
- 04解码 DRACO 和 KTX2 资源
- 05验证变更后的 glTF 文件
适用场景
- 01将 3D 模型导入 three.js 场景
- 02播放蒙皮角色动画
- 03查找并控制模型的指定部件
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill threejs-gltf-loading安装后在 Claude Code 中输入/threejs-gltf-loading确认已加载。
附带文件references/loaders-and-animation.md
详细介绍
three.js glTF 加载
在 three.js 中加载 .gltf/.glb 模型并播放其动画,包括压缩几何体(DRACO/Meshopt)和纹理(KTX2)。这些模式以
r186 为目标;除非请求迁移,否则保留现有项目锁定的版本。
适用场景
- 导入 3D 模型、将其添加到场景、检查其节点层级,并使用
AnimationMixer播放烘焙或蒙皮动画片段时使用。 - 文件为
.gltf/.glb,或代码从three/addons/loaders/...导入GLTFLoader/DRACOLoader/KTX2Loader时使用。
不适用场景: 创建渲染器/摄像机/循环 → threejs-scene-setup。
调整已加载模型的表面外观、灯光或阴影 →
threejs-materials-lighting。模型本身的制作/导出(Blender)不在此范围内;运行时优先使用 glTF,而非 OBJ/FBX。
核心工作流
- 为什么使用 glTF。 它是一种传输格式:二进制顶点数据、PBR 材质和动画都已准备好,只需少量解析即可渲染。在 Web 上,优先选择它,而不是 OBJ(没有场景图,也没有动画)或 FBX(体积大)。
- 使用
GLTFLoader加载。loader.load(url, onLoad, onProgress, onError)。结果gltf包含gltf.scene(Object3D根节点)、gltf.animations(AnimationClip[])、gltf.cameras和gltf.asset。 - 将
gltf.scene添加到场景并调整取景。使用traverse/getObjectByName检查层级,找到需要控制的部分。 - 使用
AnimationMixer播放动画。 每个带动画的根节点使用一个混合器;调用mixer.clipAction(clip).play();每帧使用mixer.update(delta)推进动画。 - 解码压缩资源。 添加
DRACOLoader(和/或KTX2Loader+ Meshopt),以便加载 DRACO 网格和 KTX2 纹理;将解码器指向对应文件。 - 如果项目已有工具,则验证已修改的 glTF 文件。 对每个已修改的
.gltf或.glb运行gltf-transform validate path/to/model.glb;加载前修复报告的错误。 - 验证加载结果 — 记录场景图和
gltf.animations,并确认模型可见(比例正确、光照正常)且动画片段确实在播放。
模式
1. 加载模型并调整取景
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load(
'assets/robot.glb',
(gltf) => {
const root = gltf.scene;
scene.add(root);
// Inspect: gltf.animations is an array of AnimationClip.
console.log('clips:', gltf.animations.map((c) => c.name));
},
(event) => console.log(`${(event.loaded / event.total) * 100}% loaded`),
(error) => console.error('glTF load failed:', error)
);
2. 使用 AnimationMixer 播放蒙皮动画
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
let mixer; // declare outside so the loop can see it
const timer = new THREE.Timer(); // THREE.Clock is deprecated since r183
new GLTFLoader().load('assets/character.glb', (gltf) => {
scene.add(gltf.scene);
mixer = new THREE.AnimationMixer(gltf.scene); // one mixer per animated root
const clip = THREE.AnimationClip.findByName(gltf.animations, 'Run')
?? gltf.animations[0];
mixer.clipAction(clip).play();
});
renderer.setAnimationLoop((time) => {
timer.update(time);
const dt = timer.getDelta();
if (mixer) mixer.update(dt); // advance the animation by real seconds
renderer.render(scene, camera);
});
3. 在两个动画片段之间交叉淡入淡出
const actions = {};
mixer = new THREE.AnimationMixer(gltf.scene);
for (const clip of gltf.animations) {
actions[clip.name] = mixer.clipAction(clip);
}
actions['Idle'].play();
function transitionTo(name, duration = 0.3) {
const next = actions[name];
next.reset().play();
for (const [n, action] of Object.entries(actions)) {
if (n !== name) action.crossFadeTo(next, duration, false);
}
}
4. DRACO 压缩几何体
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const draco = new DRACOLoader();
// Point at the decoder files you ship (or a pinned CDN copy of the same version).
draco.setDecoderPath('https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/draco/');
const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.load('assets/city-draco.glb', (gltf) => scene.add(gltf.scene));
5. 查找并为命名部件添加动画
new GLTFLoader().load('assets/car.glb', (gltf) => {
scene.add(gltf.scene);
const wheels = [];
gltf.scene.traverse((node) => {
if (node.name.startsWith('Wheel')) wheels.push(node);
});
renderer.setAnimationLoop((time) => {
timer.update(time);
const dt = timer.getDelta();
for (const w of wheels) w.rotation.x += dt * 4;
renderer.render(scene, camera);
});
});
常见问题
- 模型已加载但不可见 → 模型使用了受光照影响的(PBR)材质,而场景没有灯光或环境。添加灯光或设置
scene.environment(参见threejs-materials-lighting),并检查比例 — glTF 使用米为单位,因此缩放为 0.01 的资源会非常小。 load是异步的 →gltf只在回调中存在;在外部声明mixer/引用并在回调中赋值,或使用await loader.loadAsync(url)。- 动画始终不动 → 你没有在每帧调用
mixer.update(delta),或传入了毫秒而不是秒(在timer.update(time)后使用timer.getDelta()),或忘记调用action.play()。 - DRACO/KTX2 模型加载失败 → 解码器/转码器路径错误,或版本不匹配。
setDecoderPath/setTranscoderPath必须指向与 three.js 版本匹配的文件。 - 多个混合器相互冲突 → 每个带动画的根节点使用一个
AnimationMixer,并从中创建所有动作;不要为每个片段创建新的混合器。 - 烘焙的变换带来意外 → 导出器有时会将缩放/旋转烘焙到子节点上。依赖节点的局部变换前,先输出层级(名称 + 位置/旋转/缩放);如果骨架无法使用,请从源文件重新导出。
- 原点不正确 → 将部件重新挂到新的
Object3D下,以获得干净的枢轴点,而不是费力处理烘焙偏移。
参考资料
- 如需了解完整的解码/转码设置(同时使用 DRACO、Meshopt 和 KTX2)、
loadAsyncLoadingManager进度条、使用SkeletonUtils.clone复用模型,以及导出器指南(应用变换、使用一个干净的根节点),请阅读references/loaders-and-animation.md。
相关技能
threejs-scene-setup— 模型渲染所用的渲染器、相机和循环。threejs-materials-lighting— 让 PBR 模型呈现正确效果的光照/环境。fps-shooter— 一个由 three.js 技能组合而成的 3D 类型。
评论
支持 Markdown