Nanite 虚拟化几何体
Nanite Virtualized Geometry
Nanite 是 UE5 的虚拟化几何体 (Virtualized Geometry) 系统:导入时把网格切成分层的三角形簇 (Cluster),渲染时按相机视角逐簇选择细节层次并无缝拼接,按需从磁盘流送,并在绕过传统绘制调用 (Draw Call) 的独立通道中渲染,使帧预算不再受多边形数、绘制调用和网格内存约束。
原理
官方描述的工作方式只有两步:(Nanite Virtualized Geometry Overview)
- 导入时:网格被分析并拆成"三角形组的分层簇"。
- 渲染时:根据相机视角,以不同细节层次即时替换簇,且同一对象内相邻簇"完美衔接无裂缝";数据按需流送,只有可见细节需要驻留内存;Nanite 在自己的渲染通道中运行,"完全绕过传统绘制调用"。
下图表示数据流:离线构建簇层级,运行时按屏幕误差选簇并按需流送。
flowchart LR
A["源网格<br/>高模/扫描"] --> B["导入: 分层簇构建+压缩"]
B --> C[("磁盘: Nanite 数据")]
C -->|"按需流送"| D["GPU 驻留簇"]
E["相机视角"] --> F["逐簇选 LOD + 剔除"]
D --> F --> G["Nanite 独立光栅通道"]
G --> H["进入延迟管线 材质着色"]与通用理论的关系:
- 传统 细节层次 是"每个网格几档离散 LOD + 切换距离",切换时会跳变;Nanite 把 LOD 粒度细化到簇,自动处理、无需手工设置 LOD,官方称 LOD 过渡时"质量损失罕见或不存在"。(Nanite Virtualized Geometry Overview) 簇层级的生成离不开 网格简化(官方页面未说明简化细节;Epic 的 SIGGRAPH 2021 讲稿称每簇 128 个三角形,簇组合并后简化再拆分,形成 DAG 而非树,误差用二次误差度量 (QEM) 计算并投影成屏幕像素误差,见下文「内部实现」)。
- 逐簇剔除把 剔除技术 与 遮挡剔除 的粒度从"每图元"降到"每簇";Nanite 网格适合作为其他 Nanite 几何的"主要遮挡体"。(Nanite Virtualized Geometry Overview)
- "绕过绘制调用"相当于把 绘制调用与状态切换优化 中的 CPU 提交瓶颈整个移到 GPU 驱动 (GPU-driven) 的流程中。官方概览页没有展开 Nanite 内部如何光栅化与着色,下文「内部实现」一节依据 Epic 的 SIGGRAPH 2021 讲稿补充,可参考理论页 延迟纹理与可见性缓冲 理解这类思路,但不能当作 UE 的确切实现。
- 与 大场景渲染与虚拟纹理 的类比:官方明确说虚拟纹理对纹理数据要实现的目标,正是 Nanite 对网格数据实现的目标,二者正交但"强烈推荐"一起用。(Nanite Virtualized Geometry Overview)
内部实现:可见性缓冲与软件光栅化(SIGGRAPH 2021)
以下来自 Brian Karis 等在 SIGGRAPH 2021「Advances in Real-Time Rendering」课程上的 Nanite 讲稿,属于 UE5 早期访问版时期的资料,5.8 的实现细节可能已有变化。(A Deep Dive into Nanite Virtualized Geometry (Brian Karis et al., Epic Games, SIGGRAPH 2021 Advances in Real-Time Rendering))
为什么用可见性缓冲:密集网格走传统光栅化有三个问题:光栅化时切换着色器、材质求值的过度绘制(或需要深度预通道)、像素四边形 (Quad) 浪费。Nanite 只往屏幕写「深度 : 实例 ID : 三角形 ID」,然后由每像素的材质着色器读回三角形、变换三个顶点、求重心坐标并插值属性,最后写 G-Buffer。这样没有过度绘制和四边形浪费,每个视图只光栅化一次。这正是 延迟纹理与可见性缓冲 一页讲的思路;讲稿特别说明,只有「对象 ID + 三角形 ID」这种形式才叫可见性缓冲。
软件光栅化:硬件光栅化器按像素并行,适合大三角形,遇到只占几个像素的微多边形效率很低。Nanite 用计算着色器按三角形并行做软件光栅化,讲稿称平均比他们最快的图元着色器 (Primitive Shader) 实现快 3 倍。深度测试改用 64 位原子操作:对可见性缓冲做 InterlockedMax,高位放深度,低位放可见簇索引和三角形索引(最多 34 位)。三角形边长小于 32 像素的簇走软件光栅化,更大的走硬件光栅化,但硬件路径也不绑定颜色/深度目标,同样用原子写入 UAV。
剔除:用两遍遮挡剔除 (Two-Pass Occlusion Culling):先用上一帧的 HZB 和上一帧变换测试并绘制可见物体,再用刚绘制的结果建本帧 HZB,补画新暴露出来的物体。对照 遮挡剔除。
flowchart LR
A["实例/簇剔除<br/>上一帧 HZB"] --> B{"簇三角形边长"}
B -->|"< 32 像素"| C["软件光栅化<br/>64 位原子写入"]
B -->|"较大"| D["硬件光栅化<br/>原子写入 UAV"]
C --> V["可见性缓冲<br/>深度+簇+三角形"]
D --> V
V --> H["构建本帧 HZB → 第二遍剔除"]
V --> M["材质通道写 G-Buffer"]图中剔除结果按三角形大小分流到软件或硬件光栅化,两者都写入同一个可见性缓冲;这个缓冲既用来建 HZB 做第二遍剔除,也供材质通道把结果写进 G-Buffer。
在项目中怎么用
- 启用方式:导入时勾选 Build Nanite;内容浏览器批量右键 Nanite > Enable;或在静态/骨骼网格编辑器里勾 Enable Nanite Support。大项目建议共享 DDC,因为转换需要处理时间。(Nanite Virtualized Geometry Overview)
- 不用 Lightmass 烘焙光照时,关闭 Generate Lightmap UVs,以免高密度网格多出一套昂贵的 UV 通道和构建时间。(Nanite Virtualized Geometry Overview)
- 官方建议"在支持的平台上尽可能启用":三角形多/屏幕上很小、实例多、是主要遮挡体、用虚拟阴影贴图投影的网格尤其适合;例外如天空球,但即使开了损失也很小。(Nanite Virtualized Geometry Overview)
- 已覆盖:静态网格(无 LOD,完整支持 Lumen 和 VSM)、骨骼网格(整个网格一个绘制调用、动画 LOD)、地形、植被、样条网格、静态置换与运行时曲面细分 (Nanite Tessellation)。(Nanite Virtualized Geometry Overview)
与其他 UE5 系统的耦合:虚拟阴影贴图 是为 Nanite 设计的,非 Nanite 几何渲染进 VSM 要贵得多;(Virtual Shadow Maps) Lumen 全局光照与反射 依赖 Nanite 加速表面缓存捕获,植被和实例化静态网格只有用 Nanite 时才被 Lumen 表面缓存支持。(Lumen Technical Details)
限制与坑
| 类别 | 限制(UE 5.8 文档) |
|---|---|
| 实例数 | 场景最多 1600 万实例(只计已流送的,含非 Nanite) |
| 切线 | 不存逐顶点切线,在像素着色器中推导,边缘可能有不连续 |
| 变形 | 只支持刚体变换;WPO 支持但受限(需钳制位移量);不支持 Morph Target |
| 材质 | 只支持 Opaque 和 Masked;自定义表达式(如视差遮蔽)可能有瑕疵(尚无解析导数) |
| 渲染 | 不支持按视图的最小屏幕半径/距离剔除、前向渲染、VR 立体、MSAA、光照通道 |
| 光追 | 默认对 Fallback Mesh 做光追;原生 Nanite 光追为实验性 r.RayTracing.Nanite.Mode 1 |
| 平台 | 当前主机与 DX12 SM6 桌面;依赖快速流送,推荐 SSD |
来源:(Nanite Virtualized Geometry Overview)
取舍:Nanite 把"多边形预算"换成了"磁盘/流送带宽 + 平台门槛",同时与前向渲染、MSAA、VR 不兼容;官方也提醒实例数、单网格三角形数、材质复杂度、输出分辨率等仍要针对内容和硬件实测(帧预算见 帧时间预算与渲染性能)。半透明和大量 WPO 动画的内容仍需走传统网格并做好 LOD。
GEA 的视角:从 LOD 树到 LOD 图(卷 II 11.6.8)
GEA 卷 II 11.6.8 用一整节介绍虚拟化几何体与 Nanite,主要依据 Karis、Stubbe、Wihlidal 在 SIGGRAPH 2021 的《Nanite: A Deep Dive》,与本页「内部实现」一节的资料同源,但从通用渲染原理的角度讲得更完整。
从虚拟纹理到虚拟化几何体
- 虚拟纹理把带 mip 的大纹理切成瓦片,只把当帧需要的瓦片和 mip 级载入显存;虚拟化几何体把同样的原则用在三维几何上(见 大场景渲染与虚拟纹理)。
- GEA 说相关思想早已存在,但直到 Brian Karis、Rune Stubbe、Graham Wihlidal 等人为 UE5 开发的 Nanite 才有了高效利用 GPU 的实时实现;2020 年 Nanite 亮相后,其他团队也为自己的引擎开发了类似技术。
LOD 链 → LOD 树
- 简单的 LOD 链适合小模型,但地形、建筑、巨型飞船这类大几何的一部分离相机很近、另一部分很远,需要把模型拆成子网格树,各自独立选 LOD(传统 LOD 的问题见 细节层次 中的 GEA 部分)。
- LOD 树:把高分辨率源资产切成小三角形簇(例如每簇不超过 128 个三角形)作为叶子;选几个相邻簇合并成一个较低分辨率的网格(仍不超过 128 个三角形),作为它们的父节点;逐层重复,直到根节点是一个不超过 128 个三角形、近似整个模型的网格。
- 渲染时在树上做一次与视角相关的"切割":选尽可能低分辨率的节点,使得换成其子节点用户也察觉不到差别;换句话说,让每个三角形大约一个像素大。三角形到了像素大小,再小也不会提升画质,所以结果与渲染全部叶节点(即原始影视级资产)几乎没有区别。
裂缝与 LOD 图
- 简单 LOD 树的问题:折叠边构建上一层时,簇之间会出现裂缝。一种办法是减面时锁定每个簇的外边界边,保证父簇轮廓与子簇相同。
- 但这很快失效:叶簇的外边被锁定,父、祖父……都必须共享这些边,高分辨率细节会"堆积"在这些边上,减面效率低到不可用。
- Nanite 的关键洞见:每一层锁定不同的边。把源网格切成最多 128 个三角形的簇,选一组 N 个相邻簇,锁定这组的外边界,减面得到三角形数减半、轮廓不变的新网格;然后把它重新切成 N/2 个新簇。这样 N 个子簇就有了不止一个父簇,树变成有向无环图 (DAG)。
- 运行时同样对 DAG 做切割;Nanite 的算法保证各组簇的 LOD 决策互相独立,因而能在 GPU 上并行执行。
其他组成部分
- 层次 Z 缓冲 (HiZ/HZB):本质是带 mip 的深度缓冲,每个上层像素存下层四个像素中的最大深度。剔除时算出簇的屏幕空间范围,找到范围小于 4×4 像素的最低 mip 级,用其包围体的最小深度与之比较,每簇只需一次纹理采样(见 遮挡剔除)。
- 两遍遮挡剔除:要用 HZB 判断画哪些簇,却要先画簇才能得到 HZB。Nanite 第一遍重画上一帧可见的簇来构建本帧 HZB,再用它测试其余簇,找出本帧新出现的部分。
- 可见性缓冲:存每个像素的深度、物体 ID 和三角形 ID,把可见性问题与光照问题解耦。GEA 把它比作深度预通道,区别是它既像深度缓冲又像"廉价的 G-buffer",只存足够在之后的光照 pass 中查到材质的信息(见 延迟纹理与可见性缓冲)。
- 软件光栅化:簇的三角形约一个像素大,而硬件光栅化器为大三角形优化、以 2×2 quad 为单位工作,每个单像素三角形都要渲染整个 quad,极其低效;所以 Nanite 这类引擎用 GPU 上的计算任务做软件光栅化(见 光栅化)。
与本页其他资料的对照
- GEA 的描述与本页「内部实现」一节(同一份 SIGGRAPH 2021 讲稿)一致,没有发现冲突;本页给出了 32 像素的软硬件分流阈值和 64 位原子写入,GEA 没有涉及这些细节。
- 原书问题(11.6.8.6):GEA 说 quad 是为了对相邻像素的深度求一阶差分、近似三角形相对相机的斜率,再用于纹理过滤。实际上 2×2 quad 的差分可以对任意插值量或着色器变量求(HLSL
ddx/ddy,见 Microsoft Learn "ddx" 文档),纹理过滤选 mip 级用的是纹理坐标的屏幕空间导数,不是深度的导数(同见 硬件光栅化与透视校正插值)。结论"单像素三角形也要付出整个 quad 的代价"不受影响。 - 书中原文在讲 2×2 quad 时写"在 11.3.10.1 节我们说过",但 quad 光栅化实际在 11.3.10.8 节,属于书中交叉引用笔误。
出处
- Nanite Virtualized Geometry Overview UE 5.8 文档
- Virtual Shadow Maps UE 5.8 文档
- Lumen Technical Details UE 5.8 文档
- A Deep Dive into Nanite Virtualized Geometry (Brian Karis et al., Epic Games, SIGGRAPH 2021 Advances in Real-Time Rendering) Epic SIGGRAPH 2021 Advances 讲稿(UE5 早期访问版时期)
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.6.8 节