【Three.js】day115-lod-optimization

作者:mario 发布时间: 2026-09-07 阅读量:7 评论数:0

Day 115 · LOD 与渲染优化 — 让"上量"不卡

Day 106 用实例化解决了"重复设备";但 50 台不同的模型设备呢?粒子、辉光、阴影叠加后呢?今天的主题LOD(Level of Detail,细节层次):同一个设备准备"高模/中模/低模"三档,离得近用高模、离得远用低模——人眼根本分不出远处的细节,但 GPU 省了一大半。再补上"可见性剔除"的认知。做完,你的车间在"全效果 + 大量设备"下依然流畅——这是工业大屏"上量"的必答题。


目录


一、LOD 是什么:省掉"看不见"的细节

1.1 一个朴素的观察

你在车间里看一台 3 米外的设备,和看一台 30 米外的设备——30 米外那台的螺丝、管路细节,你根本分不出来。但 GPU 却在为一视同仁地渲染每一台的高模。

LOD 就是"距离越远,细节越少"

距离 0~15m     高模(几万面)    —— 近看要精细
距离 15~40m    中模(几千面)    —— 中景够用
距离 40m+      低模(几百面)    —— 远景只是个"影子"

1.2 工业场景的典型应用

车间俯瞰(远景):50 台设备全用低模——俯瞰谁看得清螺丝?
设备特写(近景):选中/聚焦的设备用高模

二、为什么 LOD 能救性能

3D 渲染的负担 = 面数 × 光照 × 后处理。面数减不下来,其他都白搭:

50 台高模(每台 5 万面)= 250 万面/帧
  ↓ LOD:近处 5 台高模 + 中景 20 台中模 + 远景 25 台低模
50 台混合 ≈ 50 万面/帧(省 80%)

记忆钩子:LOD 是"性能杠杆"——不是砍功能,而是把渲染预算花在人眼真正注意的地方。这是 3D 优化的第一原则:优化"看得见的",别优化"看不见的"


三、THREE.LOD 基本用法

import * as THREE from "three";

// 1. 创建 LOD 对象(它本身是个 Object3D,可加进场景、可拾取)
const lod = new THREE.LOD();

// 2. 按距离添加各级模型:addLevel(模型, 切换距离)
//    距离是"从 LOD 对象到相机的距离"
lod.addLevel(highModel, 0);      // 0m 起用高模
lod.addLevel(midModel, 15);      // 15m 起用中模
lod.addLevel(lowModel, 40);      // 40m 起用低模

// 3. 加进场景
lod.position.set(3, 0, 2);       // LOD 本身可定位/旋转
scene.add(lod);

// 4. 循环里:更新 LOD 的距离判断(关键!)
const tick = () => {
  // LOD 需要知道相机位置来自动切换——每帧更新
  lod.update(camera);            // ← 忘了这行,LOD 永远停在第一级
  renderer.render(scene, camera);
};

最容易忘的一行lod.update(camera)。LOD 不自己知道相机在哪,必须每帧把相机喂给它,它才会按距离切换。忘调 = 永远高模 = LOD 白搭。


四、多级模型从哪来

4.1 三档模型来源

来源

做法

适合

建模软件导出

建模时就做高/中/低三版(GLB)

正式资产(最推荐)

程序降面

代码里用 BufferGeometry 简化/减面

没有低模时的兜底

替身几何

远处用 BoxGeometry/简单位替代高模

极远距离(几乎看不见)

4.2 复用 AssetManager 加载三档

// 扩展 AssetManager:一次加载三档模型(Day 108 的缓存逻辑复用)
async function loadLodModels(urls: { high: string; mid: string; low: string }): Promise<THREE.Object3D[]> {
  const [high, mid, low] = await Promise.all([
    asset.getModel(urls.high),
    asset.getModel(urls.mid),
    asset.getModel(urls.low),
  ]);
  return [high, mid, low];
}

注意:三档模型必须保持同样的"原点/朝向/比例"(都是相对 LOD 原点)——否则切换时设备"跳位"(坑 2)。


五、切换距离:跳变怎么避免

5.1 跳变的本质

LOD 切换瞬间,高模变中模——如果两者的轮廓/颜色差异大,人眼会捕捉到"pop"(跳变)。

5.2 三个防跳变手法

① 各级比例一致:三档模型缩放相同,切换时大小不变
② 切换距离重叠:addLevel 的距离错开/重叠,让切换发生在"看不太清"处
③ 材质一致:三档尽量用同色系材质,切换时颜色不突变
// 示例:距离重叠(15 与 18 之间有一段"过渡带")
lod.addLevel(highModel, 0);
lod.addLevel(midModel, 15);
lod.addLevel(lowModel, 38);   // 与前一级留一点重叠余量

完美防跳变需要"交叉淡入淡出"(WebGPU 时代才有,Day 118 不展开)。本周做到"比例/材质一致"即可——这是性价比最高的防跳变。


六、可见性剔除:GPU 的"眼不见心不烦"

