mcpField
T
threejs-gltf-loading

threejs-gltf-loading

github.com/gamedev-skills开发5.4k6.3k 次安装1 天前更新
代码仓库

关于 threejs-gltf-loading

面向 three.js 开发者的 glTF 与 GLB 模型加载技能,涵盖将 3D 资源加入场景、检查节点层级和播放动画,并说明压缩几何体与纹理的解码流程。

主要功能

  1. 01加载 glTF 和 GLB 模型
  2. 02检查模型节点层级
  3. 03播放并混合动画片段
  4. 04解码 DRACO 和 KTX2 资源
  5. 05验证变更后的 glTF 文件

适用场景

  1. 01将 3D 模型导入 three.js 场景
  2. 02播放蒙皮角色动画
  3. 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。

核心工作流

  1. 为什么使用 glTF。 它是一种传输格式:二进制顶点数据、PBR 材质和动画都已准备好,只需少量解析即可渲染。在 Web 上,优先选择它,而不是 OBJ(没有场景图,也没有动画)或 FBX(体积大)。
  2. 使用 GLTFLoader 加载。 loader.load(url, onLoad, onProgress, onError)。结果 gltf 包含 gltf.scene(Object3D 根节点)、gltf.animations(AnimationClip[])、gltf.cameras 和 gltf.asset。
  3. 将 gltf.scene 添加到场景并调整取景。使用 traverse / getObjectByName 检查层级,找到需要控制的部分。
  4. 使用 AnimationMixer 播放动画。 每个带动画的根节点使用一个混合器;调用 mixer.clipAction(clip).play();每帧使用 mixer.update(delta) 推进动画。
  5. 解码压缩资源。 添加 DRACOLoader(和/或 KTX2Loader + Meshopt),以便加载 DRACO 网格和 KTX2 纹理;将解码器指向对应文件。
  6. 如果项目已有工具,则验证已修改的 glTF 文件。 对每个已修改的 .gltf 或 .glb 运行 gltf-transform validate path/to/model.glb;加载前修复报告的错误。
  7. 验证加载结果 — 记录场景图和 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)、loadAsync
    • LoadingManager 进度条、使用 SkeletonUtils.clone 复用模型,以及导出器指南(应用变换、使用一个干净的根节点),请阅读 references/loaders-and-animation.md。

相关技能

  • threejs-scene-setup — 模型渲染所用的渲染器、相机和循环。
  • threejs-materials-lighting — 让 PBR 模型呈现正确效果的光照/环境。
  • fps-shooter — 一个由 three.js 技能组合而成的 3D 类型。

评论

支持 Markdown