Communitygithub.com

zipang/Three.js 与 glTF Web管线

优化3D资产(.glb和.gltf),并将其集成到基于Three.js、React Three Fiber、GSAP和gltf-transform的交互式网页动画中。用于压缩(Draco、Meshopt、KTX2)、轴心与动画设置以及Web渲染。

Three.js 与 glTF Web管线 是什么?

该技能详细描述了从Blender或vcad模型转换为小巧、加载快速且易于在Three.js中动画化的.glb文件的完整流程。它涵盖了三部分核心内容:首先是3D建模的导出规范,强调节点命名、轴心设置、变换清零和几何体预算控制,确保模型在引擎中行为稳定;其次是使用@gltf-transform/cli工具进行命令行优化,包括Meshopt(适用于动画资产,解码快且支持关键帧压缩)和Draco(适用于密集静态网格,压缩率更高)两种压缩方案的选择,以及纹理压缩(WebP、KTX2/ BasisU)和去重步骤;最后是Three.js集成,提供了带Draco和Meshopt解码支持的健壮加载器,并对比了程序化动画(基于节点变换)和预烘焙动画(基于AnimationMixer)两种方法,同时给出了PBR金属材质的环境贴图设置和性能优化建议。该技能支持Bun、Node.js及所有主流Three.js生产管线,适用于制作机器人、车辆等可交互的3D网页应用。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/zipang/bender-agent/tree/HEAD/.agents/skills/threejs-web

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Three.js 与 glTF Web管线 是做什么的?

本技能涵盖将 Blender 或 vcad 模型转换为小巧 .glb 文件的步骤。该文件必须加载快速且在 Three.js 中易于动画化。

1. Blender 与 vcad 的导出核对清单

节点命名

  • 为每个可动部件赋予清晰且稳定的名称:Head, Visor, Eye_L, Eye_R, Torso_Door, Wheel_FL, Wheel_FR, Steering_Wheel。
  • Three.js 随后通过 model.getObjectByName("Torso_Door") 找到该部件。

轴心

  • 将根部原点置于模型底座中心。在 Three.js 中,模型将因此坐在地面的 Y = 0 处。
  • 将每个关节部件的轴心放在其旋转轴上。例如:门铰链、车轮中心、颈部底座、天线底座。

变换

  • 导出前应用所有变换。
  • 缩放必须为 1, 1, 1。静止旋转必须为 0, 0, 0。
  • 此规则可防止程序化动画中出现偏移和剪切。

几何体预算

  • 合并共享同一材质的静态网格。此步骤可减少绘制调用。
  • 仅当脚本需要单独动画化某个网格时才保持其分离。对于柔体变形,请使用带有骨骼的 SkinnedMesh。
  • 主要资产目标为数千个三角形。背景道具目标低于 1000 个三角形。

2. 使用 @gltf-transform/cli 进行命令行优化

项目使用 Bun。通过 bunx 运行该工具。npx 在安装了 Node.js 的机器上同样有效。

# 显示网格、纹理和动画
bunx @gltf-transform/cli inspect model.glb

# 针对 Web 进行全面优化:Meshopt + WebP + 清理
bunx @gltf-transform/cli optimize model.glb model-optimized.glb \
  --compress meshopt \
  --texture-compress webp \
  --texture-size 2048

# 备选:Draco 几何压缩
bunx @gltf-transform/cli draco model.glb model-draco.glb

Meshopt 或 Draco

  • Meshopt (EXT_meshopt_compression) 解码极快。它保留顶点顺序,同时压缩动画关键帧和形态目标。适用于动画资产。
  • Draco (KHR_draco_mesh_compression) 对密集静态网格提供最小文件体积。例如:汽车车身和扫描模型。解码器在启动时消耗更多时间。

其他有用的步骤

# 移除重复网格和未使用数据
bunx @gltf-transform/cli dedup model.glb model.glb

# 将纹理转换为 KTX2 (BasisU)
bunx @gltf-transform/cli etc1s model.glb model-ktx2.glb

每一步后检查文件。确认文件大小、三角形数量和纹理尺寸。

3. Three.js 集成

支持 Draco 和 Meshopt 的健壮加载器

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.7/');

const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
gltfLoader.setMeshoptDecoder(MeshoptDecoder);

const gltf = await gltfLoader.loadAsync('/models/bender-optimized.glb');
const root = gltf.scene;
scene.add(root);

两种动画方法

  1. 使用节点变换的程序化动画。 此方法适用于机器人、车辆和其他刚性部件。例如:车轮旋转、开合门、头部跟踪、天线摇摆。
const head = root.getObjectByName('Head');
const door = root.getObjectByName('Torso_Door');
const antenna = root.getObjectByName('Antenna');

function animate(time) {
  if (antenna) {
    antenna.rotation.z = Math.sin(time * 0.005) * 0.15;
  }
  if (head) {
    head.rotation.y = THREE.MathUtils.lerp(head.rotation.y, targetYaw, 0.1);
  }
}
  1. 使用 THREE.AnimationMixer 的预烘焙片段。 此方法适用于来自 Blender 的复杂循环:行走、舞蹈和面部变形。
const mixer = new THREE.AnimationMixer(root);
const idleClip = THREE.AnimationClip.findByName(gltf.animations, 'Idle');
if (idleClip) {
  mixer.clipAction(idleClip).play();
}

// 在渲染循环中:
// mixer.update(clock.getDelta());

金属部件的 PBR 光照

当周围没有任何物体时,金属材质(metalness > 0.8)在 Three.js 中会显示为黑色。设置一个环境贴图:

import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const pmremGenerator = new THREE.PMREMGenerator(renderer);
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;

使用 scene.environmentIntensity 调整反射强度。对于金属资产,保持色调映射为 THREE.ACESFilmicToneMapping。

性能规则

  • 每个网格组使用一种材质。每种材质对应一次绘制调用。
  • 重用纹理。将小纹理打包到一个图集中。
  • 设置 renderer.outputColorSpace = THREE.SRGBColorSpace。glTF 加载器会设置纹理色彩空间。
  • 当同一资产出现多次时,使用 THREE.InstancedMesh。
  • 移除模型时释放其资源:遍历场景,然后调用 geometry.dispose() 和 material.dispose()。

相关技能