6.1 Three 默认就有的剔除

Frustum Culling(视锥剔除):相机看不见的物体,根本不画
  → Three 默认开启(每个 Object3D 有 frustumCulled = true)
  → 你站在车间一角,背后的设备已经被跳过了

6.2 为什么还要"意识"它

① 粒子/后处理等"全屏"效果不受视锥剔除保护(它们总是全屏算)
② 大型模型如果包围盒不准,会"该剔不剔"
③ 面试高频点:说出"Three 默认视锥剔除 + LOD + 实例化"是性能三件套
// 让某个复杂模型"不被误剔除"(包围盒不准时):
model.geometry.computeBoundingSphere();   // 确保包围球正确

七、实战:50 台设备的 LOD 车间

把 LOD 接入车间引擎(Day 112 的模型加载升级版):

// src/core/three/workshop-with-lod.ts(LOD 装配)
import * as THREE from "three";
import { AssetManager } from "./asset-manager";

/**
 * 带 LOD 的车间装配:
 *   - 每台设备 = 一个 LOD(高/中/低三档)
 *   - 循环里统一 lod.update(camera)
 * 返回:lods(可拾取清单)+ mixers(动画)
 */
export async function buildLodWorkshop(
  layout: { id: string; position: [number, number, number]; lodUrls: { high: string; mid: string; low: string } }[],
  asset: AssetManager
) {
  const lods: THREE.LOD[] = [];

  for (const cfg of layout) {
    // 1. 加载三档模型(缓存复用)
    const [high, mid, low] = await Promise.all([
      asset.getModel(cfg.lodUrls.high),
      asset.getModel(cfg.lodUrls.mid),
      asset.getModel(cfg.lodUrls.low),
    ]);

    // 2. 组装 LOD(比例一致:三档同样缩放)
    const lod = new THREE.LOD();
    [high, mid, low].forEach((m) => m.scale.setScalar(0.01));
    lod.addLevel(high, 0);
    lod.addLevel(mid, 15);
    lod.addLevel(low, 38);

    // 3. 就位 + 上户口(拾取契约不变)
    lod.position.set(...cfg.position);
    lod.userData.deviceId = cfg.id;
    lods.push(lod);
  }
  return lods;
}

// —— 引擎循环里:每帧更新所有 LOD(必做!)——
// lods.forEach((lod) => lod.update(camera));

注意:拾取时 intersectObjects(lods, true) 命中 LOD 对象userData.deviceId 照常可用——LOD 对 Vue/交互层完全透明(边界红利又一次兑现)。


八、常见坑点

坑 1:LOD 不切换(永远高模)

原因:忘了 lod.update(camera)修法:循环里每帧调用(第三节)。

坑 2:切换时设备"跳位/跳大小"

原因:三档模型原点/比例不一致。修法:统一缩放与原点(第五、七节)。

坑 3:三档模型材质不一,切换"变色"

原因:高低模用不同材质。修法:三档尽量同色系材质(第五节)。

坑 4:LOD 拾取拿不到 deviceId

原因:命中的是 LOD 内部子模型,userData 挂在 LOD 根上。修法:intersectObjects(lods, true) 后向上找 obj.userData.deviceId 或遍历父级

坑 5:只给"一台设备"做了 LOD

原因:以为 LOD 是单机优化。修法:LOD 是"批量上量"的杠杆——50 台都要有(第二节)。


九、自测挑战

T1 · 三档 LOD(60 分钟)

用三档模型(或用高模 clone 后简化)组装一个 LOD 设备。放大/缩小视角,观察设备在 0/15/38m 处切换——用 Console 打印当前 level 验证。

T2 · 性能对比(40 分钟)

对比"50 台全高模" vs "50 台 LOD"的帧耗时(Performance 面板),记录差异。这份数据是 Day 118 预算表的又一笔

T3 · 跳变实验(30 分钟)

故意把中模 scale 设为高模的 1.5 倍,观察切换时的"跳大小";再改回来。亲手制造坑 2,记住"比例一致"的必要性

T4 · 剔除观察(20 分钟)

打开相机朝向,观察背后的设备是否被跳过(Performance 面板 draw call 变化)。在笔记里写清"视锥剔除 / LOD / 实例化"三件套的关系


十、总结

环节

要点

LOD 本质

距离越远细节越少——优化"看得见的",别优化"看不见的"

性能杠杆

50 台高模 → 混合档位,面数省 80%

THREE.LOD

addLevel(模型, 距离) + 每帧 lod.update(camera)

防跳变

比例/材质一致 + 切换距离重叠

剔除

Three 默认视锥剔除;粒子/后处理不受保护

三件套

实例化 + LOD + 视锥剔除 = 3D 性能基座

车间"上量不卡"了。明天打开 3D 特效的"最后一道门"——自定义着色器。


明日预告:Day 116 自定义着色器入门——ShaderMaterial 是什么、GLSL 语言基础(顶点着色器 vs 片元着色器)、以及第一个"自写材质":一个会发光的自定义平面。

评论