经纬志
首页 / three.js / 教程

模型加载

先看结论

建筑、设备、桥梁这些精细内容通常来自建模软件,不会用代码拼出来。 three.js 读取 glTF 最省事,但尺寸、朝向、原点和材质几乎每次都要手动校正。

最后验证 2026-11-03 · three.js 0.160.0 · 更新 2026-10-27 · 引用本文

格式选择

格式加载器说明
glTF / GLBGLTFLoader首选。文本 JSON 加外部资源,或单文件 GLB,PBR 材质完整
Draco 压缩 glTFGLTFLoader + DRACOLoader几何压缩率高,需要额外的解码器文件
KTX2 压缩纹理KTX2Loader纹理显存占用低,需要转码器并检测渲染器支持
FBXFBXLoader建模软件交换常见,体积大、材质需要转换
OBJOBJLoader仅几何,材质需另配 MTLLoader

面向 Web 交付时,优先让建模方导出 glTF,并在导出前处理掉隐藏对象、重复材质和过大的贴图。 在浏览器里做这些清理,代价远高于在源头处理。

加载与压缩解码

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

const draco = new DRACOLoader();
// 解码器与 three 版本保持一致,路径指向 examples/jsm/libs/draco/
draco.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/');

const ktx2 = new KTX2Loader();
ktx2.setTranscoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis/');
ktx2.detectSupport(renderer);

const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.setKTX2Loader(ktx2);

loader.load(
  '/models/block-a.glb',
  function (gltf) {
    const model = gltf.scene;
    model.traverse(function (obj) {
      if (obj.isMesh) {
        obj.castShadow = true;
        obj.receiveShadow = true;
      }
    });
    scene.add(model);
  },
  function (event) {
    if (event.total) {
      console.log('加载进度', Math.round(event.loaded / event.total * 100) + '%');
    }
  },
  function (error) {
    console.error('模型加载失败', error);
  }
);

进度回调只有在服务器返回 Content-Length 时,event.total 才有值。 静态服务器默认会给,对象存储或经过压缩传输时可能被去掉,代码要做兜底。

三件必做的校正

一、尺寸归一

不同建模软件的单位可能是米、厘米、英寸或无量纲。先量出包围盒, 再按目标尺寸统一缩放,比猜测导出单位可靠。

const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
const maxDimension = Math.max(size.x, size.y, size.z);
const targetMeters = 120;
const scale = targetMeters / maxDimension;

model.scale.setScalar(scale);

// 缩放后重新量一次,把模型底面中心移到原点
box.setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
model.position.x -= center.x;
model.position.z -= center.z;
model.position.y -= box.min.y;

二、朝向与坐标系

glTF 规范是 Y 轴向上、Z 轴向前,和 three 一致。但来自 BIM 或测绘的模型常是 Z 轴向上, 导入后建筑会「躺倒」。如果确认源数据是 Z 向上,在模型外层套一个组并绕 X 轴转 −90 度, 不要直接改模型内部节点的旋转,那样会破坏动画与子节点层级。

const holder = new THREE.Group();
holder.rotation.x = -Math.PI / 2; // Z 向上转为 Y 向上
holder.add(model);
scene.add(holder);

三、原点与浮点精度

模型原点常在建模软件的世界原点,而真实项目坐标可能是几百万米的投影坐标。 把这样的绝对坐标直接塞进 Float32 的顶点属性,会丢掉低位有效数字, 表现为模型抖动、面片错位。

正确做法是先把模型顶点相对某个局部原点归零,再用组的位置承载绝对位置; 或者干脆把整个项目的原点平移到场地中心,让坐标落在几千米范围内。

材质与网格整理

加载后的模型常带着几十个材质和上百个网格,直接影响绘制调用数。 如果只需要统一的体块效果,可以合并网格、替换为单一材质;需要保留贴图时, 优先合并使用同一材质的网格。

阴影也要按需开启。traverse 里无差别设置 castShadow,会让大量小构件参与阴影渲染,帧率下降得很明显。 只给主体建筑开投影,细部构件接收阴影即可。

动画与克隆

glTF 可能带骨骼动画,动画数据在 gltf.animations,用 AnimationMixer 播放。要复制一个带骨骼的模型,不能用 model.clone(),那样骨骼绑定会共享,必须用 SkeletonUtils.clone(model), 它来自 three/addons/utils/SkeletonUtils.js。

常见问题

模型全黑

场景没有灯光,或模型材质的 metalness 为 1 而没有环境贴图。 先用 HemisphereLight 加一盏平行光验证,再决定是否需要环境贴图。

模型巨大或几乎看不见

导出单位不一致。用包围盒量出实际尺寸,再按目标尺寸缩放; 不要靠肉眼拖相机去「凑」。

加载 404 或跨域

path 要按页面地址解析,不要假设站点部署在根目录。跨域时检查响应头 Access-Control-Allow-Origin,以及 Draco 解码器目录是否可访问。

glTF 不是 3D Tiles。glTF 适合单体模型,一次加载整份数据; 覆盖整个城区的倾斜摄影或 BIM 应当用 3D Tiles 流式加载, 按屏幕空间误差逐级取瓦片。用 three.js 手动实现这套调度,工作量远超加载一个模型。

引用本文

经纬志(2026)。《模型加载》。https://www.jingweizhi.com/three/tutorial/models.html(访问日期 2026-11-03)。