模型加载
建筑、设备、桥梁这些精细内容通常来自建模软件,不会用代码拼出来。 three.js 读取 glTF 最省事,但尺寸、朝向、原点和材质几乎每次都要手动校正。
格式选择
| 格式 | 加载器 | 说明 |
|---|---|---|
| glTF / GLB | GLTFLoader | 首选。文本 JSON 加外部资源,或单文件 GLB,PBR 材质完整 |
| Draco 压缩 glTF | GLTFLoader + DRACOLoader | 几何压缩率高,需要额外的解码器文件 |
| KTX2 压缩纹理 | KTX2Loader | 纹理显存占用低,需要转码器并检测渲染器支持 |
| FBX | FBXLoader | 建模软件交换常见,体积大、材质需要转换 |
| OBJ | OBJLoader | 仅几何,材质需另配 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)。