经纬志
首页 / 实战手册 / three.js 渲染

模型加载后只有指甲盖大小

先看结论

一栋 60 米高的楼,导入后和 1 米的地面网格差不多大;换一个模型,又大得把相机包在里面。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

两个模型来自不同软件,分别导成 glTF。A 模型几乎看不见, B 模型把整个场景撑满。用同一套相机参数,无法同时看清两者。 打印包围盒可以看到尺寸差异在 100 倍量级:

// A:0.62 1.87 0.55   (单位为米时,只有 1.8 米高)
// B:62000 187000 55000 (单位是毫米)

原因

glTF 规范只规定单位是米,但导出工具不一定遵守。 Revit、3ds Max 常见导出单位为毫米或厘米; SketchUp 默认按英寸建模再转米;Cesium 导出的 3D Tiles 中也常有厘米单位。 模型文件本身不带“单位倍数”,three.js 只能原样读取顶点坐标。

另一个干扰项是父节点的 scale。直接对已经带缩放的子对象再设 scale,最终尺寸是两者相乘,容易出现 100 倍偏差。

排查过程

  1. 读包围盒尺寸,与设计图上的真实尺寸对比,算出倍率。
  2. 打印模型根节点的 scale,确认是否为 1。
  3. 检查相机 near/far,模型过大时可能整个在 near 之外或 far 之外而被裁掉。
var box = new THREE.Box3().setFromObject(gltf.scene);
var size = box.getSize(new THREE.Vector3());
console.log('包围盒尺寸', size);
console.log('根节点缩放', gltf.scene.scale.toArray());

解决方案

已知单位是厘米或毫米时,直接给一个固定倍率。把它作用在最外层容器上, 避免和模型自身缩放相乘:

var unitScale = 0.01;   // 厘米转米;毫米用 0.001

var holder = new THREE.Group();
holder.add(gltf.scene);
holder.scale.setScalar(unitScale);
scene.add(holder);

不知道单位时,用目标尺寸自动归一。适合批量导入、尺寸不一的资产:

function fitToHeight(object3d, targetHeight) {
  var box = new THREE.Box3().setFromObject(object3d);
  var size = box.getSize(new THREE.Vector3());
  var s = targetHeight / size.y;

  object3d.scale.setScalar(s);

  // 缩放后重新计算,把底面移到 y = 0
  box.setFromObject(object3d);
  object3d.position.y -= box.min.y;
  return s;
}

fitToHeight(gltf.scene, 60);   // 统一成 60 米高

模型过大导致相机看不见时,先用包围盒把相机摆到合适距离, 再核对真实尺寸:

var box = new THREE.Box3().setFromObject(gltf.scene);
var sphere = box.getBoundingSphere(new THREE.Sphere());
var dist = sphere.radius / Math.sin(camera.fov * Math.PI / 360);
camera.position.copy(sphere.center).add(new THREE.Vector3(dist * 1.5, dist, dist * 1.5));
camera.lookAt(sphere.center);

验证

导入后模型高度与设计值一致,误差小于 1%。 同一个 1 米网格放在模型旁边,比例肉眼可判断正确。 相机默认视角下模型完整出现在画面中央,而不是只有一角。

先确认再缩放:在建模软件里量一个已知尺寸的构件, 与包围盒数值相除得到倍率,比反复试 0.01、0.001 更快也更可靠。

引用本文

经纬志(2026)。《模型加载后只有指甲盖大小》。https://www.jingweizhi.com/cookbook/three-gltf-scale.html(访问日期 2026-11-03)。