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 三档模型来源
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 / 实例化"三件套的关系。
十、总结
车间"上量不卡"了。明天打开 3D 特效的"最后一道门"——自定义着色器。
明日预告:Day 116 自定义着色器入门——ShaderMaterial 是什么、GLSL 语言基础(顶点着色器 vs 片元着色器)、以及第一个"自写材质":一个会发光的自定义平面。