Skip to main content

模型加载

在真实的 Web3D 项目中(如智慧城市、汽车展厅、Web 游戏或 Spline 等 3D 编辑器应用),绝大部分模型都是由建模师在 Blender、Maya 中制作好,再导出交由前端加载渲染的。

如何高效加载模型,并在加载后对其进行材质替换、动画提取、结构解析,是高级前端图形开发的核心技能。


1. 主流模型格式分类与选型

  • GLTF / GLB(Web3D 的“JPEG”)
    • 特点:现阶段绝对的行业标准。它不仅仅是模型,而是一整个场景的描述。它可以包含网格(Mesh)、材质(PBR)、骨骼动画(Skeleton)、形变动画(Morph Targets)、灯光甚至相机。
    • GLB 是其二进制格式,将贴图、Buffer 数据全部打包在一个文件内,体积更小,网络传输更优。
    • 选型建议:实际商业项目中,95% 以上的情况应首选 GLB
  • OBJ & MTL
    • 特点:古老的纯文本格式。OBJ 只存顶点、法线、UV 坐标,必须配合 MTL 文件才能引入材质贴图。
    • 缺点:文件体积庞大(解析慢),不支持任何动画,不支持现代 PBR 材质(只支持传统 Phong 光照模型)。
    • 选型建议:仅在极简静态模型,或对接老旧工业软件导出数据时使用。

2. 核心代码示例:如何在项目中加载模型?

场景一:加载带压缩的 GLB 模型 (GLTFLoader + DRACOLoader)

为了极大降低模型体积,建模师通常会使用 Draco 算法对模型进行压缩。前端在加载时必须挂载 Draco 解码器(通过 WebAssembly 加速解码)。

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

// 1. 初始化 Loader
const gltfLoader = new GLTFLoader();

// 2. 配置 DRACO 解码器 (WASM 路径通常放在 public 目录下)
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/gltf/");
gltfLoader.setDRACOLoader(dracoLoader);

// 3. 异步加载模型
gltfLoader.load(
"/models/sports_car.glb",
(gltf) => {
const model = gltf.scene;

// 将模型加入场景
scene.add(model);

// 如果模型带动画,提取并播放
if (gltf.animations.length > 0) {
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
// 别忘了在 requestAnimationFrame 中调用 mixer.update(delta)
}
},
(xhr) => {
// 进度回调,可用于实现 Loading 进度条
console.log(`加载进度: ${(xhr.loaded / xhr.total) * 100}%`);
},
(error) => {
console.error("模型加载失败", error);
},
);

场景二:加载老旧的 OBJ + MTL

由于 OBJ 和 MTL 是分离的,必须先加载材质,再将材质传给模型加载器

import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader.js";

const mtlLoader = new MTLLoader();
mtlLoader.setPath("/models/"); // 设置材质贴图的相对路径

mtlLoader.load("building.mtl", (materials) => {
// 1. 预处理材质
materials.preload();

// 2. 将材质挂载到 OBJLoader
const objLoader = new OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath("/models/");

// 3. 加载对应的 OBJ 模型
objLoader.load("building.obj", (object) => {
scene.add(object);
});
});

3. 编辑器实战:模型加载后的“二次加工”

在类似 Spline、PlayCanvas 等在线编辑器,或者“汽车换装展厅”业务中,把模型加载出来仅仅是第一步。我们通常需要遍历模型的层级结构(Scene Graph)进行二次修改:

实战操作:模型遍历与材质替换(汽车换车漆换轮毂)

建模师导出的模型往往是一个层级极深的 Group。我们可以利用 traverse 递归遍历,根据节点的名称(Name)来精准寻找并替换材质。

gltfLoader.load("/models/car.glb", (gltf) => {
const carModel = gltf.scene;

// 递归遍历模型的所有子节点
carModel.traverse((child) => {
// 判断当前节点是否是网格模型 (Mesh)
if (child.isMesh) {
// 1. 开启阴影投射和接收(GLTF 默认不开启阴影)
child.castShadow = true;
child.receiveShadow = true;

// 2. 根据建模师命名的节点名字,精准替换材质
if (child.name === "CarBody") {
// 汽车外壳:替换成红色的车漆材质 (高光感 PBR)
child.material = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 0.8,
roughness: 0.1,
clearcoat: 1.0, // 模拟车漆清漆层
});
} else if (child.name.includes("Wheel")) {
// 轮毂:替换为暗色金属材质
child.material = new THREE.MeshStandardMaterial({
color: 0x333333,
metalness: 1.0,
roughness: 0.5,
});
}

// 3. 释放不需要的原有材质,避免内存泄漏
// 假设原材质不再使用,需调用 dispose
if (child.material.dispose) {
// 注意:实际项目中需判断该材质是否被其他网格复用
}
}
});

scene.add(carModel);
});

4. 性能优化手段

  1. Draco 几何体压缩:通过 WASM 极速解压,模型体积缩减至 1/5,极大提升网络加载速度。
  2. 纹理压缩 (Basis / KTX2):将 PNG/JPG 转换为 GPU 直接支持的压缩格式。不仅减小了下载体积,更重要的是彻底解决了图片解压到 GPU 时导致的显存爆炸问题
  3. 按需加载与 LOD:对于庞大的开放世界,优先加载玩家视野近处的精细模型,远处的建筑使用低模或者延迟加载(配合 requestIdleCallback 避免阻塞主线程)。