babylonjs模型加载
# 前言
想象你正在搭建一个乐高城堡——你需要先了解积木的种类(基础概念),学会用基础块搭建简单结构(内置几何体),再尝试组合复杂的预制组件(外部模型)。Babylon.js的模型加载就像这个过程:从简单的基础形状开始,逐步掌握加载和控制复杂3D模型的能力。
# 什么是模型加载
在3D开发中,"模型加载"指的是将3D模型数据(如形状、颜色、材质等)加载到场景中的过程。就像拍照需要"导入照片"到手机,3D场景也需要"导入模型"才能展示丰富的内容。没有模型加载,场景就只是一个空房间;有了模型加载,才能创建出游戏角色、建筑、家具等各种3D元素。
# Babylon.js的两种模型加载方式
加载方式 | 特点 | 适用场景 | 类比 |
---|---|---|---|
内置几何体 | 用代码生成基础形状(立方体、球体等) | 学习入门、简单原型 | 用乐高基础块直接搭建 |
外部模型加载 | 加载外部文件(如GLB、glTF) | 复杂场景、生产环境 | 导入预制的乐高组件 |
这里我们只讲加载外部模型,因为内置几何体我已经会了。
# 使用SceneLoader加载外部模型
SceneLoader是Babylon.js加载外部模型的"主力工具",最常用的方法是ImportMesh,语法如下:
BABYLON.SceneLoader.ImportMesh(
meshNames, // 要加载的网格名称(null加载所有)
rootUrl, // 文件根目录路径
sceneFilename,// 模型文件名
scene, // 目标场景
onSuccess, // 加载成功回调函数
onProgress, // 加载进度回调(可选)
onError // 加载失败回调(可选)
);
2
3
4
5
6
7
8
9
示例:加载GLB模型并显示
// 在基础场景代码后添加:加载外部模型
BABYLON.SceneLoader.ImportMesh(
null, // 加载所有网格
"./", // 模型文件所在目录(当前目录)
"myModel.glb", // 模型文件名
scene, // 加载到我们创建的场景
// 加载成功回调函数:model是加载的结果
(meshes, particleSystems, skeletons, animationGroups) => {
console.log("模型加载成功!");
// 1. 获取加载的模型(meshes是网格数组,第一个通常是根节点)
const model = meshes[0];
// 2. 调整模型位置、旋转和缩放(根据需要)
model.position.y = 0; // 放在场景中心
model.scaling = new BABYLON.Vector3(0.5, 0.5, 0.5); // 缩小到50%(避免太大)
// 3. 可选:播放模型动画(如果模型包含动画)
if (animationGroups.length > 0) {
animationGroups[0].play(true); // 播放第一个动画,循环播放
}
},
// 加载进度回调(可选)
(event) => {
const progress = (event.loaded / event.total) * 100;
console.log(`加载进度:${progress.toFixed(1)}%`);
},
// 加载失败回调(可选)
(error) => {
console.error("模型加载失败:", error);
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 关键步骤解释:
- meshes数组:包含模型的所有网格(Mesh),通常第一个元素是根节点,控制整个模型的变换;
- position/rotation/scaling:调整模型在场景中的位置、旋转和大小,解决模型"太大""位置不对"等问题;
- 回调函数:由于模型加载是异步操作(就像点外卖,不会立即送到),需要通过回调函数处理加载完成后的逻辑。
异步加载概念:为什么需要回调函数?
初学者常困惑:"为什么不直接在ImportMesh后写模型操作代码?"
这涉及"同步"与"异步"的概念:
- 同步操作:按顺序执行,上一步完成后才执行下一步(如煮面条:烧水→下面→加调料);
- 异步操作:发起操作后不等待结果,继续执行后续代码(如点外卖:下单后继续工作,外卖到了再处理)。
模型加载是异步的(可能需要几百毫秒到几秒),如果直接在ImportMesh后操作模型,此时模型还未加载完成,会导致错误。回调函数就是"外卖到了的通知" ,确保代码在模型加载完成后执行。
# 高级加载技术:从能用到好用的进阶
掌握基础加载后,我们需要学习更专业的技术,解决实际项目中的问题:加载太慢、页面卡顿、内存占用过高……这些技术能让模型加载更流畅、用户体验更好。
# 用async/await优化异步代码:让逻辑更清晰
回调函数虽然能用,但多层嵌套时会导致"回调地狱"(代码缩进越来越深)。ES6的async/await语法能将异步代码写得像同步代码一样清晰:
// 定义异步函数
async function loadModel() {
try {
// 用await等待加载完成,直接获取结果
const result = await BABYLON.SceneLoader.ImportMeshAsync(
null, "./", "myModel.glb", scene
);
// 后续操作直接写在下面,无需嵌套回调
const model = result.meshes[0];
model.position.y = 0;
console.log("模型加载成功!");
} catch (error) {
// 错误处理
console.error("加载失败:", error);
}
}
// 调用异步函数
loadModel();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ImportMeshAsync是ImportMesh的Promise版本,返回一个Promise对象,配合await使用,代码逻辑更直观。
# 加载进度显示:给用户"等待的理由"
大模型加载需要时间,如果没有进度提示,用户可能以为页面卡住了。通过onProgress回调可以实现进度条:
<!-- HTML中添加进度条 -->
<div id="progressBar" style="width: 300px; height: 20px; background: #eee;">
<div id="progress" style="width: 0%; height: 100%; background: #4CAF50;"></div>
</div>
2
3
4
// 加载时更新进度条
BABYLON.SceneLoader.ImportMesh(
null, "./", "myModel.glb", scene,
onSuccess,
(event) => {
// event.loaded:已加载字节数,event.total:总字节数
const progress = (event.loaded / event.total) * 100;
document.getElementById("progress").style.width = `${progress}%`;
// 加载完成后隐藏进度条
if (progress === 100) {
setTimeout(() => {
document.getElementById("progressBar").style.display = "none";
}, 500);
}
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 资源管理:及时"清理"不用的模型
如果场景中需要切换模型(如关卡切换),需及时释放旧模型占用的内存,避免性能下降:
// 定义模型变量,方便后续释放
let currentModel = null;
async function loadNewModel() {
// 如果有旧模型,先释放
if (currentModel) {
currentModel.dispose(true, true); // 释放网格和材质
currentModel = null;
}
// 加载新模型
const result = await BABYLON.SceneLoader.ImportMeshAsync(
null, "./", "newModel.glb", scene
);
currentModel = result.meshes[0];
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispose方法参数:
- 第一个true:释放关联的材质和纹理
- 第二个true:释放关联的骨骼和动画