最近用 UE5.6 做项目,踩了一堆光追相关的坑——地编、美术、程序那边都遇到过不少两眼一抹黑的时刻。把一些概念性的东西和引擎模块设计整理一下,下次碰到光追相关的问题至少有个基础知识抓手。
光线追踪到底是什么、UE 的渲染模块如何容纳"光线"、以及 UE 的光追系统由哪些部分组成。概念口径参考《计算机图形学基础》(Fundamentals of Computer Graphics, 5th ed.),引擎实现参考 UE 5.6 本地源码。
0. 阅读说明
0.1 内容与基准
| 章节 | 内容 | 面向 | 依据 |
|---|---|---|---|
| 第 1 章 | 光线追踪是什么 | 概念 | 《计算机图形学基础》第 3-4 章 |
| 第 2 章 | UE Render 模块如何容纳光追 | 工程 | UE 5.6 源码 |
| 第 3 章 | Lumen:UE 处理间接光的第一层架构 | 工程 | UE 5.6 源码 |
| 第 4 章 | UE 通用光追系统设计 | 工程 | UE 5.6 源码 |
| 第 5 章 | 工程视图:要动的开关、常见翻车与排查入口 | 美术 / TA / 程序 | UE 5.6 源码 |
| 第 6 章 | 附录:源码索引 / 控制台速查 / 参考 | 工具 | UE 5.6 源码 |
0.2 三条阅读路径
正文每节开头(以及若干关键段)有一行 > **一句话**:...,它是该节结论的白话版本。按下面三条路径读,可以只看自己需要的那部分:
| 角色 | 建议路径 | 读完能得到什么 |
|---|---|---|
| 美术 / 地编 | 第 1 章 -> 2.0 节的总览图 -> 5.1 节(要动的开关)-> 5.2 节(常见翻车) | 知道光追在哪里介入画面、动哪个开关会影响什么、哪些设置容易被误解 |
| TA / 技术美术 | 上面 + 3.2 节(Lumen 组成)-> 3.4 节(每帧流程)-> 4.8 节(降噪)-> 5.3 节(排错谱系) | 能做质量与性能取舍,能定位常见异常 |
| 程序 | 全篇;第 5 章可跳到 5.3 节 | 拿到"概念 -> 代码实体"的映射、调用链入口与版本边界 |
0.3 术语最小集
首次出现的缩写先在这里给一句话口径,正文不再重复解释:
| # | 术语 | 一句话 |
|---|---|---|
| 1 | 光栅化(Rasterization) | 把三角形投影到屏幕、按对象顺序逐像素写入;UE 的主流程 |
| 2 | G-Buffer | 延迟渲染的中间缓冲集合(法线、粗糙度、深度等),后续光照与降噪都从这里取信息 |
| 3 | Pass | 管线里的一段独立工作(一次绘制或一次 compute dispatch),有明确的输入输出资源 |
| 4 | RDG(Render Dependency Graph) | UE 的 Pass 编排系统:Pass 只声明输入输出,排序与资源分配由它负责 |
| 5 | BVH(Bounding Volume Hierarchy) | 按包围盒分层组织的加速树,用于把"找最近交点"从线性扫描降到近似对数级 |
| 6 | BLAS(Bottom-Level Acceleration Structure) | 单个几何体内部的 BVH,只描述形状,不含世界位姿 |
| 7 | TLAS(Top-Level Acceleration Structure) | 场景实例的 BVH,每个实例引用一个 BLAS 并携带变换与掩码 |
| 8 | SBT(Shader Binding Table) | 把"几何 + 实例 + 材质"映射到具体命中着色器的索引表,DXR 规范要求 |
| 9 | Hit Shader | 光线命中后执行的着色程序:Closest Hit(取最近交点着色)、Any Hit(常用来做遮挡判定) |
| 10 | permutation(变体) | 同一份 shader 源码按编译期开关(如是否启用 Nanite 光追)生成的多份实例 |
| 11 | 全管线光追 / Inline 光追 | 前者用独立的 RayGen 管线派发;后者在普通 compute shader 内嵌 TraceRayInline 调用 |
| 12 | SER(Shader Execution Reordering) | 硬件按材质/命中给光线重新排序、改善执行相干性的能力;硬件不支持时开关无效果 |
| 13 | 时空降噪 | 用当前帧邻域(空间)与历史帧(时间)把"每像素少量光线"的结果补成干净画面 |
0.4 约定
- 引擎源码路径均相对于 UE 根目录(如
Engine/Source/Runtime/...)。 - 概念口径统一参照《计算机图形学基础》(Fundamentals of Computer Graphics, 5th ed.)对应章节。
- 文中
> **一句话**:...是该节结论的白话版本,便于先建立直觉;正文其余部分保留术语精确,机制、公式、时序与约束按原样陈述。
1. 光线追踪是什么
1.1 渲染的两条路线:逐物体 vs 逐像素
渲染的目标是将"三维场景"转换为"二维像素图"。像素图的本质是什么?光栅图像是一个二维数组,每个元素(像素)存储图像在该位置附近颜色的局部平均值(点样本)。[1] 换言之,无论采用何种渲染方法,最终产物几乎总是一张光栅图像。
实现这一转换存在两种相反的组织方式:
对象顺序(Object-Order) 图像顺序(Image-Order)
for every object: for every pixel:
计算它影响了哪些像素 计算它被哪个物体遮挡、
-> 光栅化 / 扫描线 以及该物体的着色结果
-> 光线追踪
- 光栅化采用对象顺序:将三角形投影到屏幕空间,逐像素覆盖写入帧缓冲。其优势在于速度,但"某一像素究竟能看到什么"并非原生问题——可见性需要 Z 缓冲、阴影需要 Shadow Map 等额外机制来近似。
- 光线追踪采用图像顺序:直接从"像素应看到什么"这一问题出发,天然能够精确求解可见性、阴影、反射、折射等效果。这些在对象顺序框架中需要大量近似手段的问题,在光线追踪中是算法流程的自然产物。[2]
需要澄清的常见误区:“光追输出光栅图像"并不矛盾。光追只是通过图像顺序的方式确定每个像素的颜色,输出仍然是光栅位图——图像在数学上可抽象为定义在矩形区域上的连续函数,光栅像素只是该函数的离散采样。[1]
1.2 基本做法:生成光线、求交、着色
基本光线追踪算法可归纳为三步[3]:
- 光线生成(Ray Generation):对每个像素,从视点
e发射一条穿过该像素的视线(view ray)。视线的数学表达是三维参数直线:
p(t) = e + t * (s - e)
其中 e 为视点位置,s 为像素在图像平面上的采样点,参数 t 表示沿视线方向的距离。t < 0 对应相机后方的点,t = 0 即为视点本身。[4]
求交(Intersection):寻找视线与场景中最近的有效交点。这一步有两个关键语义:
- “最近"的含义:离视点最近的物体会遮挡其后所有物体,因此只需保留最小的合法
t值。 - 与一组物体求交的朴素做法:把一组物体视为"一个复合表面”,逐个求交并持续收缩搜索区间
[t0, t1]——每找到一个更近的命中点,就把区间上界t1收紧到该点的t,后续物体只在更窄的区间内参与竞争。[5]
- “最近"的含义:离视点最近的物体会遮挡其后所有物体,因此只需保留最小的合法
// 一组物体求交:最近交点 + 区间收缩(示意代码)
HitRecord Hit(Group g, Ray r, float t0, float t1) {
HitRecord closest = HitRecord(); // t = INFINITY,表示未命中
for (auto surface : g.surfaces) {
HitRecord rec = surface.Hit(r, t0, t1);
if (rec.t < t1) { // 仅接受比当前更近的命中
closest = rec;
t1 = rec.t; // 收缩搜索区间
}
}
return closest;
}
需要注意的是:逐物体线性扫描的时间复杂度为 O(N),在大规模场景中不可用。工程实践中会引入加速结构(如 BVH)将"查找最近物体"降为近 O(log N)——这正是第 4 章 UE 光追系统中 BLAS/TLAS 所要解决的问题。在概念上,加速结构同样被视为"一个可求交的对象”,与普通表面在组合层级上是平级的。[5]
- 着色(Shading):在获得交点位置、表面法线、材质属性与光源信息后,计算该像素的最终颜色。着色模型本身与光栅化完全通用——区别仅在于几何信息(交点、法线)与可见性信息是求交阶段直接给出的精确值。[6]
阴影是这套流程的自然延伸:判断点 x 是否被某光源照亮,只需从 x 出发向光源方向发射一条阴影光线(shadow ray);若中途被其他物体遮挡,则 x 处于该光源的阴影中。为避免 x 所在表面因浮点误差被自身误判为遮挡,阴影光线的起始参数需设置一个极小的正偏移 t = epsilon,而非从 t = 0 开始。
1.3 为什么是从摄像机"反向"发射光线
这是初学者最常遇到的困惑:现实中光从光源射向场景,为何算法要反过来从相机发射?
答案可以从物理与计算两个层面分别理解:
物理层面:光路具有可逆性。一条从光源出发、经多次弹射最终进入相机的光路,其反向路径(从相机到光源)在物理上是等价的。渲染所关心的只是"最终到达相机的光",无论正向还是反向计算,结果一致。
计算层面:正向追踪效率极低。若从光源向所有方向均匀发射"光子",能进入某一像素视锥的比例微乎其微——绝大多数计算资源都会浪费在永远不会被看见的路径上。反向追踪则不同:每条光线都指向一个确定的像素,发射的全部视线都对最终图像有贡献。[2]
因此"反向"并非对物理事实的违背,而是一种采样策略:图像顺序的光线追踪天然只采样"对最终图像有贡献的路径",将计算量集中在有效区域。
正向(物理直觉): light -> 场景散射 .................... 绝大多数光子永远无法抵达相机
反向(算法实现): camera -> 像素对应方向 -> 命中物体 -> (按需)继续发射阴影/反射光线
^ 每条光线都对对应像素有确定性贡献
1.4 光线追踪与 Shader 的关系
一句话:光追只回答"光线碰到谁",碰到之后长什么样由着色程序决定——这条分界线在硬件光追里就是"几何层"与"Hit Shader"的分工。
“求交确定最近物体"只回答了"光线击中了谁”,还未回答"该物体呈现什么样"。答案取决于命中点执行哪段材质/光照程序。光线追踪与着色器(Shader)的关系可从以下角度理解:
- 光线追踪框架先完成"几何判定",再向着色阶段提供标准输入参数:求交直接给出交点位置
p = e + t*d与表面法线n;视线方向v取入射光线方向的反向(即v = -d_hat);此外还包括材质属性(漫反射/镜面系数、粗糙度等)与纹理坐标——以上构成着色所需的完整输入集。 - 阴影 / 光源可见性属于附加输入:判断交点是否被某光源照亮,需沿光源方向
l额外发射一条阴影光线,将遮挡结果(二元或软阴影系数)并入着色计算[7]、[8]。 - 着色模型本身(漫反射 + BRDF、反射、折射)在光线追踪与光栅化之间完全通用——同一套着色公式可无缝用于两条渲染路线。[6]
进入 GPU 硬件光追(DXR / Vulkan RT)时代后,上述流程被拆分为固定管线遍历 + 多阶段可编程 Shader:遍历由硬件完成,可编程阶段负责生成光线、判定命中与计算颜色。这四个阶段(Ray Generation / Intersection 与 Any Hit / Closest Hit / Miss)的职责清单放在 4.3 节,与 UE 的 SBT 和材质 Hit Shader 一起讲——它们是同一件事的规范侧与实现侧。
核心结论可以用一句话概括:光追框架决定"光线去向、命中对象",Shader 决定"命中后计算的颜色值"。 在 UE 中,这两个角色被清晰地划分为两个系统:前者对应第 4 章的"光追场景 + 着色器绑定表",后者对应"材质 Hit Shader"(Closest Hit / Any Hit 与 UE 材质系统的绑定)。这条分界线是阅读后续 UE 实现代码的关键钥匙。
本章小结:光追以图像顺序从像素出发,经生成 -> 求交 -> 着色三步;反向发射是光路可逆下的高效采样策略;硬件光追拆为 RayGen / Intersection / ClosestHit / Miss 四阶段,光追框架与 Hit Shader 职责分离。
2. UE 渲染模块:光栅为主,光线为辅
2.0 一帧总览
一句话:UE 每帧先按光栅化画出画面,只有当某个效果"光栅化做不好"时,才临时把场景整理成光追可用的形式、额外发射一些光线去补,补完再降噪合成。
这段骨架的三层含义分别对应后文三章:整理场景与发射光线是第 4 章(通用光追系统);“间接光"这条路主要由第 3 章(Lumen)负责;“哪些效果需要额外光线、用什么开关控制"在第 5 章。
两个容易误解的点:一是光追不替代光栅化,它是插在主循环里的若干片段,关掉后画面仍然完整,只是少了那几类效果;二是**“有光追效果"不等于"每帧重建全部加速结构”**——几何的加速结构按静态 / Nanite / 动态三类分别维护,重建是增量的(4.2 节)。
第 1 章讲概念;本章进入 UE 工程实现。首先需要建立的整体认知是:UE 渲染器并未“以光追取代光栅化”,而是以光栅为主流程,将光追作为若干嵌入 Pass 执行。理解这一"嵌入"机制的三个关键维度是:代码组织位置(Renderer 模块)、Pass 编排方式(RDG)、以及光追 Pass 在主循环中的挂载点。
2.1 Renderer 模块与 RDG
UE 渲染器位于 Engine/Source/Runtime/Renderer/。核心实现代码集中在 Runtime/Renderer/Private/ 下,按功能拆分子目录。与光线追踪主题相关的目录与文件包括:
Engine/Source/Runtime/Renderer/Private/
DeferredShadingRenderer.{h,cpp} <- 延迟渲染主循环(所有光追 Pass 的最终编排入口)
RayTracing/ <- 通用硬件光追设施与独立 RT Pass
Lumen/ <- Lumen(全局光照 / 反射)实现
Nanite/ <- Nanite(含 Nanite 专用光追 BLAS 管理)
PathTracing.{h,cpp} <- 参考级 Path Tracing(收敛级完整光追)
MegaLights/ <- 5.6 新增灯光虚拟化系统(可选硬件光追)
ScreenSpaceDenoise.{h,cpp} <- 屏幕空间时空降噪器(光追效果降噪的统一入口)
自 UE 5.0 起,主渲染器废弃了"在普通函数内串行执行立即模式 RHI 调用"的旧模式,全面切换为RDG(Render Dependency Graph)声明式 Pass 编排:每个 Pass 在注册时显式声明输入与输出的 RDG 资源,RDG 内部构建依赖拓扑图,自动完成资源生命周期管理、依赖排序、资源屏障优化与 GPU Render-Pass 合并。RDG 的核心入口是 FRDGBuilder,头文件位于 Engine/Source/Runtime/RenderCore/Public/RenderGraphBuilder.h。渲染器的每一个步骤都注册为 RDG Pass,所有 RHI 调用被约束在 Pass 的执行回调内执行。光线追踪相关逻辑完全复用这套 RDG 体系,不存在独立的调度路径——唯一区别仅在于 Pass 内部执行的是光线追踪 GPU 阶段(RayGen / TraceRay / DispatchRays),而非常规的光栅化或计算着色器调度。
2.2 RHI 层的光追抽象:DXR 的封装
RHI(Render Hardware Interface)是 UE 对所有图形 API 的统一抽象层,位于 Engine/Source/Runtime/RHI/。硬件光追所需的资源对象——加速结构、光追管线状态、Shader 绑定表——在 RHI 层均有对应的抽象接口:FRHIRayTracingGeometry(BLAS)、FRHIRayTracingScene(TLAS)、FRHIRayTracingPipelineState 等,统一声明于 Engine/Source/Runtime/RHI/Public/RHIResources.h。
具体实现落在各平台的 RHI 后端。以桌面 D3D12 为例,Engine/Source/Runtime/D3D12RHI/Private/D3D12RayTracing.{h,cpp} 将 UE 的 RHI 抽象翻译为 DXR 原生调用,包括加速结构构建、DispatchRays 派发等。更高阶的执行优化(如 Shader Execution Reordering,SER)由渲染层通过控制台开关暴露(Lumen 侧对应 r.Lumen.HardwareRayTracing.ShaderExecutionReordering),实际生效与否取决于平台与驱动的能力支持,RHI 层在运行时以能力探测结果为最终依据。
UE 5.x 中硬件光追的主力后端为 D3D12 + DXR。光追功能的总开关是 r.RayTracing,它驱动渲染器内部 FFamilyPipelineState::bRayTracing 状态位;各类具体效果(阴影、AO、半透明等)在项目设置与 RaytracingOptions.h 中进一步细分独立开关。引擎代码中大量使用 IsRayTracingEnabled() / RHISupportsRayTracing*() 作为能力门禁,在光追不可用时零成本短路相关路径。
2.3 场景数据流与主循环挂载点
光线追踪需要遍历"完整场景”,因此对几何可见性的要求高于光栅化。UE 中场景侧的数据流向为:
FPrimitiveSceneInfo(CPU 端渲染代理)
|
v
FGPUScene(GPU 场景,存储实例数据 / 变换矩阵,挂载于 SceneRenderer::Scene->GPUScene)
|
v
FRayTracingScene(每帧重建的光追专用场景)
设计要点:UE 5 引入 GPUScene 后,光追场景的构建(第 4.1 节的 FRayTracingScene::Create)直接以 GPU 场景为输入,而非逐 Primitive 从 CPU 搬运数据。这一设计从根本上降低了场景构建的 CPU 开销——每帧只需将实例信息整理进加速结构,几何数据本身可跨帧缓存复用。
在延迟渲染器 FDeferredShadingSceneRenderer(DeferredShadingRenderer.cpp)的主循环中,有两个编排函数是光追链路的核心入口:
DispatchRayTracingWorldUpdates:收集本帧所有需要更新的光追几何与实例,提交 BLAS 构建与场景构建请求(Create+Build)。SetupRayTracingRenderingData:为每个 View 组装 Lumen / Nanite / 光源网格等光追所需的统一缓冲,并判断本帧是否存在 Inline 光追 Pass。
实际的各光追 Pass 派发则嵌入在光栅化主循环的对应阶段内,例如天空光采样阶段、半透明渲染阶段。这些 Pass 的执行条件通过一组 ShouldRender* 判断汇总为"本帧是否需要构建光追场景"的综合标志,用于在不需要光追时提前短路整条场景构建链路。这正是"光追是嵌入光栅主循环的 image-order 计算片段"这一论断在代码层面的直接体现。
本章小结:UE 以光栅为主流程,光追作为嵌入 Pass 执行;Renderer 模块通过 RDG(FRDGBuilder)统一调度 Pass;RHI 层抽象 DXR 调用;场景数据沿 FPrimitiveSceneInfo -> FGPUScene -> FRayTracingScene 流转,主循环由 DispatchRayTracingWorldUpdates 和 SetupRayTracingRenderingData 两个编排函数驱动。
3. Lumen:UE 处理"间接光"的第一层架构
一句话:间接光与反射是最"吃光追能力"的两件事,UE 把它们交给 Lumen;Lumen 是第 4 章那套通用光追基础设施的最大使用者,而不是基础设施本身。
本章与第 4 章的读法(先给结论,避免读反顺序):第 4 章的通用光追系统是基础设施层,负责加速结构与命中分派,服务所有消费者;Lumen 是它的首要租户。Lumen 既可以完全不发射硬件光线(软件模式:屏幕空间追踪 + 距离场追踪),也可以借用基础设施发射硬件光线(硬件模式)。因此 3.2 与 3.4 节里的"光线来源"应理解为一个可替换的部件;若更想先看底座,可先读 4.1、4.2 节再回本章。
从用户视角提问"UE 5 如何处理光线”,第一个答案往往不是某个"光追开关",而是 Lumen——它是 UE 当前全局光照(GI)与反射的默认架构。在深入通用光追系统之前,先建立对 Lumen 的完整认知,原因有三:
- Lumen 是 GI / 反射(多次弹射、间接光)这类"最依赖光追能力"问题的当前标准答案;
- Lumen 可以选择性启用硬件光追作为部分光线的来源,是通用 RT 系统的首要租户;
- 第 4 章的"通用光追系统"负责构建底层加速结构,供 Lumen(可选)与其他独立效果(阴影、AO、半透明等)共同复用。
3.1 为什么需要 Lumen
“间接光照”(Global Illumination, GI)是实时渲染中最具挑战的问题:一个像素的最终颜色不仅来自直接光照,还包含周围物体反弹而来的间接光,且反弹次数在理论上是无限的。在 Lumen 出现之前,实时引擎对这一问题的解决方案是一系列静态或近似手段(详见 3.3 节历史回顾):光照贴图烘焙(场景变更后需重新烘焙)、屏幕空间 GI(仅覆盖屏幕可见区域)、反射捕获体与 SSR(反射保真度有限)等。
Lumen 的定位是系统性补齐上述短板:提供全动态、可扩展的实时间接光照与反射方案——太阳方位可调、场景几何体可移动/销毁、物体位姿可动态变化,间接光照结果能够实时跟上,视觉目标逼近离线烘焙与离线光追的质量。Lumen 是 UE 5 的默认光照架构(延迟渲染器中 GI 与反射的"方法枚举"默认指向 Lumen,见 3.3 节)。
3.2 Lumen 的组成
Lumen 并非单一 Pass,而是一套场景表示 + 多级缓存 + 光线来源策略的组合体系。基于 5.6.1 源码(Engine/Source/Runtime/Renderer/Private/Lumen/),其核心可拆分为四个组件:
| 组成模块 | 核心作用 | 5.6.1 代表文件(短名) |
|---|---|---|
| 场景表示 | 将场景转换为"可供光线跟踪"的近似表示,避免对所有三角形做精确求交 | LumenScene.cpp、LumenMeshCards.cpp、LumenMeshSDFCulling.cpp |
| Screen Probes(屏幕探针) | 在屏幕空间稀疏布置探针,向四周发射光线,单帧内产出可用的间接光照结果 | LumenScreenProbeGather.cpp、LumenScreenProbeFiltering.cpp |
| Radiance Cache(辐照度缓存) | 将屏幕探针的结果缓存到世界空间并跨帧累积,使光照信息向屏幕不可见区域传播 | LumenRadianceCache.cpp |
| Surface Cache(表面缓存) | 在 Mesh 表面以卡片(Card)为单位存储辐照度与材质结果,供光线命中时直接查询 | LumenSurfaceCache.cpp、LumenSurfaceCacheFeedback.cpp |
三个缓存组件的协作关系可以直观地理解为:
Screen Probes -> 负责"当前可见像素"的快速出结果(屏幕空间,单帧内完成)
Radiance Cache -> 负责"不可见 / 远处 / 间接"信息的时空传播(世界空间,跨帧累积)
Surface Cache -> 光线命中 Mesh 表面时,直接查询"该表面当时的颜色"而无需重新着色
在场景表示层面,Lumen 采用了两项极具 UE 特色的近似技巧:
- 全局距离场(Global Distance Field / Mesh SDF):将场景网格近似为有符号距离场,光线通过"跟踪 SDF"能够以极低开销穿越大片空旷区域并快速逼近表面(
LumenMeshSDFCulling.cpp、LumenMeshCards.cpp)。 - Mesh Cards(网格卡片):将每个 Mesh 的"宏观光照状态"展开为若干张正交投影的卡片纹理,光线命中卡片即可采样——这正是 Surface Cache 的物理载体。
在光线来源策略上,Lumen 提供两种运行模式:
- 软件模式(默认):光线不经过硬件光追单元,而是采用屏幕空间追踪 + SDF 追踪的混合方案。Screen Probe 先对屏幕空间可见的几何发射光线(
LumenScreenProbeTracing.cpp),不可见区域则通过 SDF 在世界空间追踪补足。 - 硬件光追模式(可选):当平台支持且项目设置开启时,同一套 Lumen 管线将"发射光线"替换为真正的硬件光追调用(硬件光追相关实现见
LumenScreenProbeHardwareRayTracing.cpp、LumenRadianceCacheHardwareRayTracing.cpp、LumenReflectionHardwareRayTracing.cpp等)。硬件 RT 提供的并非"另一套 GI 求解器",而是更精确、更稳定的光线来源——这正是 UE 官方对"Lumen + RT"关系的定位:RT 是 Lumen 的可选加速器,而非独立的完整 GI 方案。
结构说明:软件模式与硬件模式的差异仅限于"光线来源层"——前者通过屏幕空间追踪 + SDF 追踪实现,后者通过 DXR 的 RayGen 实现(屏幕探针、辐照度缓存、反射三个采样环节各有独立的硬件 RayGen Shader);从"屏幕探针结果"往下的 Radiance Cache 与 Surface Cache 是两种模式共用的统一缓存结构,不随光线来源策略切换。
5.6.1 的硬件 RT 模式还有两项值得注意的工程细节:一是支持标准光追管线与 Inline 光追两种派发方式(控制台开关 r.Lumen.HardwareRayTracing.Inline);二是暴露了大量命中光照相关的细粒度开关(r.Lumen.HardwareRayTracing.HitLighting.* 族,包括命中处直接光、阴影模式、天空光、反射捕获参与度等),这些开关集中定义于 Lumen/LumenHardwareRayTracingCommon.cpp 顶部——若需要深入排查 Lumen 硬件 RT 的各类表现差异,该文件是首选的切入点。
3.3 Lumen 与"上代"渲染方案的差异
本节为架构演进回顾,聚焦历史方案与当前架构的对比,不涉及 5.6.1 代码的具体行为细节。
“上代"概念需要从两个维度分别考察:
(a)UE4.x / 早期 UE5 的实时间接光照方案谱系
| 历史方案 | 核心思路 | 主要限制 |
|---|---|---|
| 光照贴图烘焙(Lightmap) | 离线将间接光烘焙进 UV 贴图 | 完全静态;光照/几何变化均需重新烘焙 |
| 反射捕获体(Reflection Capture)+ 探针 | 将环境光烘焙为低分辨率 Cubemap | 仅能表现低频间接光,动态场景适应性差 |
| 屏幕空间 GI(SSGI) | 利用屏幕空间信息估算间接光 | 仅"看见"屏幕内像素;遮挡与屏幕外区域失效 |
| 距离场软阴影 / AO | 基于 Distance Field 近似计算阴影与 AO | 仅为 AO/阴影级别近似,不涉及 GI 求解 |
| 独立硬件光追 GI/反射 Pass(约 5.0-5.1) | 直接用 RT 渲染间接光与反射 | 蒙特卡洛噪声严重,每像素成本高,缺乏可扩展缓存机制 |
Lumen 相对于上述方案的根本突破在于多级缓存架构:它不在每个像素上执行完整的路径追踪,而是将"廉价的稀疏光线发射"与"跨帧、跨屏幕的缓存复用"相结合,使计算成本可控且场景保持完全动态。这正是 Lumen 能够将"接近离线质量的 GI"带入实时渲染的核心原因。
(b)独立 RT Pass 架构与 Lumen 的组织差异
早期 UE5 中,硬件光追的 GI 与反射以独立 Pass 的形式存在(如单独的 Ray Tracing GI、Ray Tracing Reflections 效果)。演进至 5.6.1,RayTracing/ 目录中已不存在独立的 GI / 反射 Pass 文件——两者在组织上完全并入 Lumen(反射对应 LumenReflections,GI 对应 Screen Probe / Radiance Cache 体系)。今日 RayTracing/ 目录保留的是"通用光追基础设施 + 非 GI 类效果”(阴影、AO、半透明、天空光等,详见第 4.4 节)。
渲染器内部的 GI / 反射 / AO “方法枚举"仍然保留了回退至上代方案的选项:
// DeferredShadingRenderer.h(5.6.1,节选)
enum class EDiffuseIndirectMethod { Disabled, SSGI, Lumen, Plugin };
enum class EAmbientOcclusionMethod { Disabled, SSAO, SSGI, RTAO };
enum class EReflectionsMethod { Disabled, SSR, Lumen };
具体含义为:GI 可在 关闭 / SSGI(上代屏幕空间方案)/ Lumen / 自定义插件 之间选择;AO 可在 SSAO / SSGI 附带 AO / 硬件光追 AO(RTAO)之间选择;反射可在 SSR / Lumen 之间选择。默认组合为完整的 Lumen 体系(Lumen GI + Lumen Reflections),项目可根据目标平台按需回退。这张枚举表同时是理解"Lumen 与通用光追职责边界"的坐标系:RTAO 属于通用光追效果,而 GI 与反射默认由 Lumen 体系负责。
3.4 Lumen 每帧工作流概览
进入通用光追系统之前,先用极简流程将 Lumen 的时间线固定在整体框架中(各环节的具体细节在第 4 章相关章节展开):
Lumen 的核心哲学可以概括为:“每帧只发射少量光线,将信息存入缓存跨帧复用。” 这一设计理念是理解 Lumen 体系的核心抓手——无论是性能优化还是问题排查,都可以从"光线发射的稀疏性策略"与"缓存的复用效率"这两个维度系统切入。
本章小结:Lumen 是 UE 默认的 GI/反射架构,通过场景表示(SDF + Mesh Cards)、屏幕探针、辐照度缓存、表面缓存四块组合实现全动态间接光;采用 Screen Probes -> Radiance Cache -> Surface Cache 的多级缓存流水线;软件(Screen+SDF)与硬件(DXR RayGen)双模式仅差异在光线来源层,缓存结构共用。
4. UE 通用光追系统设计
一句话:这一章是"底座”——它只负责两件事:把场景整理成能高效求交的加速结构,以及把"命中后该跑哪个着色器"组织成一张索引表;具体是谁在用(Lumen、阴影、AO、半透明)属于上层。
本章进入 UE 光追系统的核心:通用硬件光追基础设施。它是"光线与谁相交"这一问题的工程实现者——向下对接 D3D12RHI / DXR,向上服务两类消费者:Lumen(作为可选的硬件光线来源)与独立 RT 效果(阴影、AO、天空光、半透明等)。代码主要分布于 RayTracing/ 目录与 DeferredShadingRenderer.cpp 的编排函数中。
4.1 光追场景:FRayTracingScene 的每帧生命周期
UE 中的光追场景是场景级对象(挂载于 FScene,即 Scene->RayTracingScene),而非 View 级对象。在展开生命周期之前,先明确 DXR 加速结构的两个核心概念 BLAS 与 TLAS:
- BLAS(Bottom-Level,底层加速结构):将单个几何体的三角形组织为 BVH 树,仅描述"形状"(几何内部的层次关系),不包含世界位姿信息。
- TLAS(Top-Level,顶层加速结构):将"场景中的实例"组织为 BVH 树,每个实例引用一个 BLAS 并携带变换矩阵、实例掩码、命中分组索引等元数据,描述"哪个物体以何种位姿出现在场景中"。
两者涉及两种关键顺序,需严格区分:构建顺序为先 BLAS 后 TLAS(TLAS 的实例必须引用已就绪的 BLAS 地址);光线遍历顺序则恰好相反,先 TLAS 后 BLAS(先确定命中哪个实例,再通过实例变换矩阵将光线变换至该 BLAS 的局部空间,最终查找具体三角形)。这正是下方 Build 方法注释中"BLAS 已就绪时,这里主要做 TLAS 构建"的语义来源。核心接口声明见 RayTracing/RayTracingScene.h:
class FRayTracingScene {
// Create: 帧起始阶段,基于 FGPUScene 收集/计算需要入场景的实例与几何
void Create(FRDGBuilder& GraphBuilder, const FViewInfo& View,
const FGPUScene* GPUScene, ERDGPassFlags ComputePassFlags);
// Build: 真正提交加速结构构建(BLAS 就绪后,此处以 TLAS 构建为主)
void Build(FRDGBuilder& GraphBuilder, ERDGPassFlags ComputePassFlags,
FRDGBufferRef DynamicGeometryScratchBuffer);
// PostRender: 帧末收尾(统计复位 / 临时资源释放)
void PostRender(FRDGBuilder& GraphBuilder, ERDGPassFlags ComputePassFlags);
};
每帧生命周期可归纳为"复位 -> 几何/实例更新 -> 场景构建 -> 数据就绪 -> 消费使用 -> 帧末收尾"五个阶段,各阶段对应函数如下(FRayTracingScene 方法见 RayTracingScene.h,编排函数见 DeferredShadingRenderer.cpp):
| 阶段 | 对应函数 | 核心职责 |
|---|---|---|
| 复位 | FRayTracingScene::Reset | 清空内部数组,为新一轮实例收集做准备 |
| 几何/实例更新 | DispatchRayTracingWorldUpdates | 等待场景初始化任务;提交"待构建 BLAS"列表;处理 Nanite 与动态几何的更新请求 |
| 场景构建 | FRayTracingScene::Create + FRayTracingScene::Build | 基于 Scene->GPUScene 重建 TLAS 顶层场景 |
| 数据就绪 | SetupRayTracingRenderingData | 为各 View 组装 Lumen / Nanite / 光源网格等统一缓冲 |
| 消费使用 | 各光追 Pass(见 4.4 节) | 场景消费:阴影 / 天空光 / AO / 半透明 / Lumen 硬件光追 |
| 帧末收尾 | FRayTracingScene::PostRender | 释放临时资源、复位统计信息 |
整条链路的总开关由 FamilyPipelineState[&FFamilyPipelineState::bRayTracing](由 r.RayTracing 控制台变量驱动)控制。当整帧不存在任何需要执行的光追 Pass 时,整个场景构建链路会被完全短路——不会白白支付加速结构的构建与维护成本。
具体调用链(下表是 FDeferredShadingSceneRenderer::DispatchRayTracingWorldUpdates 内部的执行顺序,也是排查"加速结构没准备好"这类问题的第一现场):
| 顺序 | 调用 | 作用 |
|---|---|---|
| 1 | Nanite::GRayTracingManager.ProcessBuildRequests(GraphBuilder) | 处理 Nanite 侧已排队的 BLAS 构建请求 |
| 2 | GRayTracingGeometryManager->ForceBuildIfPending(GraphBuilder.RHICmdList, RayTracingScene.GeometriesToBuild) | 对本帧必须就绪的几何强制完成挂起的 BLAS 构建,输入来自 RequestBuildAccelerationStructure 排队的请求 |
| 3 | Nanite::GRayTracingManager.ProcessBuildRequests(GraphBuilder)(第二次) | 返回"本帧是否有 BLAS 被重建";为真时对相关视图调用 PathTracingInvalidate(),因为 Path Tracing 的跨帧样本累积要求几何在时间上一致(4.6 节) |
| 4 | DynamicGeometryUpdateManager->AddDynamicGeometryUpdatePass(...) | 注册动态几何(蒙皮 / 布料 / 顶点动画)的顶点更新 Pass,并输出本帧的 scratch buffer,供随后的 FRayTracingScene::Build 使用 |
| 5 | ((FRayTracingGeometryManager*)GRayTracingGeometryManager)->ResetVisibleGeometries() | 复位本帧的可见几何集合,供下一帧重新收集 |
除这条主链外还有两处调用点:一是 FDeferredShadingSceneRenderer::Render 里的 GRayTracingGeometryManager->PreRender() 与其后的 ProcessBuildRequests(GraphBuilder.RHICmdList)(源码此处留有 // TODO: should only process build requests once per frame,即当前实现每帧会处理两次请求);二是主循环中多次出现的 SetupRayTracingRenderingData(GraphBuilder),它是否需要执行由本函数的返回值 bNeedToSetupRayTracingRenderingData 与 Lumen 的两个谓词(Lumen::UseHardwareRayTracedSceneLighting / Lumen::UseAsyncCompute)共同决定,见 4.5 节。
4.2 几何与实例进入加速结构的机制
几何侧(BLAS) 按数据来源分为三类独立管理,实例侧(TLAS) 负责实例收集、剔除与掩码通道。UE 的具体实现如下:
几何侧(BLAS):三类来源的独立管理策略
| 几何来源 | 核心管理机制 |
|---|---|
| 常规静态网格 | FRayTracingGeometryManager(声明于 Engine 模块的 Rendering/RayTracingGeometryManager.h):请求侧 RequestBuildAccelerationStructure(Geometry, Priority, BuildMode) 入队,BoostPriority 调整排队顺序,RemoveBuildRequest 撤销;提交侧 ProcessBuildRequests(CmdList, bBuildAll) 批量执行;ForceBuildIfPending 用于在本帧内强制完成指定几何的挂起构建 |
| Nanite 网格 | Nanite::GRayTracingManager(Nanite/NaniteRayTracing.{h,cpp}):Add / Remove / AddVisiblePrimitive 维护参与光追的 Primitive,RequestUpdates 收集更新请求,Update + ProcessUpdateRequests 计算裁剪后可见的集群并生成 BLAS,ProcessBuildRequests 提交构建,EndFrame 收尾;ScheduledBuilds 是"本帧要构建的几何",PendingBuilds 的注释把未构建的几何归因于"每帧 BLAS 构建预算节流"(注释里用的是旧名 r.RayTracing.Nanite.MaxBlasBuildsPerFrame,实际 CVar 为 r.RayTracing.Nanite.MaxBuiltPrimitivesPerFrame) |
| 动态/蒙皮几何 | FRayTracingDynamicGeometryUpdateManager(Renderer/Public/RayTracingDynamicGeometryUpdateManager.h):AddDynamicGeometryToUpdate 登记需要逐帧更新顶点的几何,AddDynamicGeometryUpdatePass 申请 scratch buffer 并注册更新 Pass;旧的 DispatchUpdates / EndUpdate / Update 已在 5.5、5.6 相继标记弃用,新代码只需调用 AddDynamicGeometryUpdatePass |
Nanite 链路:UE 5.6 中 Nanite 网格参与硬件光追时,并不把原始高模塞进 BLAS,而是由 Nanite::GRayTracingManager 依据 Nanite 自身的裁剪结果,把"当前可见的集群集合"流式构建为 BLAS——光追场景下的几何裁剪收益因此与光栅化路径保持一致。
实例侧(TLAS):场景成员与可见性通道
TLAS 的输入是"实例列表",UE 通过一组专用工具对其进行管理:
Engine/Source/Runtime/Renderer/Private/RayTracing/
RayTracingInstance.{h,cpp} FRayTracingInstance:单个实例 = 几何 + 变换 + 掩码 + 标志位
RayTracingInstanceMask.{h,cpp} 实例掩码:通道可见性控制(主光追 / 阴影 / 天空光分层)
RayTracingInstanceCulling.{h,cpp} 实例剔除:每帧仅将"可能被需要的"实例放入 TLAS
RayTracingInstanceBufferUtil.cpp 实例数据 -> GPU Buffer 的组装工具
“实例掩码"是"阴影光线仅判断遮挡、无需材质计算"这一原则的工程化落地:通过掩码通道,同一份场景能够分别服务于"材质命中求解”(Closest Hit)与"遮挡测试"(Any Hit / 阴影光线),避免为阴影路径支付昂贵的材质着色开销。[7]
4.3 命中与材质:从 HitRecord 到 Shader 绑定表
一句话:软件模型里的"命中记录 + 多态分派",在 UE 里被拆成三件具体东西:draw 命令集、Hit Shader,以及一张"命中索引 -> 该跑哪个 Shader"的绑定表。
硬件光追的四个可编程阶段(1.4 节提到的规范侧清单,此处给出职责划分):
| 可编程阶段 | 在基本光追流程中的角色 |
|---|---|
| Ray Generation(射线生成) | 第 1 步:生成视线 / 阴影 / 反射光线,对应 1.2 节的"光线生成" |
| Intersection / Any Hit | 求交期间的命中决策(加速结构遍历本身是固定管线),对应 1.2 节的"求交" |
| Closest Hit | 找到最近交点后执行,对应第 3 步"着色"的入口 |
| Miss | 光线未击中任何物体时执行(返回背景色或采样天空光) |
四个阶段里,只有 Closest Hit 与 Any Hit 会随"命中到什么材质"而不同——这正是 UE 需要材质 Hit Shader 与 SBT 的原因。
第 1.2 节的软件光追模型中,HitRecord(记录命中表面、参数 t、法线)与 Surface::hit 的多态分派是核心抽象[5]。UE 的工程化版本将这一概念拆分为三个独立组件:
- 将"表面集合"物化为 GPU 端 Mesh Draw 命令集:
RayTracingMeshDrawCommands.cpp将场景网格转换为光追可用的 draw 命令集合(对应软件模型中"将一组物体视为复合 Surface"的 Group 组织方式[5])。 - 将"材质着色程序"物化为 Hit Shader:
RayTracingMaterialHitShaders.{h,cpp}为每个 mesh draw 生成对应的 Closest Hit / Any Hit Shader——其内部通过 UE 统一材质系统完成"命中后颜色计算"。材质管线完全复用FDeferredShadingSceneRenderer统一管理的着色流程。[6] - 将"命中后分派哪个 Shader"物化为 SBT(Shader Binding Table):
RayTracingShaderBindingTable.{h,cpp}将"几何 + 实例 + 材质"的笛卡尔积组装为 DXR 规范要求的着色器绑定表,使 GPU 命中时能够通过索引快速定位到正确的 Hit Shader。
| 软件光追基础模型 | UE 工程化实现 |
|---|---|
Scene 表面集合 | FGPUScene / FRayTracingScene(4.1、4.2 节) |
Surface.hit(ray) | BLAS 遍历(固定管线)+ 可编程命中阶段 |
HitRecord(s, t, n) | 命中数据 + RayTracingBarycentrics(重心坐标等) |
| 材质(决定颜色) | Closest Hit / Any Hit(RayTracingMaterialHitShaders) |
| 光源 / 光照方向 | RayTracingLighting(光照采样与光源网格) |
| 多态分派 | Shader Binding Table(命中索引 -> 正确 Shader) |
这张表把第 1 章的概念模型与第 4 章的代码实体一一对应。1.4 节给出的职责分界线在这里可以直接落地:几何层(BLAS / TLAS / SBT 索引)决定"光线碰到谁",SBT 与 Hit Shader 决定"碰到后计算多少光照"。
4.4 应用 Pass 全景
一句话:这些效果没有一个是"整屏的光追开关",它们各自挂在主循环里最合适的阶段(光照循环、环境光、AO、半透明),关掉某一个只是少了那一类效果。
UE 中直接消费通用光追系统的效果以 RayTracing/ 目录下的实现文件为准。每个效果都有两道门:通用门(平台是否支持光追、是否允许该视图用光追)与效果门(ShouldRender* 系列函数,见 RaytracingOptions.h)。逐项梳理如下:
| 效果名称 | 挂载点 / 调用链 | 效果门与开关 | 实现说明 |
|---|---|---|---|
| Ray Traced Shadows(光追阴影) | LightRendering.cpp 在光照循环内逐光源调用 RenderRayTracingShadows(RayTracingShadows.cpp) | ShouldRenderRayTracingShadows / ShouldRenderRayTracingShadowsForLight;r.RayTracing.Shadows(默认 0,即仍用传统阴影贴图),另有 r.RayTracing.Shadows.Lights.* 按光源类型细分 | 非独立全屏 Pass,而是作为阴影渲染的可选模式嵌入逐光源渲染流程;降噪也发生在光照循环内(DenoiseShadowVisibilityMasks) |
| Ray Traced Ambient Occlusion | RayTracingAmbientOcclusion.cpp;渲染器侧通过 EAmbientOcclusionMethod::RTAO 枚举接入 | ShouldRenderRayTracingAmbientOcclusion;r.RayTracing.AmbientOcclusion(默认 -1,由后处理体积驱动;0 关 / 1 开) | 输出与 ScreenSpaceDenoise 结合执行时空降噪 |
| Ray Traced Sky Light(天空光采样) | 渲染器在天空光采样阶段调用 RenderRayTracingSkyLight(RaytracingSkylight.cpp) | ShouldRenderRayTracingSkyLight;r.RayTracing.SkyLight(默认 0) | 以 RT 精确求交取代"反射捕获体 + 低分辨率环境贴图"的近似,额外输出 SkyLightHitDistanceTexture 等缓冲;它不能按视图临时开关(原因见 4.9 节) |
| Ray Traced Translucency(半透明光追) | 渲染器在半透明阶段调用 RenderRayTracingTranslucency(RayTracingTranslucency.cpp) | ShouldRenderRayTracingTranslucency;r.RayTracing.Translucency(默认 -1,由后处理体积驱动) | 由 RenderRayTracingPrimaryRaysView(RayTracingPrimaryRays.cpp)提供"主光线"求交与着色,处理半透明折射等光栅化难以精确表达的效果 |
| 配套实现(非独立效果) | RayTracingDecals.{h,cpp}、RayTracingBarycentrics.cpp、RayTracingDebug.cpp、RayTracingTraversalStatistics.{h,cpp} | 无独立 ShouldRender* 门禁 | 贴花在光追下的求交支持、重心坐标验证、可视化调试与遍历性能统计 |
效果门之上还有一层公共判定:ShouldRenderRayTracingEffect(bEffectEnabled, CompatibilityFlags, View) 会先检查 IsRayTracingEnabled(ShaderPlatform) 与 View.IsRayTracingAllowedForView(),再由 CompatibilityFlags 判断该效果能否在当前可用的派发方式上运行——效果必须声明支持"全管线"或"Inline"至少之一,否则直接不执行(详见 4.10 节)。
编号表示这些挂载点在一次渲染中的先后位置(同一阶段内部可能并行)。
4.5 光追系统与 Lumen 及其他系统的租户关系
一句话:底座只有一套,用它的有好几家;每家在"整理场景"这一步各自提需求,中间不需要谁认识谁。
本节是理解 UE 光追架构的核心:通用光追场景(第 4.1 节)是"基础设施层",Lumen 与各类独立效果仅是它的不同"租户"。
- Lumen 硬件模式:当
r.Lumen.HardwareRayTracing开启且平台支持时,Lumen 通过Lumen::UseHardwareRayTracedSceneLighting(声明于Lumen/Lumen.h,实现于Lumen/LumenSceneLighting.cpp)参与调度编排:FDeferredShadingSceneRenderer::Render中SetupRayTracingRenderingData的多处调用点都带Lumen::UseHardwareRayTracedSceneLighting(ViewFamily)或Lumen::UseAsyncCompute(ViewFamily)条件,即**“底座什么时候需要被组装好"这件事是由租户决定的**。Lumen 还向场景构建器传递定制化需求:场景选项的构造函数FSceneOptions::FSceneOptions(...)(RayTracing/RayTracing.cpp)依次调用LumenHardwareRayTracing::SetRayTracingSceneOptions(View, DiffuseIndirectMethod, ReflectionsMethod, SceneOptions)(Lumen/LumenHardwareRayTracingCommon.h)、RayTracingShadows::SetRayTracingSceneOptions(View.bHasRayTracingShadows, SceneOptions),再按半透明与 Path Tracing 的需求补齐——同一份场景选项由多个消费者分别写入,谁需要哪类几何就打开哪个开关,避免为单一用途构建整场景。 - Lumen 硬件 Shader 的结构印证:
FLumenHardwareRayTracingShaderBase(同文件中定义)是所有 Lumen 硬件 RayGen 的公共基类,其参数列表中同时出现FNaniteRayTracingUniformParameters(Nanite 场景数据)与FRayTracingLightGrid(通用光源网格)——这一设计再次印证"Lumen 硬件光追 = 通用光追场景 + 通用光源数据"的复用架构。 - Inline 光追的调度:5.6 中的光追并不总是采用"全管线派发"模式。Lumen 可选 Inline 光追(在 Compute Shader 中内嵌
TraceRayInline调用,开关r.Lumen.HardwareRayTracing.Inline,默认 1),判定谓词是Lumen::UseHardwareInlineRayTracing(ViewFamily);SetupRayTracingRenderingData在编排时会检测本帧是否存在任何 Inline 光追 Pass。它的代价与收益见 4.10 节。 - 5.6 新增的 RT 消费者:
MegaLights(灯光虚拟化系统,MegaLights/MegaLightsRayTracing.cpp)可选择性使用 Inline 光追评估大规模光源的直接光照;HairStrands(毛发系统)与HeterogeneousVolumes(异质体积渲染)也通过IsRayTracingEnabled()门禁接入通用光追体系(详见各系统实现中的入口判断)。
核心结论:Lumen 决定"需要多少间接光照”,通用光追系统决定"光线如何发射、命中如何计算";5.6 中越来越多的子系统(Lumen、MegaLights、毛发、体积)选择复用这套基础设施,而非各自独立造轮。
4.6 Path Tracing(参考级完整光追)
第 1.3 节提到,实时光追通过各类近似手段替代了"完整的多弹射积分"。UE 同时保留了"老老实实做积分"的完整参考实现——Path Tracing(Engine/Source/Runtime/Renderer/Private/PathTracing.cpp)。
- 它作为可选渲染模式存在(非实时路径默认启用),逐像素逐样本执行完整的多弹射路径追踪直至收敛,输出近乎离线渲染质量的参考图像。
- 它的核心价值之一是作为正确性验证的参照系:开发与调试光追 / Lumen 效果时,可对照 Path Tracing 的输出判断偏色、泄漏、噪声来源等问题。
- 它内置独立的时空降噪器(
PathTracingSpatialTemporalDenoising.cpp),通过跨帧样本累积压低蒙特卡洛噪声。 - 它在架构上甚至绕开了实时那套"光栅为主"的主循环(代码注释明确指出 Path Tracing 使用自己的光源管理器,不需要常规的实时光源网格)。值得注意的联动点:当 Nanite BLAS 重建时会触发
ViewState->PathTracingInvalidate(),因为 Path Tracing 的逐帧样本累积要求几何数据在时间上保持一致。
4.7 每帧数据流时序
将第 4.1-4.5 节的各环节拼接为完整的时序图(以"本帧存在 RT / Lumen 硬件 Pass"为前置条件):
4.8 降噪体系
一句话:实时每像素只发很少的光线,噪声必然存在;UE 备了三套降噪通道,各自匹配不同形态的结果。
实时光追每像素通常仅发射 1 根(或极少量)光线,蒙特卡洛噪声是固有属性。UE 5.6.1 的降噪体系分为三层:
- 通用屏幕空间时空降噪器:
ScreenSpaceDenoise.{h,cpp}定义的IScreenSpaceDenoiser是 RT 阴影 / AO / 天空光 / 半透明的统一降噪通道。它按"逐像素信号 + 命中距离 / 掩码"的接口组织输入输出(FShadowVisibilityInputs、FAmbientOcclusionInputs、FReflectionsInputs、FDiffuseIndirectInputs等),并提供三个运行模式(EMode):Disabled/DefaultDenoiser(用IScreenSpaceDenoiser::GetDefaultDenoiser())/ThirdPartyDenoiser(用全局GScreenSpaceDenoiser,可被插件替换)。调用站在各效果自己那边:例如光追阴影的降噪发生在光照循环内(LightRendering.cpp调用DenoiseShadowVisibilityMasks),AO 与天空光分别在RayTracingAmbientOcclusion.cpp、RaytracingSkylight.cpp内调用对应入口;r.Shadow.Denoiser(默认 2)与r.AmbientOcclusion.Denoiser(默认 2)这类开关选择的正是上面三种模式(注意后者的名字里没有RayTracing前缀)。 - Lumen 专用滤波管线:Lumen Screen Probe 的结果重建与缓存复用强耦合,因此不走上面的通用通道,而是自己的滤波管线(
LumenScreenProbeFiltering.cpp等);它只复用ScreenSpaceDenoise.h里的信号纹理结构(FSSDSignalTextures)作为数据容器。 - Path Tracing 的独立时空降噪:详见 4.6 节。
噪声问题从反面塑造了架构决策:独立 RT GI/反射(UE5 早期方案)最终被 Lumen 取代的核心原因之一,正是"每像素发射大量光线 + 强力降噪"的成本结构远不如"稀疏光线发射 + 缓存复用"的方案健康(呼应 3.3 节的架构演进分析)。
4.9 边界条件与工程限制
一句话:光追的可用性由平台能力决定,成本由"进场景的实例规模"决定,而它几乎从不全量开启。
- 平台能力面:硬件光追需要 D3D12 + DXR 兼容的 GPU(RHI 层对应
D3D12RayTracing.{h,cpp}的实现)。Inline 光追、Shader Execution Reordering(SER)等属于更细粒度的平台相关能力,引擎通过RHISupportsRayTracing*系列函数在运行时逐能力探测;渲染层的控制台开关(如r.Lumen.HardwareRayTracing.Inline/...ShaderExecutionReordering)仅在与硬件能力匹配时才真正生效。 - 成本构成面:TLAS 每帧重建 + BLAS 增量更新的总代价与可见实例数强相关。
RayTracingInstanceCulling(实例剔除)、掩码通道拆分、r.RayTracing.Culling(取值 0-3,分别对应不剔除 / 只剔除相机后方的远近与立体角组合策略)共同目标是把"进入光追场景"的实例规模压到最小。凡是本帧用不到的物体,都应通过剔除机制挡在光追场景之外。可参考的量级:r.Lumen.HardwareRayTracing的帮助文本明确提示,场景实例超过 10 万时硬件光追的场景更新开销显著。 - 与 Nanite 的联动面:5.6 中 Nanite 通过
Nanite::GRayTracingManager参与硬件光追(BLAS 由可见集群流式构建,详见 4.2 节);Lumen 硬件 Shader 为此提供了NANITE_RAY_TRACING的专用 permutation。 - 一个不能按视图开关的效果:光追天空光要求
FScene的 base pass 着色器按"是否启用光追天空光"配置,而该配置变化会触发静态网格元素重建(bScenesPrimitivesNeedStaticMeshElementUpdate),无法逐帧或逐视图切换——因此它被单独实现为ShouldRenderRayTracingSkyLightEffect(),属全局开关,常见于影视级捕获而非游戏运行时(DeferredShadingRenderer.cpp中的注释说明了这一限制)。 - 帮助文本与默认值可能不一致:
r.RayTracing.Culling的帮助文本写"0: Culling disabled (default)",而实际默认参数是 3;r.RayTracing.Shadows.SamplesPerPixel的默认参数是 -1(表示交给默认值 1)。这类不一致在排查时容易误判,判断应以声明处的默认参数为准。
4.10 派发模式与执行相干性
一句话:同一套光线既可以用独立管线派发,也可以内嵌在普通 compute shader 里;选哪种取决于硬件能力与相干性优化。
全管线 vs Inline。效果本身要声明自己支持哪种派发方式:ERayTracingPipelineCompatibilityFlags(RaytracingOptions.h)只有两个取值 FullPipeline 与 Inline。门禁函数 ShouldRenderRayTracingEffectInternal(DeferredShadingRenderer.cpp)在此基础上再叠加平台与开关:
| 判定 | 条件 |
|---|---|
| 允许全管线 | GRHISupportsRayTracingShaders 且 r.RayTracing.AllowPipeline 且效果声明了 FullPipeline |
| 允许 Inline | GRHISupportsInlineRayTracing 且 r.RayTracing.AllowInline 且效果声明了 Inline |
| 两者都不允许 | 该效果直接不执行 |
也就是说 Inline 光追的一个实际作用,是让效果在"支持 Inline 但不支持完整光追管线"(或反之)的设备上仍能存活。此外 r.RayTracing.ForceAllRayTracingEffects 可以强制开关全部效果,用于验证问题是否出在能力探测上。
Inline 模式有两条具体约束与收益(均来自源码注释):
- 约束:当前的 Inline 实现要求线程组与 wave 一一对应(
LumenHardwareRayTracingCommon.cpp的GetThreadGroupSizeInternal注释),因此线程组尺寸不能自由选择。 - 收益:Lumen 的异步计算路径"只能用于 Inline 光追,或 RHI 支持异步光追派发"(
Lumen.cpp)——即 Inline 让硬件光追结果可以更自然地并入 Async Compute 队列。
SER(Shader Execution Reordering)。开关 r.Lumen.HardwareRayTracing.ShaderExecutionReordering 默认 true,帮助文本的口径是:用它改善材质求值的相干性,对使用大量材质的场景可能有性能收益,硬件不支持时该开关无效果——所以"开了没变化"通常不是 bug,而是能力探测未通过(可用 5.3 节的排查路径确认)。
本章小结:通用光追系统向下管理 FRayTracingScene 每帧生命周期(Reset -> Build -> Pass -> PostRender)、BLAS 三类管理(静态 / Nanite 流式 / 动态蒙皮)与 TLAS 剔除掩码,向上通过 SBT 命中分派服务 RT Pass 清单(阴影 / AO / 天空光 / 半透明等)与多租户(Lumen / MegaLights / 毛发 / 体积),配合三层降噪、两种派发模式与平台能力约束,构成完整的硬件光追基础设施。
5. 工程视图:要动的开关、常见翻车与排查入口
一句话:这一章不引入新机制,只把前面四章的结论按"谁在什么情况下会用到"重排成三类表——该动哪些开关、哪些现象最常见、出了问题先看哪里。
光追不是单个开关,而是链条上的若干道门;开关拨了没反应,通常是上游某道门没开。本章默认值均取自 5.6 源码的变量声明处;标 (只读) 的变量只能在启动时设置(ECVF_ReadOnly),运行时用控制台改无效。
5.1 要动的开关(按角色)
(a)美术 / 地编:影响画面表现
| 想改变什么 | 关键项 | 默认值 | 说明 |
|---|---|---|---|
| 是否整体允许光追 | r.RayTracing | 0,(只读) | 总开关只决定"允许不允许",不代表每类效果都开;需在启动时设置 |
| 间接光 / 反射方案 | EDiffuseIndirectMethod / EReflectionsMethod | Lumen | 可回退 SSGI / SSR(3.3 节) |
| 光追阴影 | r.RayTracing.Shadows | 0 | 默认仍用阴影贴图;打开后阴影精确,但成本与噪声上升 |
| 光追天空光 | r.RayTracing.SkyLight | 0 | 不能逐视图开关(4.9 节),面向影视级捕获 |
| 光追 AO | r.RayTracing.AmbientOcclusion | -1(后处理体积驱动) | 效果门还会要求 AO 强度大于 0 |
| 光追半透明 | r.RayTracing.Translucency | -1(后处理体积驱动) | 默认走光栅化半透明 |
(b)TA / 技术美术:质量与性能取舍
| 想改变什么 | 关键项 | 默认值 | 说明 |
|---|---|---|---|
| Lumen 的光线来源 | r.Lumen.HardwareRayTracing | 1 | 平台不支持时自动回退软件模式;实例超过 10 万的场景,场景更新开销显著 |
| 命中处是否实时着色 | r.Lumen.HardwareRayTracing.LightingMode | 0 | 0 走 Surface Cache;1 / 2 在命中点实时算光照,质量升、成本显著升 |
| 派发方式 | r.Lumen.HardwareRayTracing.Inline | 1 | Inline 更易并入 Async Compute(4.10 节) |
| 执行相干性 | r.Lumen.HardwareRayTracing.ShaderExecutionReordering | true | 硬件不支持时该开关无效果 |
| 进入光追场景的实例规模 | r.RayTracing.Culling | 3(取值 0-3) | 帮助文本把 0 写成 default,与声明处不一致(4.9 节) |
| 阴影 / AO 降噪算法 | r.Shadow.Denoiser、r.AmbientOcclusion.Denoiser | 均 2 | 后者名字里没有 RayTracing 前缀,容易搜错(4.8 节) |
(c)程序:能力门与调试入口
| 想做什么 | 关键项 / 函数 | 默认值 | 说明 |
|---|---|---|---|
| 判断是不是能力探测挡掉了效果 | r.RayTracing.ForceAllRayTracingEffects | -1 | 设 0 / 1 强制关 / 开全部光追效果,绕过各效果门(4.10 节) |
| 允许或禁止某种派发方式 | r.RayTracing.AllowPipeline、r.RayTracing.AllowInline | 均 1 | 效果必须声明对应的 ERayTracingPipelineCompatibilityFlags 才会执行 |
| 观察遍历开销与命中分派 | RayTracingTraversalStatistics.{h,cpp}、RayTracingDebug.cpp | 无独立门 | 遍历统计与可视化调试 |
5.2 常见翻车
| 现象 | 最可能的成因 | 先动哪里 |
|---|---|---|
| 打开光追后画面完全没变化 | 总开关 r.RayTracing 默认 0 且只读,未在启动时设置;或平台不支持光追 | r.RayTracing(启动参数 / 项目设置)-> 4.10 节的能力门 |
| 某一类效果没出现 | 该效果自己的门没开:阴影 / 天空光默认 0,AO 与半透明默认 -1 由后处理体积驱动 | 5.1 节 (a) 逐项对照默认值 |
| Lumen 看起来不受光追影响 | Lumen 默认走软件模式;硬件模式要求平台支持且 r.Lumen.HardwareRayTracing 开启 | 4.5 节、5.1 节 (b) |
| 开了 SER 性能没有变化 | 硬件不支持 SER,开关不产生效果 | 4.10 节 |
| 天空光无法按视图或按帧临时切换 | 该效果是全局开关,切换会触发 base pass 重配置与静态网格元素重建 | 4.9 节 |
| 实例规模变大后帧率骤降 | 硬件光追的场景更新成本与实例数强相关,超过 10 万实例时显著 | 4.9 节;检查剔除设置 |
| Nanite 物体在光追里闪烁或缺失 | 加速结构尚未就绪:Nanite BLAS 受帧预算节流,PendingBuilds 跨帧累积 | 4.2 节、5.3 节 |
| Path Tracing 画面跳变或重影 | BLAS 重建触发 PathTracingInvalidate(),跨帧样本累积被清空 | 4.6 节 |
| 噪声残留 | 降噪通道或模式选择不合适 | 4.8 节 |
5.3 排错谱系
按"现象 -> 第一现场(开关 / 谓词)-> 第二现场(函数)-> 落地文件"的顺序定位:
| 现象 | 第一现场 | 第二现场 | 落地文件 |
|---|---|---|---|
| 光追完全没生效 | r.RayTracing、IsRayTracingEnabled()、GRHISupportsRayTracingShaders | ShouldRenderRayTracingEffectInternal | DeferredShadingRenderer.cpp |
| 某个效果不执行 | 该效果的 ShouldRender* 与其 CompatibilityFlags | ShouldRenderRayTracingEffect | RaytracingOptions.h |
| 加速结构没准备好、几何缺失 | RayTracingScene.GeometriesToBuild | DispatchRayTracingWorldUpdates(五步链,4.1 节) | DeferredShadingRenderer.cpp、RayTracingScene.h |
| Nanite 光追几何跨帧闪烁 | ScheduledBuilds / PendingBuilds | Nanite::GRayTracingManager::ProcessBuildRequests | NaniteRayTracing.h |
| 动态物体(蒙皮 / 布料)光追几何不更新 | AddDynamicGeometryToUpdate | AddDynamicGeometryUpdatePass | RayTracingDynamicGeometryUpdateManager.h |
| Lumen 硬件光追没生效 | r.Lumen.HardwareRayTracing、Lumen::UseHardwareInlineRayTracing | SetupRayTracingRenderingData 的调用条件 | LumenSceneLighting.cpp、DeferredShadingRenderer.cpp |
| 命中处光照表现异常 | r.Lumen.HardwareRayTracing.LightingMode、...HitLighting.* 族 | 命中光照开关集中定义处 | LumenHardwareRayTracingCommon.cpp |
| 光追天空光泄漏或关不掉 | ShouldRenderRayTracingSkyLightEffect() | base pass 重配置 bScenesPrimitivesNeedStaticMeshElementUpdate | DeferredShadingRenderer.cpp |
| 实例过多导致卡顿 | r.RayTracing.Culling | RayTracingInstanceCulling | RayTracingInstanceCulling.cpp |
本章小结:开关分三层(总门 / 效果门 / 能力门),且多处默认值是"关闭"或"交给后处理体积",因此"没效果"最常见的原因是门没开,而非功能异常;排错沿"开关 -> 谓词 -> 函数 -> 文件"逐级下沉,可覆盖绝大多数"光追不生效"类问题。
6. 附录
6.1 源码文件索引
正文采用"文件短名"引用,本节提供短名到引擎相对路径的完整映射。
| 短名 | 相对引擎根路径 |
|---|---|
DeferredShadingRenderer.cpp | Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp |
DeferredShadingRenderer.h | Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.h |
RayTracingScene.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingScene.{h,cpp} |
RayTracingInstance*.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingInstance*.{h,cpp} |
RayTracingInstanceCulling.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingInstanceCulling.{h,cpp} |
RayTracingInstanceMask.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingInstanceMask.{h,cpp} |
RayTracingMaterialHitShaders.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingMaterialHitShaders.{h,cpp} |
RayTracingShaderBindingTable.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingShaderBindingTable.{h,cpp} |
RayTracingMeshDrawCommands.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingMeshDrawCommands.cpp |
RayTracingShadows.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingShadows.cpp |
RayTracingAmbientOcclusion.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingAmbientOcclusion.cpp |
RayTracingPrimaryRays.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingPrimaryRays.cpp |
RayTracingTranslucency.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingTranslucency.cpp |
RaytracingSkylight.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RaytracingSkylight.cpp |
RayTracingDecals.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingDecals.{h,cpp} |
RayTracingBarycentrics.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingBarycentrics.cpp |
RayTracingDebug.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingDebug.cpp |
RaytracingOptions.h | Engine/Source/Runtime/Renderer/Private/RayTracing/RaytracingOptions.h |
RayTracingDynamicGeometryUpdateManager.cpp | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingDynamicGeometryUpdateManager.cpp |
NaniteRayTracing.{h,cpp} | Engine/Source/Runtime/Renderer/Private/Nanite/NaniteRayTracing.{h,cpp} |
LightRendering.cpp | Engine/Source/Runtime/Renderer/Private/LightRendering.cpp |
ScreenSpaceDenoise.{h,cpp} | Engine/Source/Runtime/Renderer/Private/ScreenSpaceDenoise.{h,cpp} |
LumenScreenProbeHardwareRayTracing.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/LumenScreenProbeHardwareRayTracing.cpp |
LumenRadianceCacheHardwareRayTracing.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/LumenRadianceCacheHardwareRayTracing.cpp |
LumenReflectionHardwareRayTracing.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/LumenReflectionHardwareRayTracing.cpp |
LumenHardwareRayTracingCommon.{h,cpp} | Engine/Source/Runtime/Renderer/Private/Lumen/LumenHardwareRayTracingCommon.{h,cpp} |
LumenScreenProbeGather.cpp 等 | Engine/Source/Runtime/Renderer/Private/Lumen/(Screen Probe 系列文件) |
LumenRadianceCache.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/LumenRadianceCache.cpp |
LumenSurfaceCache.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/LumenSurfaceCache.cpp |
LumenMeshCards.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/LumenMeshCards.cpp |
PathTracing.cpp | Engine/Source/Runtime/Renderer/Private/PathTracing.cpp |
PathTracingSpatialTemporalDenoising.cpp | Engine/Source/Runtime/Renderer/Private/PathTracingSpatialTemporalDenoising.cpp |
MegaLightsRayTracing.cpp | Engine/Source/Runtime/Renderer/Private/MegaLights/MegaLightsRayTracing.cpp |
D3D12RayTracing.{h,cpp} | Engine/Source/Runtime/D3D12RHI/Private/D3D12RayTracing.{h,cpp} |
RHIResources.h | Engine/Source/Runtime/RHI/Public/RHIResources.h |
Lumen.h、LumenSceneLighting.cpp | Engine/Source/Runtime/Renderer/Private/Lumen/Lumen.h、Engine/Source/Runtime/Renderer/Private/Lumen/LumenSceneLighting.cpp |
RayTracingGeometryManager.h | Engine/Source/Runtime/Engine/Public/Rendering/RayTracingGeometryManager.h |
RayTracingDynamicGeometryUpdateManager.h | Engine/Source/Runtime/Renderer/Public/RayTracingDynamicGeometryUpdateManager.h |
RayTracingTraversalStatistics.{h,cpp} | Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingTraversalStatistics.{h,cpp} |
反向索引(按问题找文件)
| 需求 / 问题 | 首选文件 |
|---|---|
| 光追总开关与派发门禁 | DeferredShadingRenderer.cpp、RaytracingOptions.h |
| 加速结构生命周期与实例剔除 | RayTracingScene.{h,cpp}、RayTracingInstanceCulling.{h,cpp} |
| 材质命中与 SBT 组织 | RayTracingMaterialHitShaders.{h,cpp}、RayTracingShaderBindingTable.{h,cpp} |
| Lumen 光线来源与命中光照 | LumenHardwareRayTracingCommon.{h,cpp} |
| 降噪统一入口 | ScreenSpaceDenoise.{h,cpp} |
| Nanite 光追 BLAS 管理 | NaniteRayTracing.{h,cpp} |
| 动态几何顶点更新 | RayTracingDynamicGeometryUpdateManager.h |
| 平台能力探测与 DXR 翻译 | D3D12RayTracing.{h,cpp}、RHIResources.h |
6.2 控制台变量 / 设置速查
| 变量名 | 默认值 | 作用说明 | 定义出处 |
|---|---|---|---|
r.RayTracing | 0(只读) | 光追总开关,驱动 bRayTracing 管线状态位 | DeferredShadingRenderer.cpp |
r.RayTracing.AllowPipeline | 1 | 允许全管线光追派发 | DeferredShadingRenderer.cpp |
r.RayTracing.AllowInline | 1 | 允许 Inline 光追派发 | DeferredShadingRenderer.cpp |
r.RayTracing.ForceAllRayTracingEffects | -1 | 强制关 / 开全部光追效果(-1 表示不强制) | DeferredShadingRenderer.cpp |
r.RayTracing.Shadows | 0 | 用光追阴影取代传统阴影贴图 | LightRendering.cpp |
r.RayTracing.AmbientOcclusion | -1 | 光追 AO(-1 由后处理体积驱动) | RayTracingAmbientOcclusion.cpp |
r.RayTracing.AmbientOcclusion.SamplesPerPixel | -1 | 每像素采样数(-1 表示取默认值 1) | RayTracingAmbientOcclusion.cpp |
r.RayTracing.SkyLight | 0 | 光追天空光(全局开关,不可逐视图) | RaytracingSkylight.cpp |
r.RayTracing.Translucency | -1 | 光追半透明(-1 由后处理体积驱动) | RayTracingTranslucency.cpp |
r.RayTracing.Culling | 3(取值 0-3) | 光追实例剔除策略 | RayTracingInstanceCulling.cpp |
r.RayTracing.Nanite.MaxBuiltPrimitivesPerFrame | 8388608(约 8M 三角形) | Nanite BLAS 每帧构建预算(节流来源);同族还有 MaxStagingBufferSizeMB(1024)、BLASScratchSizeMultipleMB(64) | NaniteRayTracing.cpp |
r.RayTracing.Nanite.MaxBlasBuildsPerFrame | 不存在 | 旧名,仅残留于 NaniteRayTracing.h 的注释;实际控制项见上一行(待高版本源码复核) | — |
r.Lumen.HardwareRayTracing | 1 | Lumen 启用硬件光追(需平台能力支持) | LumenHardwareRayTracingCommon.cpp |
r.Lumen.HardwareRayTracing.Inline | 1 | Lumen 硬件光追切换为 Inline 派发模式 | 同上 |
r.Lumen.HardwareRayTracing.LightingMode | 0 | 命中处光照计算模式(0 走 Surface Cache) | 同上 |
r.Lumen.HardwareRayTracing.HitLighting.DirectLighting | 1 | 命中处直接光是否实时计算 | 同上 |
r.Lumen.HardwareRayTracing.HitLighting.ShadowMode | 2 | 命中处阴影模式(0 关 / 1 硬 / 2 软) | 同上 |
r.Lumen.HardwareRayTracing.HitLighting.Skylight | 2 | 命中处天空光(2 表示仅独立 Lumen 反射启用) | 同上 |
r.Lumen.HardwareRayTracing.ShaderExecutionReordering | true | 启用 SER(需硬件能力支持) | 同上 |
r.Shadow.Denoiser | 2 | 阴影降噪算法模式(0 关 / 1 渲染器默认 / 2 可被插件替换) | LightRendering.cpp |
r.AmbientOcclusion.Denoiser | 2 | AO 降噪算法模式(名字中无 RayTracing 前缀) | RayTracingAmbientOcclusion.cpp |
使用提示(易误判项):一是
r.RayTracing.Culling的帮助文本写 “0: Culling disabled (default)",而声明处的默认参数是 3;二是r.RayTracing.AmbientOcclusion.SamplesPerPixel的默认参数 -1 表示取默认值 1,而非"关闭”;三是r.Shadow.Denoiser与r.AmbientOcclusion.Denoiser的帮助文本都把第 0 项写成 “(default)",而两者的声明默认值都是 2;四是r.RayTracing.Nanite.MaxBlasBuildsPerFrame只在NaniteRayTracing.h的注释里出现,渲染器源码中没有该 CVar 声明——它已是旧名,实际控制项为r.RayTracing.Nanite.MaxBuiltPrimitivesPerFrame(默认 8M 三角形),不要把旧名当作可调参数依赖(待高版本源码复核)。控制台变量的语义与默认值一律以声明处为准,帮助文本只作线索。
6.3 参考
正文中以 [1] 形式标注的引用编号,对应下列条目;全部内容出自《计算机图形学基础(第 5 版)》(Fundamentals of Computer Graphics, 5th ed.)。
[1]: 第 3 章"光栅图像” —— 像素定义、光栅概念与图像的函数抽象。
[2]: 第 4 章"光线追踪"开篇 —— 对象顺序与图像顺序两种渲染组织方式的对比。
[3]: 第 4.1 节"基本光追算法" —— 光线生成、求交、着色三步法的完整流程。
[4]: 第 4.3 节"计算视图光线" —— 视线参数方程 p(t) = e + t(s - e) 与参数 t 的几何语义。
[5]: 第 4.4 节"光线与物体的交点" —— 与一组物体求交的 Group 抽象、t 区间收缩算法与加速结构概念。
[6]: 第 4.5 节"着色 Shading" —— 着色输入参数的完整清单与着色模型在两条渲染路线间的通用性。
[7]: 第 4.5.3 节"阴影" —— 阴影光线的构造方法与 epsilon 偏移的数值处理。
[8]: 第 4.5.1 节"光源" —— 点光源 / 方向光源 / 环境光的数学表达与着色所需向量。
补充说明:以上 8 条引用均借用《计算机图形学基础》第 5 版的概念口径,作为本文术语定义的统一基准。本文范围之外的相邻主题(渲染方程与蒙特卡洛路径追踪的数学基础、DXR 加速结构构建的底层算法、降噪器的具体滤波核设计)不在本篇展开。
UE 5.6 的光追以图像顺序算法为基础,经 RDG 与 FGPUScene 接入渲染主循环,上层由 Lumen 负责 GI/反射的多级缓存软/硬件双模式求解,底层由通用光追系统管理 BLAS/TLAS/SBT 与多租户 RT Pass,硬件光线最终沿 TLAS -> BLAS -> SBT -> Hit Shader 完成闭环。