babylonjs模型加载

更新时间: 2025-08-19 14:11:05

# 前言

想象你正在搭建一个乐高城堡——你需要先了解积木的种类(基础概念),学会用基础块搭建简单结构(内置几何体),再尝试组合复杂的预制组件(外部模型)。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       // 加载失败回调(可选)
);
1
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);
  }
);
1
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();
1
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>
1
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);
    }
  }
);
1
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];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

dispose方法参数:

  • 第一个true:释放关联的材质和纹理
  • 第二个true:释放关联的骨骼和动画