🌌 构建宇宙不是梦:Three.js 中的多个子场景、LOD 切换与资源统一管理

247 阅读3分钟

作者:一名白天写着渲染管线,晚上梦见三角形的计算机图形学民工
—— 在光照照不到的地方,我们依旧 Debug!


🧠 引子:3D 世界的复杂性

在传统的小游戏开发中,一个 Scene 通吃天下。但当你要渲染一个「城市模拟器」、或者一个拥有不同房间的「博物馆交互场景」时,单个 Scene 处理所有逻辑就像让一只猴子管理一座图书馆 —— 太难了。

我们需要:

  • 多个子场景管理(Sub Scenes):每个子区域可以独立更新、切换、隐藏。
  • LOD(Level of Detail)切换:高模近看精致,远看不浪费资源。
  • 统一的资源管理系统:模型、贴图、纹理、声音……都要有地方查找,有地方清理。

🌍 场景切片:多个子场景的管理方法

🔧 设计思路

把一个大 Scene 拆成多个子场景(Scene Tree),统一通过「主控制器」来调度更新和渲染。

const sceneA = new THREE.Scene();
const sceneB = new THREE.Scene();
const activeScenes = [sceneA]; // 当前激活的子场景

function render() {
  activeScenes.forEach(scene => {
    renderer.render(scene, camera);
  });
}

🧩 子场景的状态管理

function switchToScene(scene) {
  activeScenes.length = 0;
  activeScenes.push(scene);
}

你甚至可以加一个场景状态管理器:

const sceneManager = {
  current: null,
  scenes: new Map(),

  add(name, scene) {
    this.scenes.set(name, scene);
  },
  switch(name) {
    this.current = this.scenes.get(name);
  }
}

🧊 LOD 系统:离得远一点,就别太讲究

LOD(Level of Detail) 的核心目标是:近处看精致、远处看节能

🧰 Three.js 提供的 LOD 机制

const lod = new THREE.LOD();

// 添加不同距离的模型
lod.addLevel(highDetailMesh, 0);     // 离得近时用高模
lod.addLevel(mediumDetailMesh, 50); // 中模
lod.addLevel(lowDetailMesh, 100);   // 远模

scene.add(lod);

它的底层原理并不复杂:在每一帧 update() 时,它会根据相机距离来切换内部的 mesh 显隐。节约 GPU,就靠它。

🤓 想 DIY 更智能的 LOD?

你甚至可以自己写逻辑,比如只在相机看到某区域时才加载:

const distance = camera.position.distanceTo(targetMesh.position);

if (distance < 50 && !scene.children.includes(highDetailMesh)) {
  scene.add(highDetailMesh);
}

📦 资源统一管理:我们不能让纹理乱飞

你需要一个 资源中心(AssetManager) 来:

  • 加载资源
  • 避免重复加载
  • 释放资源内存
  • 跟踪加载进度

🗂️ 简化版 AssetManager

class AssetManager {
  constructor() {
    this.cache = new Map();
    this.loader = new THREE.GLTFLoader();
  }

  async loadGLTF(url) {
    if (this.cache.has(url)) return this.cache.get(url);
    const gltf = await this.loader.loadAsync(url);
    this.cache.set(url, gltf);
    return gltf;
  }

  disposeAll() {
    this.cache.forEach((asset) => {
      asset.scene.traverse((child) => {
        if (child.isMesh) {
          child.geometry.dispose();
          if (child.material.map) child.material.map.dispose();
          child.material.dispose();
        }
      });
    });
    this.cache.clear();
  }
}

💬 使用方式

const assets = new AssetManager();
const model = await assets.loadGLTF('/models/spaceship.gltf');
scene.add(model.scene);

🔁 全流程组合范式

假如我们做一个太空模拟游戏,有三种区域:

  • 空间站(stationScene)
  • 飞船内舱(shipScene)
  • 星球表面(planetScene)

那么你可以用这样的框架结构:

const scenes = {
  station: new THREE.Scene(),
  ship: new THREE.Scene(),
  planet: new THREE.Scene()
};

let activeScene = scenes.station;

function gameLoop() {
  requestAnimationFrame(gameLoop);
  renderer.render(activeScene, camera);
}

切换场景:

function goTo(sceneName) {
  activeScene = scenes[sceneName];
}

资源由统一 AssetManager 管理,每个场景只加载自己需要的资源。


🎭 彩蛋段子:为什么你需要这些系统

如果你不做资源管理,

“你加载的 GLTF 模型如天女散花,一会丢贴图,一会爆内存。”

如果你不做 LOD 切换,

“你的显卡风扇会跑得比你还快。”

如果你不管理子场景,

“你会在 scene.children 中迷失,像在 IKEA 找不到出口。”


🧾 总结

模块技术亮点对应场景
多子场景解耦逻辑,独立更新游戏多个区域、不同房间
LOD 切换性能提升,高低模切换城市漫游、大地图渲染
资源管理避免重复加载、释放内存复杂项目必备,防炸机神器

✨ 推荐拓展阅读