glTF 模型加载
本教程共 40 篇 · 第 28 篇 · 更新于 2026-08-14 · 约 8 分钟阅读
本节目标:认识 glTF 格式与 .gltf/.glb 的区别,学会用 GLTFLoader 加载模型、加入场景、自动适配大小,并掌握 Draco 压缩模型的加载方法。
为什么是 glTF
glTF(GL Transmission Format)由 Khronos 组织维护——WebGL、OpenGL 都是这个组织制定的标准。它专为 3D 内容在 Web 上分发而设计,文件小、加载快,被称为”3D 界的 JPEG”,是 three.js 官方推荐的格式。
一个 glTF 文件可以包含:网格、材质、纹理、骨骼、变形目标、动画、灯光、相机,甚至完整场景。美术在 Blender 里做好模型,导出 glTF,网页直接加载。
注意版本:three.js 支持的是 glTF 2.0。glTF 1.0 没有流行起来,three.js 已不再支持。
.gltf 与 .glb
- .gltf:JSON 文本文件。几何数据放在同名的 .bin 二进制文件里,纹理可能是外部图片。可以用文本编辑器打开,方便调试。
- .glb:二进制单文件,模型、几何、纹理全部打包在一起。体积更小、请求更少,正式项目首选。
另外还有 Draco 压缩的变体:几何数据用 Google 的 Draco 算法压缩,文件能小一个数量级,加载时再解压(见下文)。
Tip调试用 .gltf(能打开看内容),上线用 .glb(小、快)。glTF 相关的第三方工具有很多,比如模型预览器、格式转换器,遇到问题先换工具打开模型确认它本身没问题。
GLTFLoader:引入与加载
GLTFLoader 是扩展模块,用 addons 路径引入:
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
回调式加载:
loader.load(
'models/robot.glb', // 1. 文件地址
(gltf) => { /* 2. 加载成功 */ },
(progress) => { /* 3. 加载进度 */ },
(error) => { /* 4. 加载失败 */ }
);
Promise 式加载,配合 async/await 更简洁:
const gltf = await loader.loadAsync('models/robot.glb');
加载结果是一个 gltf 对象,最常用的是这些字段:
gltf.scene:模型根节点(一个 Scene 对象),直接scene.add(gltf.scene)加入你的场景。gltf.animations:动画剪辑数组(第 26 章讲过怎么播放)。gltf.cameras、gltf.scenes、gltf.userData:文件里可能带的相机、多场景、自定义数据。
loader.load('models/robot.glb', (gltf) => {
scene.add(gltf.scene);
});
适配大小与位置
不同软件导出的模型尺度千差万别:有的模型 1 个单位是 1 米,有的导出来整个模型只有 0.01 个单位(官方示例 LittlestTokyo 需要缩放 0.01 倍才能看)。固定套路是用包围盒自动适配:
const model = gltf.scene;
scene.add(model);
// 算出模型的世界包围盒
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
const center = box.getCenter(new THREE.Vector3());
// 缩放到最长边约 2 个单位
const maxSide = Math.max(size.x, size.y, size.z);
model.scale.setScalar(2 / maxSide);
// 让模型中心回到原点附近
box.setFromObject(model); // 缩放后重算
model.position.sub(box.getCenter(new THREE.Vector3()));
Tip模型加载后”看不见”,先试试缩放 1000 倍或 0.001 倍,八成是单位不一致。还不行再看控制台报错和灯光——模型在暗处也会”看不见”。
播放模型自带动画
glTF 文件可以带动画,加载后直接播放(细节回第 26 章):
loader.load('models/bird.glb', (gltf) => {
const model = gltf.scene;
scene.add(model);
const mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play(); // 播第一个剪辑
// 别忘了在动画循环里 mixer.update(delta)
});
Draco 压缩模型
Draco 是 Google 开源的三维几何压缩库。导出 glTF 时勾选 Draco 压缩,文件体积大幅下降,代价是加载时要额外下载解码器来解压。
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
// 1. 创建 Draco 解码器,指向解码器文件目录
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
'https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/libs/draco/gltf/'
);
// r185 起 setDecoderPath 支持传入预置常量对象 DRACO_GLTF_CONFIG(或直接传解码器路径字符串,两种都可用):dracoLoader.setDecoderPath(DRACO_GLTF_CONFIG);
// 2. 告诉 GLTFLoader 用它解码
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.load('models/compressed.glb', (gltf) => {
scene.add(gltf.scene);
});
解码器是 three.js 仓库自带的 JS/WASM 文件(examples/jsm/libs/draco/),上面用的 CDN 路径直接可用。解码器需要跨域加载,本地调试务必起本地服务器,直接双击 HTML 会失败。
可运行示例:加载鹦鹉并播放飞行动画
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>28. glTF 模型加载</title>
<style>
* { margin: 0; padding: 0; }
body { overflow: hidden; background: #1a1a2e; }
canvas { display: block; }
#info {
position: fixed; left: 12px; top: 12px;
color: #fff; font: 14px/1.6 sans-serif;
background: rgba(0, 0, 0, 0.45); padding: 8px 12px; border-radius: 6px;
}
</style>
</head>
<body>
<div id="info">Parrot.glb · 加载后自动适配大小并播放动画 · 拖动旋转视角</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
scene.add(new THREE.AmbientLight(0xffffff, 1));
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(2, 4, 3);
scene.add(dirLight);
const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 1, 3);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.append(renderer.domElement);
new OrbitControls(camera, renderer.domElement);
let mixer;
const loader = new GLTFLoader();
loader.load(
'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r185/examples/models/gltf/Parrot.glb',
(gltf) => {
const model = gltf.scene;
// 自动适配大小:最长边缩放到 2 个单位
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
const maxSide = Math.max(size.x, size.y, size.z);
model.scale.setScalar(2 / maxSide);
scene.add(model);
// 播放模型自带的飞行动画
mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play();
},
undefined,
(error) => console.error('模型加载失败:', error)
);
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
示例用的模型直接取自 three.js 官方仓库(jsdelivr 镜像,跨域可用)。模型本身带飞行动画,加载后 mixer 一接上,鹦鹉就开始扇翅膀。
Note模型、纹理、字体这类资源文件不在 npm 包
three@0.185.0里,需要从 GitHub 镜像(如上面的 jsdelivr gh 地址)或 threejs.org 加载,或下载到自己的服务器。
模型文件动辄几十 MB?先检查压缩:几何用 Draco 或 Meshopt 压缩(上一节刚讲过),纹理转 KTX2 或 WebP,通常能砍掉一大半体积。另外解码器本身(WASM 文件)也是一次下载量,把它放在加载早期或直接打进应用,进度条体验会好一些。
模型复用与清理
同一个模型要出现多次(场景里摆 10 棵树),克隆最省事:
const tree = gltf.scene;
scene.add(tree);
for (let i = 1; i < 10; i++) {
const clone = tree.clone(); // 浅克隆:几何体、材质与原件共享
clone.position.set(i * 2, 0, 0);
scene.add(clone);
}
clone() 是浅克隆:几何体和材质与原件共享,不会复制 GPU 数据,10 棵树只占一份显存。但带蒙皮动画的模型不能直接 clone——骨骼引用会错乱,要用 three/addons/utils/SkeletonUtils.js 里的 clone() 处理。
场景切换或模型不再需要时,记得释放资源:geometry.dispose()、material.dispose()、texture.dispose(),否则显存只增不减。这里先记住”用完释放”的原则,细节后面章节再展开。
加载成功但颜色不对:确认模型导出时的色彩空间设置,three.js 默认把颜色贴图按 sRGB 处理,导出乱了才会偏色。模型表面一闪一闪:多半是两个面重叠(z-fighting),这个要在建模软件里处理,运行时改不了。
加载失败的排查清单
按顺序检查:控制台有没有报错(onError 回调要写上);是不是直接双击 HTML 打开的(file:// 会被浏览器跨域拦截,起个本地服务器);模型是不是太小或太大;有没有灯光;glTF 引用的纹理路径是不是相对模型文件(网络面板看有没有 404)。