首页 / Three.js 入门教程 / glTF 模型加载

Three.js 入门教程

glTF 模型加载

本教程共 40 篇 · 第 28 篇 · 更新于 2026-08-14 · 约 8 分钟阅读

Three.jsglTFGLTFLoader模型加载Dracoglb3D模型

本节目标:认识 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.camerasgltf.scenesgltf.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)。