UE5.8 Nanite 虚拟几何体:把手工 LOD 扔进历史的垃圾桶
引子:老周的岩石资产
美术组长老周记得很清楚,2023 年那个夏天,他花了整整三天给一块英雄级岩石雕 LOD。LOD0 八十三万面,LOD1 二十一万面,LOD2 五万面,LOD3 八千面。每一级都要手动减面、修 UV、烘法线,最后还要在引擎里一个个设置切换距离。
「这块石头很重要,玩家会贴着它走。」策划说。
三个月后,关卡改了,相机距离变了,LOD 切换点全得重调。老周盯着屏幕,第一次认真思考:「为什么我要当一台人肉减面机?」
UE5 给出的答案叫 Nanite。到 5.8 版本,这套系统已经成熟到足以支撑绝大多数静态几何体管线。本文不聊营销话术,只聊它到底怎么工作、什么时候该开、什么时候得关。
一、手工 LOD 的账,算起来很疼
在理解 Nanite 之前,先看看它要取代的东西。
传统 LOD 管线的核心假设是:一个模型在远处不需要那么多面。所以美术要准备多个版本,从精细到粗糙,引擎根据距离切换。这个思路简单直接,但成本悄悄摊在很多地方:
- 美术时间:每个重要资产要做 3-5 级 LOD, photogrammetry 扫描件还要 retopo;
- 内存占用:LOD0 到 LOD3 全要常驻显存,远处物体也不能完全卸载;
- Draw Call:每个 LOD 层级通常对应一个 draw call 或 instance;
- 切换裂缝:LOD 切换点没调好,玩家会眼睁睁看着石头「跳」一下;
- 反复调整成本:相机距离一变,切换距离就得重调。
更麻烦的是,LOD 的粒度是「整个模型」。一块石头上离相机很近的凸起和远处的平面,必须共享同一个 LOD 级别。这意味着要么局部过糊,要么整体浪费。
Nanite 把粒度从「整个模型」降到了「三角形集群」。
二、Nanite 的核心思路:只渲染看得见的三角形
Nanite 不准备离散 LOD。它把原始网格预处理成一组层级化的三角形集群(cluster),每个 cluster 最多 128 个三角形。这些 cluster 组成一棵有向无环图(DAG),父节点是低细节版本,子节点是高细节版本。
flowchart TD
A["原始网格 500 万三角形"] --> B["切分为 Cluster"]
B --> C["构建层级 DAG"]
C --> D["父 Cluster 简化版"]
C --> E["子 Cluster 细节版"]
D --> F["运行时按屏幕大小选择"]
E --> F
F --> G["只渲染可见 Cluster"]运行时,GPU 做三件事:
- 可见性判断:用 HZB(Hierarchical Z-Buffer)剔除被遮挡的 cluster;
- LOD 选择:根据每个 cluster 在屏幕上的投影大小,决定用父节点还是子节点;
- 光栅化与着色:把选中的 cluster 送进 VisBuffer + BasePass。
关键在这里:不同 cluster 可以处于不同细节级别。玩家贴着的凸起用高密度 cluster,远处的平面用低密度 cluster,不需要统一切换。
flowchart TD
subgraph gpu ["GPU"]
hzb["HZB 遮挡剔除"]
lod["Cluster LOD 选择"]
visbuf["VisBuffer 生成"]
basepass["BasePass 材质着色"]
end
subgraph cpu ["CPU"]
importer["导入时构建 Cluster DAG"]
end
importer --> hzb
hzb --> lod
lod --> visbuf
visbuf --> basepass三、VisBuffer:Nanite 的秘密武器
传统 deferred / forward 渲染的流程大致是:CPU 提交 draw call → GPU 光栅化 → 写入 G-Buffer / 直接着色。Nanite 把前面两步改成了 GPU-driven。
3.1 Visibility Buffer
Nanite 使用一种叫 Visibility Buffer 的中间表示。第一步只记录每个像素属于哪个 cluster、哪个三角形、哪个重心坐标,不立即着色。第二步再根据这个信息做延迟的材质着色。
这样做有两个好处:
- 材质解耦:几何处理不需要关心材质复杂度;
- ** overdrawing 减少**:只有最终可见的三角形才会进入 BasePass。
sequenceDiagram
participant C as CPU
participant G as GPU
participant V as "Visibility Buffer"
participant B as BasePass
C->>G: 提交 Nanite 绘制命令
G->>V: 光栅化可见 Cluster,记录 (ClusterID, TriangleID, Barycentric)
V->>B: 按像素解算材质与着色
B-->>G: 最终图像3.2 为什么是 GPU-driven
传统管线里,CPU 要决定画哪个 mesh、哪个 LOD、哪个 instance。当场景里有几万个静态物体时,CPU 端的开销会先于 GPU 成为瓶颈。Nanite 把 culling、LOD 选择、instance 合并都搬到 GPU,CPU 只需要给一个高层指令:「这些 Nanite 资源,渲染。」
用公式粗略描述渲染负载的转移:
T_{Nanite} \approx T_{CPU}^{minimal} + T_{GPU}^{culling} + T_{GPU}^{geometry}^{'} + T_{GPU}^{shading}^{'}
当物体数量 增大时, 线性增长,而 Nanite 的 CPU 开销几乎不变。这就是开放世界场景能从 Nanite 获得最大收益的原因。
四、集群与内存:天下没有免费的午餐
Nanite 不是魔法。它省掉了手工 LOD 的时间,但付出了其他代价。
4.1 磁盘与内存开销
启用 Nanite 后,单个网格的磁盘占用通常是普通网格的 2-3 倍。因为除了要存最终几何,还要存 cluster 层级、LOD 层次、BVH 加速结构等元数据。
内存方面,Nanite 会按需流送 cluster。但所有 instance 的根 cluster 必须常驻显存。如果你在一个开放世界里放了几万棵 Nanite 树,即使每棵树远处只显示几百个三角形,根 cluster 的总量也可能压垮 VRAM。
flowchart LR
A["Disk 2-3x"] --> B["DDC / Pak"]
B --> C["运行时按需流送"]
C --> D["VRAM 中的可见 Cluster"]
C --> E["常驻的根 Cluster"]
E --> F["Instance 数量过多会爆显存"]4.2 r.Nanite.MaxNodes 与 MaxTriangles
UE5.8 提供了几个关键控制台变量控制 Nanite 的渲染预算:
r.Nanite.MaxNodes:剔除遍历阶段最多处理多少 BVH 节点,默认约 200 万;r.Nanite.MaxTriangles:每帧最多渲染多少 Nanite 三角形;r.Nanite.MaxCandidateClusters:候选 cluster 池大小。
当场景过于复杂,这些缓冲区溢出时,引擎会弹警告:「Nanite node buffer overflow detected」。此时画面可能出现低精度几何或闪烁。解决方式通常是:
- 提高 MaxNodes / MaxTriangles;
- 减少 instance 数量;
- 检查是否有异常高面数的小物体。
[/Script/Engine.RendererSettings]
r.Nanite.MaxNodes=4194304
r.Nanite.MaxTriangles=2000000五、哪些资产该开 Nanite,哪些不该
Nanite 不是万能开关。根据官方文档和实际项目经验,可以总结出下面这张表:
| 资产类型 | 是否适合 Nanite | 原因 |
|---|---|---|
| 英雄级环境物体(岩石、建筑、废墟) | ✅ 适合 | 高面数、近距离、细节重要 |
| Photogrammetry 扫描件 | ✅ 适合 | 省去 retopo 和手工 LOD |
| 简单小道具(箱子、瓶子、碎石) | ⚠️ 可选 | 收益小,远处直接变成几个像素 |
| 植被(带 Masked 材质) | ⚠️ 谨慎 | Masked bin 开销高,需测试 |
| 半透明物体(玻璃、水) | ❌ 不适合 | Nanite 不支持 translucent |
| 骨骼网格角色 | ❌ 默认不适合 | 5.5+ 有实验性支持,但限制多 |
| 地形 Landscape | ✅ 适合 | UE5 原生支持 Landscape Nanite |
5.1 一个常见错误
很多团队第一次用 Nanite 时,会把所有东西一股脑开启,包括小石子、草丛、远处的广告牌。结果性能没提升,内存还涨了。原因是:Nanite 的收益来自「减少远距离/overdraw 的几何开销」,但如果一个物体本身就很便宜,那预处理 cluster 的 overhead 反而会占主导。
老周后来定了一条内部规范:「面数低于两千、屏幕上常驻像素少于五十的静态物体,先不开 Nanite,等项目后期 profiling 再决定。」
六、Nanite 与 Virtual Shadow Maps 的共生关系
Nanite 的另一个重要搭档是 Virtual Shadow Maps(VSM)。传统阴影贴图对每个光源都要渲染一次阴影 pass,光源多了 shadow draw call 会爆炸。VSM 把阴影缓存成页表,只更新有变化的区域。
Nanite 网格投射 VSM 阴影时,可以利用同样的 cluster 流送机制,只渲染必要的三角形。这意味着:动态光源多了,Nanite + VSM 的组合比传统静态网格 + 级联阴影要便宜得多。
graph LR
A["Nanite 几何"] --> B["VisBuffer 主 Pass"]
A --> C["Virtual Shadow Maps"]
B --> D["最终图像"]
C --> D这也是 MegaLights 能成真的基础之一。没有 VSM 和 Nanite,几百盏动态阴影灯根本不敢想。
七、5.8 的改进与仍需注意的坑
7.1 5.8 没有颠覆 Nanite,但打磨了细节
UE5.8 对 Nanite 的改动相对温和,主要是:
- 更好的 cluster culling 效率;
- 与 Mesh Terrain 的集成更顺畅;
- 对 masked / foliage 材质的 bin 优化;
- 调试视图和性能分析工具更完善。
7.2 仍然存在的限制
- 不透明和 masked 材质才能用:translucent、decal、two-sided foliage 等 unsupported;
- 不支持顶点动画:水面、布料变形不能用 Nanite;
- 碰撞要单独处理:Nanite 几何不能直接用,需要生成 collision mesh;
- 材质 ID 上限 64:超复杂模块化建筑可能触及;
- 磁盘占用大:需要规划 DDC 共享和 Pak 分包。
7.3 一个故事:植被的教训
老周的团队曾经把所有树都开了 Nanite,以为可以告别 LOD。结果在森林里帧率反而下降。查了一周 profiler 才发现:树叶用了 masked 材质,每片叶子都被分到不同的 raster bin,导致 bin count 爆炸。
最后的方案是:
- 树干和主要树枝用 Nanite;
- 大量小叶片用传统 LOD + impostor;
- 远景树林用 Billboard 或 HLOD。
Hybrid 方案往往比「全开」或「全关」更靠谱。
八、检查清单:给美术和程序的实操建议
美术侧
- 英雄级静态几何尽量开 Nanite,省去手工 LOD;
- 保持三角面分布合理,避免平坦区域过度细分;
- UV 和顶点数据保持干净,只保留必要的通道;
- 复杂资产注意材质 ID 不要超过 64;
- 半透明部分拆出来用传统 mesh。
程序侧
- 用
stat Nanite观察 cluster、triangle、instance 数量; - 监控
r.Nanite.MaxNodes是否溢出; - 关注 VisBuffer 和 BasePass 各自的开销;
- 开放世界项目重点控制 instance 总量,不是 triangle 总量;
- 为小物件制定 Nanite 启用白名单。
九、Cluster 生成的幕后:导入时发生了什么
很多人在引擎里点一下「Enable Nanite」,以为只是改了个 bool。其实后台发生了一系列预处理。
9.1 导入流程
当你对一个 StaticMesh 勾选 Nanite 时,Unreal Header Tool 和导入管线会:
- 读取原始三角形数据;
- 把网格切分成最多 128 个三角形一组的 cluster;
- 为每组 cluster 生成简化版本,构建父子关系;
- 建立 BVH(Bounding Volume Hierarchy)加速结构;
- 把结果存进 DDC(Derived Data Cache),供运行时流送。
sequenceDiagram
participant A as "美术导入 FBX"
participant B as "StaticMesh 导入管线"
participant C as "Cluster Builder"
participant D as "BVH 构建"
participant E as "DDC 缓存"
A->>B: 提交源网格
B->>C: 切分 Cluster
C->>C: 生成简化层级
C->>D: 构建空间加速结构
D->>E: 写入预处理数据
E-->>B: 返回可用资产这个预处理只在导入或修改时做一次。第一次导入高面数资产时,编辑器可能会卡几秒到几十秒,取决于面数和机器性能。
9.2 什么样的拓扑适合 Nanite
虽然 Nanite 能处理各种拓扑,但效率有高低之分。理想的输入是:
- 三角形分布相对均匀;
- 没有大量退化的细长条面;
- UV 接缝合理,不会导致 cluster 边界过度破碎;
- 顶点属性通道精简。
老周团队的经验是:从 ZBrush 直接导出的高模通常比从 CAD 软件转出来的模型更适合 Nanite。CAD 模型经常有大量共面、微小面和非法拓扑,cluster 生成效率低。
十、Raster Bin:材质复杂度才是新瓶颈
很多人以为 Nanite 之后就不用关心材质了。错。Nanite 只是把瓶颈从几何转移到了别处,其中最隐蔽的就是 raster bin。
10.1 什么是 Raster Bin
Nanite 在光栅化阶段会把三角形按材质属性分组到不同的 bin 里。每个 bin 对应一种材质状态。状态越多,bin 越多,GPU 切换开销越大。常见的高 bin 场景包括:
- 大量使用 masked / alpha test 的树叶、草丛;
- 每个 mesh 使用大量不同材质 ID;
- 复杂的两面渲染或自定义深度材质。
10.2 一个 profiling 案例
老周用 stat Nanite 看到一个植被场景的 raster bin 数飙到了三千多。作为参考,普通室内场景通常控制在几百以内。问题出在每片叶子都用了一个独立的 masked 材质实例。
优化方案:
- 把相同植物的叶子合并到同一个材质实例;
- 减少 alpha test 边缘的复杂度;
- 远处树叶改用 opaque 或 dithered LOD。
优化后 bin 数降到六百,帧率回升了 18%。
10.3 常用 Profiling 命令
UE5.8 提供了一套完整的 Nanite 调试视图和统计命令:
stat Nanite # 查看 Nanite 基础统计
stat GPU # 查看 VisBuffer / BasePass 开销
r.Nanite.Visualize 1 # 打开可视化模式
r.Nanite.Visualize 2 # 查看 Cluster 分布
r.Nanite.Visualize 3 # 查看 Triangle 密度在可视化模式下,你可以直观看到哪些区域 cluster 密度异常高,哪些资产在远处仍然保留了过多细节。
十一、与 Landscape 和 World Partition 的协同
UE5 从某个版本开始支持 Landscape Nanite。这让大面积地形也能享受自动 LOD 和 VSM 阴影的好处。对于开放世界项目,Nanite Landscape 配合 World Partition 可以显著减少远距离地形的渲染开销。
但这里有个容易忽略的点:Nanite Landscape 的 cluster 选择是基于屏幕空间的。如果相机拉得很高,整个地形会退化成很粗的三角形。这对策略游戏或俯视视角项目很友好,但对第一人称近距离场景需要更细粒度的高度图和材质层。
flowchart LR
A["Nanite Landscape"] --> B["World Partition 流送"]
B --> C["按相机距离加载地形 Cell"]
C --> D["Nanite Cluster 自动 LOD"]
D --> E["VSM 阴影"]十二、写在最后
很多人以为 Nanite 之后就不用关心材质了。错。Nanite 只是把瓶颈从几何转移到了别处,其中最隐蔽的就是 raster bin。
9.1 什么是 Raster Bin
Nanite 在光栅化阶段会把三角形按材质属性分组到不同的 bin 里。每个 bin 对应一种材质状态。状态越多,bin 越多,GPU 切换开销越大。常见的高 bin 场景包括:
- 大量使用 masked / alpha test 的树叶、草丛;
- 每个 mesh 使用大量不同材质 ID;
- 复杂的两面渲染或自定义深度材质。
9.2 一个 profiling 案例
老周用 stat Nanite 看到一个植被场景的 raster bin 数飙到了三千多。作为参考,普通室内场景通常控制在几百以内。问题出在每片叶子都用了一个独立的 masked 材质实例。
优化方案:
- 把相同植物的叶子合并到同一个材质实例;
- 减少 alpha test 边缘的复杂度;
- 远处树叶改用 opaque 或 dithered LOD。
优化后 bin 数降到六百,帧率回升了 18%。
9.3 常用 Profiling 命令
UE5.8 提供了一套完整的 Nanite 调试视图和统计命令:
stat Nanite # 查看 Nanite 基础统计
stat GPU # 查看 VisBuffer / BasePass 开销
r.Nanite.Visualize 1 # 打开可视化模式
r.Nanite.Visualize 2 # 查看 Cluster 分布
r.Nanite.Visualize 3 # 查看 Triangle 密度在可视化模式下,你可以直观看到哪些区域 cluster 密度异常高,哪些资产在远处仍然保留了过多细节。
十、与 Landscape 和 World Partition 的协同
UE5 从某个版本开始支持 Landscape Nanite。这让大面积地形也能享受自动 LOD 和 VSM 阴影的好处。对于开放世界项目,Nanite Landscape 配合 World Partition 可以显著减少远距离地形的渲染开销。
但这里有个容易忽略的点:Nanite Landscape 的 cluster 选择是基于屏幕空间的。如果相机拉得很高,整个地形会退化成很粗的三角形。这对策略游戏或俯视视角项目很友好,但对第一人称近距离场景需要更细粒度的高度图和材质层。
flowchart LR
A["Nanite Landscape"] --> B["World Partition 流送"]
B --> C["按相机距离加载地形 Cell"]
C --> D["Nanite Cluster 自动 LOD"]
D --> E["VSM 阴影"]十一、写在最后
Nanite 最打动人的地方,不是它能让一千万面的模型跑在主机上,而是它把「减面」这个枯燥、重复、容易出错的工作从美术身上卸了下来。老周现在做英雄级岩石,导入引擎后点一下 Enable Nanite,再去检查 cluster 分布和材质开销,省下的时间可以拿来雕更多细节。
但这套系统有它的脾气。它擅长处理复杂静态几何,不擅长处理半透明、变形、超低面物体。把它当成一把精确的手术刀,而不是一把乱砍的斧头,它才能真的帮到你。
工具的价值从来不在于它能做什么,而在于它让你不必做什么。Nanite 让开发者不必再当人肉 LOD 生成器,这已经够值了。剩下的时间,你可以去做更重要的事:设计更有趣的关卡,打磨更细腻的光照,或者干脆早点下班。
十三、给新人的一句话建议
如果你刚接触 Nanite,记住三条原则就够了:
- 静态、不透明、面数高的物体,先开 Nanite;
- 半透明、会变形、面数极低的物体,别开;
- 开了之后一定要用
stat Nanite和stat GPU验证,别凭感觉。
剩下的事,交给时间。用得多了,你自然会培养出对 cluster 密度和 raster bin 的直觉。这跟骑车一样,摔过几次坑,就知道哪里该刹车。Nanite 已经帮你看清了很多坑,但方向盘还在你手里。开得快不快,路线选得对不对,最终还是取决于开车的人。工具再聪明,也替代不了判断力。希望这篇文章能帮你少摔几个坑,多省几天时间。