核心问题:前面八篇各算了一笔账。ep02 与 ep03 算了加速结构的构建与内存,ep04 算了命中着色器与 SBT,ep05 算了混合管线的编排,ep06 与 ep07 算了降噪,ep08 算了采样与复用——每一篇都是单独成立的一本账,但它们有一个共同的分母从未被摆到台面上:光线。
一帧里能发出的光线总数是有限的。反射多要一条,阴影就少一条;全局光多更新一个探针,反射就少降半级采样。所以"每个效果各配多少光线"这件事,本质上是在同一条预算线上分钱——而且这张表必须在运行时自己算,没法离线定死,因为屏幕内容每一帧都在变。
本篇要回答的是十件事——一条光线到底贵在哪几项上、反射凭什么敢按每像素 8 到 22 条开价、阴影凭什么只要 1 到 2 条、全局光为什么连"花钱单位"都跟别人不一样、固定配额与按需裁剪各有什么代价、预算不够时误差会以什么形式暴露、一张能自己填的预算表长什么样、三款作品分别怎么分、以及 2025 到 2026 年这张表正在被哪些东西改写。十件事合起来,才算把"光线"这个分母讲清楚。
本篇承接 ep08 的交棒。ep08 讲的是怎么让一条光线干出几百条光线的活——那是降单价;本篇讲的是总额怎么定、怎么分——那是分预算。降噪器内部机制(SVGF / A-SVGF / NRD / TSR / DLSS 光线重建)已在 ep06 与 ep07 讲透,采样与复用的数学已在 ep08 讲透,本篇只在"它吃掉哪一格预算"的地方引用结论,不再展开。本篇也不涉及:RT Core 的内部微架构与求交电路(硬件实现层《GPU 架构剖析》已写)、加速结构的构建/更新/压缩算法细节(ep02/ep03 已写)、离线渲染的采样理论综述、具体引擎的编辑器操作与蓝图配置。这么切是因为这四类问题的约束条件与实时渲染完全不同——硬件电路关心的是门级实现,加速结构算法关心的是数据结构,离线渲染不关心 16 ms 的预算,硬凑在一起只会把"预算内怎么分钱"讲糊。
本系列(实时光线追踪与混合渲染,共 12 篇)的前置阅读:第一篇《分界线》给出了三类效果判据与三条成本模型;第二、三篇《加速结构工程账》上下给出了构建/更新/压缩与动态场景的账;第四篇《命中着色器与 SBT》讲了找到三角形之后该调用哪段着色器;第五篇《混合管线编排》讲了光栅化与光追怎么接力、性能预算怎么分;第六篇《降噪(上)》给出了时域复用的数学、运动矢量与重投影、六类失效模式;第七篇《降噪(下)》讲了空间滤波、方差驱动与学习型重建;第八篇《ReSTIR 与光照采样》讲了重采样、蓄水池与时空复用如何把单条光线放大成几百条。其余前置专题为算法层《实时渲染管线探析》5 篇、约束层《移动端图形与功耗》7 篇、硬件实现层《GPU 架构剖析》12 篇。
本文由@溯光 出品,转载请注明出处。本文同步发布于公众号与个人博客:技术博客页。
点题:光线是唯一被反复消耗的资源
0.0 内容总览:本篇知识地图
先把十章各自回答的问题钉在一张表上,后面每一章都在展开其中的某一行。大家可以从这张表里找到自己最关心的那一行,再跳到对应章节;如果时间紧,第一章(给光线定价)与第七章(填一张自己的表)是两处最容易被略过、也最容易在项目里吃亏的地方,建议至少读到那两章。
| 章 | 回答的问题 | 关键结论 |
|---|---|---|
| 零 | 为什么"分钱"比"省钱"更难 | 光线是唯一被反复消耗的资源,而它没有全局最优分配 |
| 一 | 一条光线到底贵在哪几项上 | 遍历、命中着色、降噪是三项独立支出,省一项可能抬高另一项 |
| 二 | 反射凭什么敢按每像素 8–22 条开价 | 它买的是方向维度,而方向可以用粗糙度门限与降采样来裁剪 |
| 三 | 阴影凭什么只要 1–2 条 | 可见性只问一次,问完就能终止,且命中着色极简 |
| 四 | 全局光的花钱单位为什么不一样 | 它是"每探针几条 × 每帧更新几个探针",不是每像素几条 |
| 五 | 固定配额、按需裁剪、感知加权各有什么代价 | 固定配额换来恒定成本,代价是质量随场景复杂度下滑 |
| 六 | 预算不够时误差以什么形式暴露 | 过遮蔽、偷样本、降噪兜底失效——误差会被写成结论 |
| 七 | 一张能自己填的预算表长什么样 | 从帧时间倒推,换算链是唯一的工程抓手 |
| 八 | 三款作品分别怎么分 | 同一条硬件,三种分法,差别在"谁先被砍" |
| 九 | 2025–2026 在改写这张表的哪些格 | 固定配额、神经降噪、亚 1 spp 自适应采样 |
| 十 | 本篇不涉及什么、下一步去哪 | 交棒给"RT 资源的生命周期与同步" |
0.1 从 ep08 的交棒说起:复用管单价,不管总额
ep08 的结论里有一句是全篇的落脚点:复用改变的是每一条光线的性价比,不是这一帧能发多少条光线。蓄水池把一条初始样本放大成几百条等效样本,这是分子上的功夫;可它一个字节也没有增加 RT Core 一帧能通过的射线总数。
这条边界必须划清楚。
两件事必须分开看,否则会得出一个危险的推论:既然复用能把 1 条变成 300 条,那是不是可以无限降采样、把省下的时间全给另一个效果?答案是不行,因为复用的放大倍数依赖于样本之间"有没有话可说"——支持集不匹配、随机数共享、条件概率这三条偏的来源(ep08 第四章)会随着复用半径变大而放大。换句话说,复用把单位成本压下去了,但它自己也吃预算:它需要额外的中间缓冲、额外的访存带宽,以及额外的 pass 时间。
所以到了这一层,问题变成了一个纯粹的分配问题:一帧的射线总额度定了,谁多一点、谁少一点。
这个前提为什么成立?
要把这件事摆清楚,得先承认一个前提——光线预算没有全局最优解。
这不是修辞。屏幕不同区域的噪声、重建难度、感知重要性都不一样:一面镜子里映出的高光和一个背光墙角,对同一帧画面的贡献可以差一个数量级;同一个像素在静止时和在快速横移时,能容忍的采样数也完全不同。任何"给所有像素、所有效果配同一个数"的方案,都必然在某些区域浪费、在另一些区域亏欠。这就是为什么这张表必须在运行时算,也是为什么它成了一个编配问题而不是一个配置问题。
这张表要回答的三个问题,构成了后面九章的骨架。
这张图想说明的是问题的层次:定价是基础,总额是约束,分配是决策。三层里最容易被跳过的是定价——很多团队把"光追很贵"当成一个整体直觉,从来没有把一条光线拆开看过。可一旦拆开,就会发现有些开销跟"发多少条"完全无关(比如每帧重建的 TLAS),有些开销跟"发到哪"强相关(比如遍历时的网格重叠),还有些开销根本不在光线这一侧(比如降噪)。
0.2 先给个量级:每像素 1 到 2 个样本
在拆开定价之前,先让大家对"总额"有个直觉。NVIDIA 在《Coffee Break Series: Ray Tracing in Games with NVIDIA RTX》里把这个量级说得很直白:实时渲染的现实预算是每像素 1 到 2 个样本,而这个量级本身不足以得到可信结果;能成立的原因是降噪把差额补上了,并且降噪自己的预算大约在 1 毫秒或更低每帧。
这个数字需要停一下。"每像素 1 到 2 个样本"是什么概念?1080p 一帧有约 207 万像素,按 2 个样本算就是约 414 万条光线;60 Hz 下就是约 2.5 亿条光线每秒。这个量级听起来很大,但《Ray Tracing Gems》开篇给出的定调是:实时场景下"只负担得起每像素少数几条射线,且往往随机分布",结果必然带噪;把稀疏但正确的逐像素估值与时空滤波结合,是用偏差换方差的合理交易。
这三栏并排之后,本篇的主线就清楚了:只有中间那一栏是"射线预算",但它不是唯一的支出;上下两栏都会来抢同一帧的时间。 一个只盯射线数的预算表,会同时在两头出错——它既会漏掉每帧重建加速结构的固定开销,也会忘了降噪器还在等着分钱。
0.3 边界声明与关键参数范围
按本系列的惯例,先把本篇的边界钉住:本篇只讨论实时渲染里光线在效果之间的分配——定价、总额、分配策略与失效模式。不讨论 RT Core 内部微架构、不重复加速结构的构建/更新/压缩算法、不重复采样与复用的数学、不做离线渲染的采样理论综述、不写具体引擎的编辑器操作。
参数取值上,本篇涉及的量级大致落在:帧预算从 4 毫秒(60 fps 主机档)到 16.6 毫秒(60 fps 通用档),射线预算占其中约三到六成;每像素射线数从亚 1 条(自适应稀疏采样)到 22 条(路径追踪的 Overdrive 档);每探针射线数在 64 到 288 之间;每帧更新的探针数在几十到几万之间;光追场景实例数上限约 10 万。凡是引用到具体设备的绝对值(耗时、内存、吞吐),都会同时给出分辨率与口径——本系列在 ep07 里已经吃过"混用口径"的亏,这里保持同样的纪律。
最后再强调一遍口径问题:本篇引用的数字来自三类文献——引擎官方文档(可复现的 CVar 与档位)、厂商白皮书(特定作品与特定模式的实测)、学术论文(特定数据集与特定基线)。这三类不能直接并列比较,所以第七章会给出一张带"口径"列的对照表,凡是结论性的比较都在同一口径内做。
一、给一条光线定价:遍历、着色、降噪
1.1 三张账单,不是一张
Epic 的《Ray Tracing Performance Guide》把硬件光追的成本明确切成三类,这个切法本身就是本篇最重要的一件工具:
| 成本类目 | 性质 | 由什么决定 | 能不能靠"少发光线"省 |
|---|---|---|---|
| BLAS 构建(动态形变网格) | 每帧重建 | 骨骼网格/毛发/地形的三角形数 | 不能 |
| TLAS 构建 + SBT | 每帧重建 | 进入光追场景的实例数 | 不能 |
| 每帧各效果的射线遍历 | 每帧消耗 | 该效果发出的 ray query 条数 × 场景重叠度 | 能,且几乎是唯一能动的 |
同一份文档还给出了一句定性到定量的关键话:射线遍历成本与"光线查询条数"和"场景网格重叠程度"双双成正比。这句话解释了为什么光追的性能在不同场景之间差得那么远——同样的射线数,放进一个"拼装式"(kitbashing)堆叠的场景里,遍历开销可以翻几倍。原因是两级加速结构下,光线要逐一穿过每一个重叠网格才能确定最近命中,重叠越多,穿过的次数越多。
这个切法带来的第一个工程结论就很反直觉:如果你的项目光追很慢,第一件该查的未必是"射线发太多",而可能是"进光追场景的实例太多"或者"重叠太严重"。 这两项都属于上表的第二、第三类,砍射线数一点也救不了它们。
官方给出的可对照实测剖面也值得一看。一帧的真实 GPU 日志里,Lumen 反射单独一项就占 2.36 毫秒(一帧的 4.1%),其中硬件光追遍历部分 0.67 毫秒;也就是说,在光线真正"跑起来"之前,反射这一路已经花掉了大头——那部分开销来自场景表示的组织、着色与重建,而不是遍历本身。这条数据对预算表的意义很直接:把反射的射线数砍一半,省下的不是反射总成本的 50%,而是它遍历那部分的 50%。
1.2 遍历的单价:50 到 100 次迭代
遍历这一步有一个可以直接读出来的单价指标——BVH 遍历的迭代次数。Epic 给出的经验区间是:
| 场景类型 | 每像素遍历迭代次数 |
|---|---|
| 典型场景 | 50 – 100 次 |
| 复杂场景(多个重叠高模) | 100 – 150 次 |
| 超过 150 次 | 说明存在问题,需要排查 |
这个区间比"耗时"更好用,因为它与硬件无关,可以直接拿来判断一个场景的几何表示是否健康。引擎还提供了对应的调试热力图:Traversal 模式下,颜色从绿(迭代少)到红(迭代多),直观显示哪些像素在把预算烧在穿越上。
两类最典型的"元凶"几乎在每个项目里都出现:
- 天空盒(skybox):它包裹整个场景,所以每一条光线都必须穿过它,哪怕最后没打中。正确做法是把它从光追场景里去掉,改在 miss shader 里着色。
- 草:包围盒巨大而几何复杂,一条横穿的射线要穿过大量叶片。
处理办法只有一句:把这两类物体的"在光追中可见"关掉。注意这里没有任何算法技巧,纯粹是把它们从预算表的分母里移出去——这也是本篇反复出现的一种取胜方式:不优化已有的支出,而是取消根本不值得存在的支出。
1.3 固定开销:payload、递归深度与提前终止
除了遍历,一条光线还有几项每像素都要付一次的固定开销,它们不由射线数决定,而由着色器接口的声明决定。NVIDIA 的《Tips and Tricks: Ray Tracing Best Practices》把这几条列得很清楚:
| 项目 | 影响 | 口径 |
|---|---|---|
MaxPayloadSizeInBytes |
直接决定寄存器压力与占用率 | 载荷越大越容易溢出到内存 |
MaxAttributeSizeInBytes |
同上 | 固定功能三角形求交用两个属性,可作参考 |
MaxTraceRecursionDepth |
决定 DispatchRays 分配的栈内存 |
取到最小可能值 |
| 提前终止标志 | 阴影/AO 类射线的常规优化 | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH 比用 any-hit 显式终止更高效 |
同一份文档还给了两条直接影响预算的策略。第一条是给不同类型的光线用不同的光追管线:阴影类射线的着色器简单、载荷小、寄存器压力低,反射类射线着色器复杂、载荷大——把它们分开,驱动能更有效地调度,占用率更高。这实际上是承认了"一条阴影射线"和"一条反射射线"根本不是同一种商品,第三章会沿着这条线继续算。第二条是把加速结构的构建/更新与降噪都挪到异步计算队列,目标是把它们藏在其他阶段后面,控制在 2 毫秒以内。
到这里可以给一条光线下第一个粗定价了:它的成本 = 一次固定开销 + 一次遍历 × 迭代次数 + 一次命中着色 + 分摊到它头上的构建与降噪。 后面四章要做的,就是看不同效果在这四项上的取值差多少。
1.4 光线的单价在代际之间下降得很快
定价还有一项常被忽略的变量:你用的是哪一代硬件。Ada 白皮书给出的 RT 侧算力是:RTX 2080 Ti 42.9 RT-TFLOPS、RTX 3090 Ti 78.1、RTX 4090 191;第三代 RT Core 的射线-三角形求交吞吐约为上一代的 2 倍。配套的还有两项专门用来压低单条光线成本的机制——OMM(Opacity MicroMaps,不透明微图)让 alpha-tested 几何的光追快 2 倍,SER(Shader Execution Reordering,着色器执行重排序)通过重排二次命中着色来改善发散与占用率。
这件事对预算表的影响是:同一张表换一代硬件,可以整体抬一档,而不必重新分配。 换句话说,预算表里的"比例"比"绝对值"稳定得多——这也是为什么第七章给出的换算链要以比例和公式为主,而不是给一张死数字表。
这张成品图用来标定"天花板":当反射与全局光都按较高档位付钱时,画面能到什么程度。它的作用不是证明"好东西值得贵",而是提供参照——后面每一章砍预算的讨论,都需要一个"砍掉之后差在哪"的锚。
二、反射为什么敢要 8 到 22 条
2.1 三档公开数字:8 / 10 / 22
关于"反射到底发多少条光线",目前最具体的公开数字来自 NVIDIA 的《Ada GPU Artistry》白皮书,它对《赛博朋克 2077》给出的口径是:
视场景与反射面数量而定,就每像素生成的光线数而言,Ultra 模式 8 条/像素,Psycho 模式 10 条/像素,Overdrive(路径追踪)模式 22 条/像素;阴影侧则每像素只向场景中最重要的光源发约 1 到 2 条阴影光线。
这组数字是本篇的核心对照物。同样是"一条光线",反射侧要用 8 到 22 条,阴影侧只用 1 到 2 条,差了一个数量级。 差价不在硬件,而在这两个效果在积分维度上的本质不同——这一点第三章会展开。
同一时期另一款作品的数字可以从另一个角度校准。Digital Foundry 对《战地 5》的光追分析给出了"最大射线数占屏幕分辨率的比例":低档 23.3%、高档 31.6%、超高档 40.0%,同时还给出了一个"光滑度阈值"(0.9 / 0.5 / 0.5)来决定哪些材质被分配光线追踪反射。40% 的像素发反射光线,折合到每像素是 0.4 条——这与"每像素 22 条"看着矛盾,其实不矛盾:前者是屏幕空间上的覆盖率,后者是每个被选中像素内部的采样数,两者的乘积累起来才是总射线数。这个例子正好说明为什么第七章必须给对照表加"口径"列。
2.2 反射贵的根本原因:它在买方向
差价到底从哪来?
反射贵,是因为它要买的不是"这个方向有没有东西",而是"每个方向上有多少光"。阴影只需要回答一个二值问题——这个点对你看到的光源可见吗;反射要回答一个定义在整个半球上的函数——从镜面方向到粗糙方向的连续分布。要重建一个函数,就得在它的定义域上采样;要重建得平滑,就得采得足够密。
这就是"每像素 8 到 22 条"的来源:那 22 条分配在半球的不同方向上,共同重建出一个粗糙度相关的反射瓣。粗糙度越低(越接近镜面),需要的方向采样越少;粗糙度越高,越接近漫反射,需要的方向采样越多——但这一点在实时渲染里的处理方式恰恰相反,下面这一节会看到。
上面这张成品把"定义域里处处有东西"这件事画得很具体:画面里每个金属球都是一个反射体,而它们彼此互为对方的反射内容。在这种场景里,反射函数没有"大部分方向是空的"这种便宜可占——每一个方向都得采样,否则就是一片糊。这也是金属材质在实时渲染里最贵的原因:它的粗糙度低(需要锐利的镜面方向),但方向上的内容又极其复杂。
2.3 第一刀:按粗糙度门限裁剪
Epic 的《Lumen Performance Guide》实际上就是一份预算分配手册,它逐条写明了"谁分到光线"。第一刀最狠,也最有效:
默认情况下,所有粗糙度低于 0.4 的像素会追踪一条反射光线。粗糙度高于这个阈值的像素,会得到一个基于 Lumen 全局光照的免费粗糙高光近似。
这一刀的逻辑很值得琢磨。按"物理正确"的方向想,粗糙表面应该采更多方向;但工程上恰恰反过来——粗糙表面的反射瓣很宽,高光的空间变化很平缓,用便宜的近似糊过去,肉眼几乎看不出来;而光滑表面的反射是锐利的,一旦漏采样就是明显的噪点或缺失。 于是预算被优先给到"省不了的地方"。
这套门限还能局部微调,而且有两个数字很能说明工程直觉:
| 控制项 | 作用 | 实测收益 |
|---|---|---|
r.Lumen.Reflections.MaxRoughnessToTrace |
全局粗糙度门限(默认 0.4) | 门限越高,发反射光线的像素越多,越贵 |
r.Lumen.Reflections.MaxRoughnessToTraceForFoliage |
植被独立门限 | 设为 0 可省掉大量射线且几乎不影响观感 |
r.Lumen.Reflections.Allow=0 |
用屏幕空间反射顶替光追反射 | Xbox Series S 上可省约 1 毫秒 |
植被那一行值得单独说:植被上的反射"常常很难看出来",所以把它的门限设成 0(也就是一片叶子都不发反射光线)是一个几乎零成本的质量交换。这类"感知上不值钱、计算上很贵"的位置,是预算表里最该先砍的地方。
2.4 第二刀:空间摊薄
门限决定的是"哪些像素发光线",降采样决定的是"多久发一次"。Lumen 提供了三档:
| 模式 | 采样密度 | 代价 |
|---|---|---|
| 全分辨率 | 每像素 1 条 | 最贵,最清晰 |
DownsampleFactor=2 |
4 个像素只发 1 条 | 反射更糊、更不稳 |
DownsampleCheckerboard |
2 个像素 1 条 | 介于两者之间 |
这是一种用重建换射线的交易:发出去的射线少了,但被跳过的像素要从低分辨率的追踪结果里重建出来。所以它的代价写得很清楚——"会让反射更模糊、更不稳定"。注意这里和 ep06/ep07 的降噪是同一类思路:把方差换成偏差,然后指望重建器把偏差压回来。
2.5 第三刀:换一条更便宜的路
前面两刀砍的都是量,这一刀换的是路线。Lumen 允许直接用屏幕空间反射(screen space reflections, SSR)顶替光追反射,官方给的收益口径是 Xbox Series S 上 约 1 毫秒。SSR 便宜是因为它根本不发新光线——它只是在已经渲染好的颜色缓冲里按屏幕空间做一次射线推进,碰到屏幕外的物体就无从查找。
所以这个交换的边界也很清楚:屏幕外与被遮挡的部分没有反射。对峡谷、走廊这类绝大部分内容都在屏幕内的场景,这是笔极划算的买卖;对开阔场景,代价会露在画面边缘。
到这里可以把反射这一格的定价策略收起来了,它的三段式结构非常清晰:先按粗糙度门限筛像素,再把留下的像素做空间摊薄,最后对剩下的部分考虑换更便宜的算法。 三刀都砍完之后还在的那部分,才是真正"非光追不可"的反射。
先把这段结论钉住:反射的 8 到 22 条不是"奢侈",而是"要在方向维度上重建一个函数"这件事的最低成本。 所有能砍的地方(粗糙度门限、降采样、换 SSR)本质都是在缩小这个函数需要被重建的区域。理解了这一点,第三章的"1 到 2 条"才不显得像是偷工减料。
三、阴影为什么只要 1 到 2 条
3.1 可见性只问一次
阴影贵不贵,取决于你要问的问题有多复杂。阴影光线要回答的是一个二值问题:从这个点到那个光源,中间有没有东西挡住?这个问题一旦问出来,答案就是要么占、要么不占,没有"占多少"这一层。而反射要回答的是"每个方向上有多少光"——一个定义在连续半球上的函数。问一个是非题和重建一个函数,成本差一个数量级是正常的。
所以"每像素约 1 到 2 条阴影光线、打向场景中最重要的光源"这个做法,本质上是一次问题降维:不是对每个光源都发一条(那会随光源数线性增长),而是只挑最重要的少数几个,然后把剩下的交给别的机制。
这张流程图的重点是那个提前退出的出口。阴影射线不需要知道"最近命中的是哪个物体、它的材质是什么、它反射了多少光"——它只需要知道"有没有"。这个差别直接决定了它可以用 RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH 这类标志提前结束,而不是像反射那样必须求到最近命中点,再调用完整的命中着色。
3.2 阴影射线在四项定价上都更便宜
回到第一章那张定价表,把阴影射线逐项填进去,差价就很清楚了:
| 定价项 | 阴影射线 | 反射射线 |
|---|---|---|
| 射线长度 | 短——只需够到光源 | 长——要够到场景任意深度 |
| 遍历迭代 | 少——找到任意阻挡即停 | 多——必须确认最近命中 |
| 命中着色 | 极简——只要可见性,不要材质 | 复杂——要算完整着色,可能还要递归 |
| 载荷与寄存器 | 小 | 大(这也是为什么官方建议把两类射线放进不同光追管线) |
四项叠加的结果就是每像素 1 到 2 条对比 8 到 22 条。这不是"阴影做得敷衍",而是阴影这个问题本身的定义就比反射窄。
3.3 那软阴影和面光源呢
这里有一个常见的疑问:如果只发 1 到 2 条,那面光源的软阴影(soft shadow)和半影(penumbra)怎么办?它们需要在一个有面积的发光体上采样,看起来应该要很多条才对。
答案分两层。第一层是面光源采样本来就不需要很密:半影是一个低频变化的现象——它只在物体边缘附近才有梯度,画面大部分区域的可见性是均匀的。所以"每像素 1 到 2 条"配上时空重建,足以把半影的形状恢复出来,代价是边缘可能有轻微的抖动或闪烁,这正是降噪器最擅长处理的部分。
第二层更有意思:如果嫌不够,可以换一套完全不同的分账方式——这正是第五章要讲的 MegaLights 路线。它彻底放弃了"逐光源建阴影图"的思路,改成"每像素固定几条射线,随机打向被重要性采样选中的光源"。
上面这张图是这条预算线的另一端。玻璃材质把"贵在方向"推到了极端:它同时要求反射、折射与焦散,每一项都得在半球上采样,而且方向上的内容极不平滑——这正是它从阴影那一格"借"走预算的典型方式。有了"阴影只要 1 到 2 条"与"方向采样可以吃掉 8 到 22 条"这两端,中间所有档位才有参照系。
3.4 阴影侧的省法:不是砍射线,是砍光源
阴影这一格有一个反射侧没有的省钱途径:减少需要被采样的光源数量与包围盒。Epic 在文档里给出了两条具体做法——Lighting 模式每帧只更新表面缓存的一部分,而 Lumen Scene Lighting "每帧只挑一小部分最重要的光源逐表面缓存 tile 处理",因此它的成本对场景里的光源总数不敏感,每 tile 的光源数由 r.LumenScene.DirectLighting.MaxLightsPerTile 控制。
这条结论对预算表的意义很大:阴影这一行不是"光源数 × 每条光源的成本",而是"每 tile 的固定条数 × tile 数"。 当光源数从几百涨到几千时,这张表的这一格几乎不动。
阴影这一格可以收口了:问一个二值问题、提前退出、极简着色、按 tile 限额——四件事加起来,就是"1 到 2 条"能成立的全部理由。
四、全局光的花钱单位不一样
4.1 它买的不是"每像素几条",是"每探针几条 × 每帧更新几个探针"
全局光(global illumination, GI)是这张预算表里最特殊的一格,因为它的花钱单位跟别人不在一个维度上。
反射是"每像素 N 条",阴影是"每像素 N 条",而实时全局光的主流做法是探针(probe):在空间里布一张网格,每个网格点存一份方向性辐照度,渲染时按像素附近的探针插值。所以它的预算是两个数的乘积:
全局光每帧的射线数 = 每探针射线数 × 每帧更新的探针数
这两个数在公开资料里都有具体取值,而且量级差异很大:
| 实现 | 每探针射线数 | 每帧更新探针数 | 实测成本 | 来源口径 |
|---|---|---|---|---|
| DDGI(动态漫反射全局光照,原论文) | 64 | 全部(每帧全更新) | 2.5 ms | JCGT 2019 |
| DDGI(同一团队的后续实现) | 最高 256 | 全部 | 1.5 ms | 同团队后续报告 |
| RTXGI(SDK 建议值) | 默认 288 | — | — | 官方 SDK 文档 |
| Lumen Lite 辐照度场 Gather | — | 示例 100 | — | 引擎文档与实测笔记 |
先看 DDGI 的两个数字:同样一份全局光,64 条射线每探针要 2.5 毫秒,而 256 条射线每探针只要 1.5 毫秒——射线数翻了 4 倍,耗时反而降了 40%。这个反直觉的结果来自吞吐效率:射线数少的时候,线程占用不满、访存局部性差、固定开销摊不开;射线数上来之后,硬件才真正跑起来。论文给出的吞吐口径是"除极端情况外均高于 1.5 GRays/秒",也就是每秒 15 亿条光线这个量级。
这件事对预算表是一个重要提醒:"少发光线"和"更快"不是单调关系。 在低占用区间里,砍射线数可能一点时间都省不下来,甚至更慢。这跟反射那边"砍一半射线省一半遍历"的直觉完全相反,原因在于全局光的射线是按探针成批发出的,批太小就摊不开。
4.2 探针会失败,而且失败得很显眼
探针这套方案有一个与生俱来的问题:它布在空间里,而空间里不是处处都有意义。 布在墙里、地板下、家具内部的探针,测到的是一个物理上不存在的"内部光照"。引擎的调试视图把这件事画得很直白:被迫移出几何的探针显示为绿色,仍然卡在几何里的探针显示为红色、并且不参与光照。
这条调试信息对预算的意义是:卡住的探针不是"便宜地省了",而是"花了钱但没产出"。 预算表里应该把它当成浪费项处理——把这类探针清掉,等于凭空多出一份预算。
4.3 同一份预算怎么切:稳定度换响应速度
全局光这一格最有学头的是它允许在同一份总额内换性质。Lumen 的辐照度场 Gather 与屏幕空间探针 Gather 都提供了 NumProbesToTraceBudget 这类控制项,用来设定每帧更新的探针数上限;探针图集的容量则由 ProbeAtlasResolutionInProbes 之类的参数决定(例如 128² = 16384 个槽位)。
官方对"设得太低"的后果说得很具体:在快速转视角时会出现光照跳变,因为探针更新跟不上相机,光照只能慢慢追上来;而要进一步压低成本,连探针网格的分辨率也必须一起降。
这就形成了全局光独有的一对交换:
- 每个探针采更多射线 → 单个探针更准、画面更稳,但更新一整套探针更贵,相机移动时的响应更慢;
- 每帧更新更多探针 → 光照跟随相机更快、跳变更少,但每个探针的噪声更大,需要更强的重建。
这两个方向花的是同一笔钱。 这是本篇反复出现的"分钱"主题里最纯粹的一例——不是"要不要多花",而是"同样多的钱,买稳定还是买响应"。
4.4 GI 那一格到底买到了什么
讲完了怎么分,还得回答"值不值"。这件事最好的证据是一组同场景、只差一个全局光开关的对照渲染。
上面两张来自不同场景,但差别落在同一处:暗部(间接光把原本死黑的角落抬亮)与接触处(物体相接的地方出现颜色渗透和柔和的环境遮蔽)。而这两处,恰恰是采样数最吃紧的地方——它们的信息量集中在很小的空间范围内,且梯度很陡。两个场景都选了"暗部占比大"的取景,正是因为这一格的边际收益在那里最容易看出来。
换句话说:全局光那一格的预算,买到的主要是"暗部与接触"这两块区域的可信度。 如果你做的场景里这两块区域根本看不到(比如强光下的室外远景),那这一格的边际收益就非常低——这是一条比"有无 GI"重要得多的判断。
上面这张图代表的是另一条完全不同的分账思路。我们讲的所有方法都是"从相机侧发光线、往光源方向找";光子映射反过来——从光源侧把能量以光子的形式撒到场景里存起来,渲染时只在相机侧做一次便宜的查找。这两种路线的预算表长得完全不同:前者花的是"每像素几条",后者花的是"总共撒多少光子、存多少、查多快"。实时渲染主流选了前者,是因为前者能随时间稳定重建,而后者要改变场景就得重新撒一遍。
五、三种分法:固定配额、按需裁剪、感知加权
5.1 固定配额:成本恒定,质量随复杂度下滑
MegaLights 是"固定配额"路线的代表,它的机制一句话就能说完:每像素固定发几条光线,打向被重要性采样选中的光源;命中了就累加贡献。
这套做法的后果需要逐条摆出来,因为它把预算表的性质改变了:
| 维度 | 延迟着色(Deferred Shading) | MegaLights(固定配额) |
|---|---|---|
| GPU 成本 | 随光源数增长 | 恒定 |
| 光照质量 | 恒定 | 随光照复杂度下降 |
| 对光源数的敏感度 | 高 | 低(可支持数万个动态光源) |
| 失败形式 | 变慢 | 变糊、鬼影、噪声 |
这是一个把"性能风险"换成"画质风险"的交易。 延迟着色下,光源数翻倍可能直接掉帧;固定配额下,光源数翻倍只会让画面越来越糊,但帧率不动。对必须守住帧时间的实时应用来说,后者在工程上往往更可接受——因为掉帧是硬失败,变糊是软降级。
配套的两项机制也很关键。第一项是光线引导(ray guiding):把样本优先导向"可能有影响的光源",对"可能被遮挡的光源"少发样本。但官方同时指出一个必须配套的代价——被少发样本的光源仍然需要周期性回头采样,否则光源变可见的瞬间系统发现不了。第二项是隐藏光预算:官方文档提到大约 20% 的隐藏光预算反而有助于处理"强隐藏光"这类光源,因为它能保证这类光源最多拿到 20% 的射线。
5.2 按需裁剪:Lumen 的三刀
第二章和第三章已经拆完了 Lumen 的做法,这里把它归纳成一种策略类型。按需裁剪的核心特征是:光线的发放由场景内容逐像素决定——
- 按粗糙度门限决定哪些像素发反射光线(默认 <0.4)
- 按降采样因子决定多久发一次(1/2 或棋盘格)
- 按方案可用性决定要不要用更便宜的算法顶替(SSR 省约 1 毫秒)
- 按 tile 限额决定每块地方处理几个光源
这套策略的优点是在物理上精确地砍掉了"感知上不值钱"的部分,而且每一项都有明确的 CVar 可以调、可以量化收益。它的代价是分布不均:预算像水一样流向了"粗糙度低、屏幕内、光源重要"的地方,而这些地方有多少,是场景决定的,不是引擎决定的——所以同一个档位在不同场景里的实际画质差别可能很大。
5.3 感知加权:让采样器自己学
第三条路最激进:不再人工划定门限,而是让一个网络学会"这片区域需要多少样本"。
2026 年的一篇论文把这个方向推到了极限。它的出发点是一个已被广泛接受的事实:实时路径追踪越来越经常运行在 1 spp 以下,而超分(DLSS/FSR/XeSS 这类)是均匀地牺牲空间细节的——它无法利用"噪声大小、重建难度、感知重要性"在画面上的不均匀分布。
论文的做法是把采样密度解释成一个概率:密度 0.3 spp 意味着"这个像素有 30% 的概率被渲染一个样本"。这个"随机取整"(stochastic rounding)的表述解决了此前方法的根本困难——选哪些像素是离散操作,没法直接求梯度;而既往基于近似梯度的方法在低于 2 spp 时就失效了。换成概率表述之后,梯度可以从逐像素统计里解析地导出,论文报告它在最稀疏区域低到 0.01 spp(每 100 个像素 1 个样本)时仍然稳定。
配套的一步同样重要:优化的目标不能是"逐像素误差最小",而要是"感知误差最小"。论文用了感知损失并做了色调映射感知(tonemapping-aware)的训练,理由是低采样区间下"偏离参考图的幅度必然更大",一个只盯着数值误差的损失函数会把预算浪费在视觉上无所谓的区域。
5.4 三条路其实是同一件事的三个时间尺度
把三条路并排之后会发现,它们的分歧只在一件事上:谁来画"该给多少"的那条线。
- 固定配额:不画。给所有人一样多,让重建器去承接剩下的差异。
- 按需裁剪:人画。用粗糙度、屏幕位置、光源重要性这些可解释的量做门限。
- 感知加权:学出来。用一个网络直接预测密度图,判据是感知损失。
这三者的时间尺度也不同:固定配额是每帧恒定的,按需裁剪是逐像素实时判定的,感知加权是训练期学好的先验加上运行时推理。工程上常见的是三种混用——比如用固定配额守住总预算上限,用按需裁剪砍掉明显不值钱的部分,再用一个轻量网络决定剩下的部分怎么撒。
六、预算不够的时候,误差会以什么形式出现
6.1 过遮蔽:误差被写成了结论
第一种失效最隐蔽,因为它看起来不像"没算完",而像"算出来就是这个结果"。
Lumen 文档给出了一个可以直接终止长射线、省下昂贵遍历的参数 r.Lumen.HardwareRayTracing.MaxIterations:限制 BVH 遍历的迭代次数,提前终止又长又贵的射线。这条优化在几何重叠严重的场景里非常有效。但官方紧接着写了一句必须被记住的话:
被终止的射线被当作完全遮蔽、零辐射处理,这会造成过遮蔽(over-occlusion)。
这句话的分量在于:它不是"少了一点光",而是"把'我没查完'当成了'那里确实什么都没有'。" 一个可见性查询被迫中止,结果被记成了"被挡住"——误差从此不再以噪声的形式出现(噪声至少是零均值的,平均下来会收敛),而是以系统性变暗的形式固化下来,降噪器再怎么平均也平均不掉。
这是本篇最想让大家记住的一条判断:预算不足有两种后果——方差变大,或者偏差被引入。前者看得见、会收敛;后者看不见、不收敛。 凡是"终止/截断/兜底/近似"类的省法,都要先问一句:它把不确定变成了什么?如果答案是把不确定变成了一个确定的结论,那它写进预算表的时候就得单独标注。
6.2 偷样本:固定配额下的新问题
第二种失效是固定配额带来的。MegaLights 文档专门做了光照复杂度可视化(Light Complexity)来诊断它,而它要解决的问题说得很直白:
一个场景里经常在同一位置挤着很多光源,并不总是容易找出真正影响屏幕区域的重要光源。时而需要找出那些"偷样本"的强隐藏光,它们会加剧噪声。
这背后是一个固定配额特有的困境。因为每像素的射线数是固定的,每一个被采样到的光源都在从同一个池子里拿样本。一个藏在墙里、永远看不见、但包围盒巨大的强光源,会持续分走本来该给可见光源的样本——它不产生任何可见贡献,却实实在在地消耗预算。
官方给出的处理方式是双管齐下:一是用可视化把这类光源找出来(把像素连向真正影响它的光源,按权重比排序),二是在美术侧收窄光源的衰减范围与包围盒。这里有一条原则很值得抄进项目规范:"应该在场景里避免放置包围盒影响整个场景的光源"。
6.3 降噪兜底失效:糊、鬼影、噪声
第三种失效落在降噪这一侧。固定配额路线把大量重建压力推给了降噪器,而降噪器自己的预算是固定的。官方把失效链条写得很完整:
光照复杂度上升 → 降噪器需要做更多工作 → 于是可能导致光照被模糊,或者产生鬼影;避免的办法是把小光源合并成大面光源,并仔细收窄光源的包围盒。
"光照被模糊"和"鬼影"是两种性质不同的失败。 模糊是空间上的过度平滑——预算不够时,重建器只能扩大它的滤波核去借更多像素的信息,于是细节被抹平;鬼影是时间上的——它借了已被替换的历史帧信息,于是运动的物体拖着旧位置的痕迹。这两者都说明同一件事:当降噪器开始"借"的时候,说明上游给的信息不够了。
而这条链条里最该记的是那个不对称:降噪器能补方差,但补不了偏差。 ep07 已经讲过这个结论——降噪是质量放大器,它放大的是上游已存在的信息。一个被前面几章"省"掉的样本,不会因为降噪器更聪明而回来。
上面这张对照图是"廉价近似"这条路线的参照。环境光遮蔽(ambient occlusion, AO)不发光线、不算真实间接光,只按几何关系估计每个点被包围的程度——它便宜到几乎不占预算,也能把接触处的暗部补回来。但它补不出颜色渗透:真实全局光里,一面红墙会把红光弹到旁边的白墙上,而 AO 对此一无所知。
这就是预算表里最典型的一类交换:花很少的钱买到"暗部不空",但永远买不到"颜色会流动"。 判断该不该花这一格的钱,等价于判断你的场景里颜色渗透看不看得出来。
这张就是 AO 通道本身。把它单独看一眼就明白它为什么便宜又为什么不够:它只是一张"被包围程度"的静态图,不含任何光能量、颜色或方向信息。所以它能让接触处变暗、让缝隙显得深,但它既不会把红墙的光弹到白墙上,也不会因为光源换了位置而变化。免采样买到的是遮蔽感,不是光照。
七、填一张自己的预算表
前面六章把每一格的单价与策略都拆开了,这一章把它们拼回一张能落地的表。
7.1 从帧时间倒推
第一步是确定总额。这件事有一个必须先说清的层级关系:帧预算是总钱数,光线预算是其中一个格子。 Epic 给出的主机档口径是:Lumen 在 1080p 下对"全局光照 + 反射(不透明与半透明材质)+ 体积雾"的总目标,是 30 fps 对应 8 毫秒、60 fps 对应 4 毫秒;而且每一个画质档大约等于上一档的一半成本。
后一句话比前一句更有用,因为它给出了一把比例尺:如果 60 fps 主机的光线预算是 4 毫秒,那么桌面档抬一档就是 8 毫秒,降一档就是 2 毫秒——档位之间是等比关系,不是等差关系。这意味着分预算时应该按比例分配,而不是给每一格加一个固定毫秒数。
把总额铺到一帧的时间轴上,大致长这样:
| 阶段 | 典型占比 | 说明 |
|---|---|---|
| G-Buffer / 光栅化 | 约三到四成 | 与光追无关,但和它抢同一帧 |
| 加速结构构建/更新 | 目标 ≤2 ms | 官方建议挪到异步计算队列隐藏 |
| 光线遍历 + 命中着色 | 约三到六成 | 本篇讨论的主战场 |
| 降噪 | 约 1 ms 或更低 | 官方给出的量级 |
| 后处理 / 色调映射 / UI | 剩余 | 被前面挤剩下的 |
7.2 换算链:从毫秒到射线数
第二步是把时间换成射线数。这条链上每一环都有公开口径可以代入:
可分配光线预算(毫秒)
→ × 硬件光追吞吐(GRays/秒,DDGI 论文口径:>1.5)
= 每帧可发射的射线总数
→ ÷ 屏幕像素数(1080p ≈ 2.07 M;1440p ≈ 3.69 M)
= 每像素每帧的射线预算
这里有一个特别容易被忽略的乘数:分辨率。NVIDIA 的《Control Graphics and Performance Guide》把这件事说得很直接——每个像素每帧至少要发一条射线,所以 2560×1440 比 1920×1080 的像素数多 77%,光追负载随之显著上升;而光追性能取决于三件事:屏幕分辨率、RT Core、场景复杂度。
所以"分辨率"在本篇的语境里有一个更准确的名字:它是预算表的分母。 把渲染分辨率降一档(或用超分从低分辨率重建),本质上是把分母变小、让每个像素分到更多射线——这和"提高画质档"在数学上是同一件事的两种写法。这也解释了为什么现代引擎普遍选择"内部 1080p + 时间超分到 4K"这条路线:它把预算集中在更少的像素上,而不是让光线在 4K 的像素海里被稀释。
7.3 别忘了下游:降噪器会反过来约束分配
这张表还有一条容易被忽略的反馈线:分配出去的光线落在哪里,会决定下游重建器好不好过。
道理并不复杂。重建器能补的是方差,它靠的是"在相似像素之间平均";所以样本落在边界明确、邻域相似的地方时,它很好过;落在边界陡峭、邻域不相似的地方时,它会很吃力。而高光边缘、阴影边界、去遮挡区域恰好同时具备两个特征:采样本来就少(往往是 1 条甚至 0 条),邻域又不相似(没法借邻居的样本)。
这意味着一条很实用的分配经验:在总预算不变的前提下,把样本从"大片平坦区域"挪到"高光与阴影边界"上去,收益通常大于把它平均摊开。 平坦区域用邻居的样本就够了,而边界区域没有邻居可借。这也正是第五章那条"感知加权"路线之所以成立的物理基础——它学出来的密度图,本质上就是在做这件事。
反过来说,如果一个分配策略只知道"按重要性排序砍",把边界区域的样本砍掉了,那省下的时间会以画质的形式在重建阶段还回来,而且还回来的形式通常是模糊与鬼影(第六章第三节),而不是更干净的结果。
7.3 带口径的横向对比表
第三步是选档位。下面这张表把本篇引用过的公开数字集中起来,每一行都标明了口径——它们不能直接互相比较,只能在同一口径内比较:
| 效果 | 公开口径 | 数值 | 口径来源 |
|---|---|---|---|
| 反射(Ultra) | 每像素光线数 | 8 | Ada Artistry 白皮书 |
| 反射(Psycho) | 每像素光线数 | 10 | 同上 |
| 反射(Overdrive/路径追踪) | 每像素光线数 | 22 | 同上 |
| 反射覆盖率(低/高/超高档) | 占屏幕分辨率的比例 | 23.3%/31.6%/40.0% | Digital Foundry《战地 5》分析 |
| 阴影 | 每像素光线数(打向最重要光源) | 约 1–2 | Ada Artistry 白皮书 |
| 全局光(探针式) | 每探针射线数 | 64/256(实测 2.5 ms/1.5 ms) | DDGI 论文与后续实现 |
| 全局光(探针式) | 每探针射线数 | 288(SDK 建议默认) | RTXGI 文档 |
| 全局光(探针式) | 每帧更新探针数 | 100(Lumen Lite 示例) | 引擎文档 |
| 直接光(MegaLights) | 每像素固定条数 | 1 次 trace ≈ 0.8 spp;画质档示例 16 | MegaLights 论文与文档 |
| 降噪 | 每帧时间 | 约 1 ms 或更低 | NVIDIA Coffee Break |
| 遍历健康度 | BVH 迭代次数 | 典型 50–100,重叠高模 100–150 | Epic 性能指南 |
| 累积性能代价(粗口径) | 相对光栅化的 FPS 损失 | 光追反射/阴影约 10–25%;全光追约 20–40%;路径追踪约 70–85% | 第三方汇总量级 |
最后一行是唯一一行"第三方汇总"的数字,放在这里只作量级参照——它把三个效果的累积代价给出了一个感性刻度:路径追踪之所以是"另一个世界",是因为它把三格预算全部按最高档打开。
7.4 一张可填的模板
把前面所有结论收成一张模板。这张表的用法不是照抄数字,而是逐格判断"这一格在我的场景里值不值得":
| 格 | 单价 | 我的场景里值不值 | 若不值,第一刀砍在哪 |
|---|---|---|---|
| 反射 · 镜面 | 高(方向维度重建) | 画面里有大量光滑面? | 粗糙度门限下调;降采样;换 SSR |
| 反射 · 粗糙 | 低(有免费近似) | 一向不值 | 直接用 GI 的粗糙高光 |
| 阴影 · 主光 | 低(1–2 条,极简着色) | 几乎总是值 | 无 |
| 阴影 · 次要光源 | 低但随光源数累积 | 看画面是否需要 | 抬高光源重要性阈值;收窄包围盒 |
| 全局光 · 暗部 | 中(探针预算) | 画面里有明显暗部? | 降探针更新率;降低探针分辨率 |
| 全局光 · 接触 | 中 | 有近距离接触的物体? | 用 AO 近似顶替 |
| 全局光 · 颜色渗透 | 中 | 有大面积有色反射面? | 只能砍,无廉价替代 |
| 直接光 · 百万光源 | 恒定(固定配额) | 有大量动态光源? | 降每像素采样数;清理隐藏光 |
| 降噪 | 约 1 ms,几乎不可省 | 总是值 | 无 |
最后两列是这张表真正的价值:它强迫你在写下"这一格值"之前,先说出"如果预算不够,我会先砍它哪里"。说不出第二列的场景,通常意味着你也不知道这一格到底在为什么服务。
八、生产落地:三款作品三种分法
8.1 三种分法的对照
把公开信息拼起来,可以看到同一代硬件上,三款作品对这张表做了三种不同的分法:
| 作品 | 分法 | 反射 | 阴影 | 全局光 | 代价落在哪 |
|---|---|---|---|---|---|
| 《赛博朋克 2077》(Overdrive) | 全格高档 | 每像素 22 条 | 每像素 1–2 条 | 路径追踪式全局光 | 需要降噪与超分兜底,帧率代价约 70–85% |
| 《Portal with RTX》/RTX Remix 系 | 重制路线 | 路径追踪 + ReSTIR GI | 光追阴影 | 复用式全局光 | 老游戏资产 + 新渲染路径的适配成本 |
| Lumen 系(UE5 作品) | 按需裁剪 | 粗糙度门限 + 降采样 | 每 tile 限额 | 探针预算 | 场景依赖性强,档位效果随内容变化 |
第三列是三类分法真正的分野:第一条路是"每格都给足,让重建和超分去扛帧率",第二条路是"重制场景,把几何与材质控制在光追友好的范围内",第三条路是"逐格裁剪,把预算导向感知上值钱的地方"。
它们对应三种完全不同的工程约束。路径追踪路线的约束是"画面必须封顶",所以它接受 70–85% 的性能代价,用 DLSS 类超分和神经降噪把帧率买回来;重制路线的约束是"内容不是自己做的",所以它必须在既有资产上做适配;引擎路线的约束是"要跑在很多不同的场景上",所以它把决策权交给场景内容和一套可调的 CVar。
8.2 一帧的真实剖面
对预算表最有用的一件工具,其实是引擎自带的帧剖面。它能把"我以为的时间分配"和"实际的时间分配"对照起来——这是所有分配讨论的起点,因为没有它,一切分配都是猜。
Neural 渲染这条线的官方描述也印证了同一件事。DLSS 光线重建(Ray Reconstruction)的定位是"在射线未被采样的噪声区域生成更高质量像素"——这句话反过来读就是:降噪器在替那些没有发出去的光线买单。 它把降噪与超分统一成一个模型,替代了此前手工调参的降噪器;而采用它的路径追踪作品清单(《赛博朋克 2077》《心灵杀手 2》《Portal with RTX》《半条命 2 RTX》《夺宝奇兵:古老之圈》《星球大战:亡命之徒》《毁灭战士:黑暗时代》)本身说明"预算表"已经是一个量产议题,而不是技术演示。
九、2025 到 2026:这张表正在被改写
9.1 三条改写线
过去两年里,这张表上有三格的性质发生了变化:
| 改写线 | 改的是哪一格 | 改成了什么 | 公开依据 |
|---|---|---|---|
| 固定配额直接光 | 阴影/直接光 | 从"成本随光源数增长"变成"成本恒定、质量随复杂度下降" | MegaLights 文档与 SIGGRAPH 2025 课程 |
| 神经降噪与超分 | 降噪/重建 | 从"手工调参的滤波"变成"统一模型替未采样的区域生成像素" | DLSS 光线重建(2026 年更新为 4.5) |
| 亚 1 spp 自适应采样 | 全部射线格 | 从"每像素固定条数"变成"每像素一个采样概率" | 2026 年自适应采样论文 |
第一条线我们已经在第五章讲过。第三条线值得再补一句:它的意义不只是"更省",而是预算表的粒度变了。当采样密度可以写成 0.3 甚至 0.01 这样一个连续的概率值时,"每像素几条"这个整数格子就消失了——预算变成了一张连续的密度场,而这张场由网络学出来、由感知损失校准。
第二条线的意义在于它改变了责任归属。传统分工是"采样侧负责出样本、降噪侧负责修样本";而神经重建把两者合成一个模型之后,"没采样"这件事本身就是模型的一个输入条件——模型不是在修噪声,而是在根据 G-buffer 与已被采样的稀疏位置推断没采样的地方应该是什么样。这在工程上带来一个很现实的后果:采样侧的分配策略必须考虑下游重建器能不能接得住。你省下的样本如果落在重建器最不擅长的位置(比如高光、阴影边界、去遮挡区域),那省下的时间会以画质的形式还回来。
9.2 那些不会变的部分
不过这张表上有些格子始终没变,把它们列出来比列变化项更有用:
- 阴影只需要问可见性。 这个问题的定义不会因为硬件变强而改变,所以"1 到 2 条"这个量级会长期成立。
- 反射要重建一个方向函数。 只要还在用蒙特卡洛方法,方向采样数就仍然受方差支配。
- 降噪补方差、不补偏差。 这是估计量的性质,不是模型的能力问题。
- 分辨率的像素数仍在分母上。 屏幕像素翻倍,同一份预算就被摊薄一倍,这条算术关系与实现无关。
这张经典成品是"预算不设上限"那一端的参照。把它和第八章的路径追踪成品放在一起看,能看出实时预算表这些年到底追上了多少、又还差在哪一格——差的通常不是反射也不是阴影,而是二次以上的间接光与正确的焦散(caustics):它们在预算是"每像素几条"的世界里最难被采样到。
十、边界与交棒
10.1 本篇不涉及什么
按惯例把边界再钉一遍。本篇只讨论光线在效果之间的分配,以下内容不在范围内,且各有归属:
- RT Core 的内部微架构与求交电路——属硬件实现层《GPU 架构剖析》,本篇只在"单条光线的单位成本"处引用其结论;
- 加速结构的构建/更新/压缩算法细节——ep02 与 ep03 已写,本篇只用到"成本与实例数成正比"与"重叠会拉长遍历"两个结论;
- 采样与复用的数学——ep08 已写,本篇只用到"复用降单价但不增总额"这一条推论;
- 降噪器内部机制——ep06 与 ep07 已写,本篇只用到"它吃掉约 1 毫秒"与"它补方差、不补偏差"两条;
- 离线渲染的采样理论综述——约束条件完全不同;
- 具体引擎的编辑器操作与蓝图配置——那是使用手册。
10.2 三条留给自己的结论
如果这篇只能带走三句话,我希望是这三条:
第一,光线是唯一被反复消耗的资源,但它不是唯一的支出。 加速结构的构建与降噪都在同一帧里抢钱,只盯射线数的预算表会在两头同时出错。
第二,不同效果的"一条光线"不是同一种商品。 反射买方向、阴影买可见性、全局光买的是"每探针几条 × 每帧几个探针"——单价差一个数量级是它们的定义决定的,不是实现水平决定的。
第三,预算不足有两种后果,要分开处理。 方差变大看得见、会收敛;偏差被引入看不见、不收敛。凡是"终止/截断/兜底"式的省法,都要先问它把不确定变成了什么。
10.3 下篇预告
这一篇把"一帧里光线怎么分"讲完了,但还有一件事一直没有正面处理:这些被分配出去的光线所依赖的资源,在帧与帧之间是怎么活下来的。
加速结构不是一帧建完就完事了——它要在每一帧重建,而重建意味着上一帧正在被读取的那份不能被就地覆盖;光追资源有自己的生命周期,命中着色器读的几何、SBT 指向的着色器表、探针缓存里上一帧的辐照度,都要在"还在用"和"该更新了"之间排好队。这不是一个"加个 barrier"就能解决的问题:光追的异步性与光栅化管线不同步,同一个资源会同时出现在多个队列的视野里。
下一篇《RT 资源的生命周期:加速结构的可见性与同步》,我们来讲清三件事:加速结构与 SBT 的重建时机和屏障该怎么排、异步计算队列上的光追与图形队列怎么交接、以及"可见性"在光追语境下为什么比光栅化语境更容易出错。这是本系列的第十篇,也是把前九篇的所有资源账合到时间轴上的一篇。
10.4 参考资料与延伸阅读
引擎与厂商官方文档
- Ray Tracing Performance Guide(Epic Games)
- Lumen Performance Guide(Epic Games)
- MegaLights in Unreal Engine(Epic Games)
- Tips and Tricks: Ray Tracing Best Practices(NVIDIA)
- Coffee Break Series: Ray Tracing in Games with NVIDIA RTX
- Control Graphics and Performance Guide(NVIDIA)
- Download RTXGI SDK(NVIDIA)
白皮书与架构资料
- NVIDIA Ada GPU Artistry(含《赛博朋克 2077》每像素光线数口径)
- NVIDIA Ada GPU Architecture Whitepaper
- GeForce RTX 40 系列发布资料(RT-TFLOPS 口径)
论文与技术课程
- Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields(JCGT)
- DDGI 高分辨率版(McGill University)
- MegaLights: Stochastic Direct Lighting in Unreal Engine 5(SIGGRAPH 2025 Advances)
- Forget Superresolution, Sample Adaptively (when Path Tracing)(arXiv 2602.08642)
- Ray Tracing Gems 全文(免费版)
- Denoising and Filtering: Part V of Ray Tracing Gems(NVIDIA)
第三方分析与二次资料
- Battlefield 5 RTX 光追分析(Digital Foundry,最大射线数占屏幕比例口径)
- DLSS 4.5 Ray Reconstruction 公告(NVIDIA)
- gamescom 2026:DLSS 4.5 光线重建与路径追踪作品清单(NVIDIA)
- Cyberpunk 2077 画质设置与光追代价(第三方实测汇总)
- What is ray tracing? An honest look at the FPS cost(第三方量级汇总)
图片来源
本文题图与正文中的渲染成品图均来自 Wikimedia Commons,逐张出处与许可已列在页面下方图注中;自绘示意图由本系列按统一色板绘制,图注统一标注"本系列绘制"。
以上就是第九篇的全部内容。降噪讲了上下两篇、采样讲了一篇、预算分了一篇,光线这条线到这里算是铺完了——但铺完不等于收尾,因为前面每一篇都默认了一件事:资源想用就能用,想更新就能更新。 这个默认是错的,下一篇我们就来拆它。
愿大家的每一帧都刚好花在该花的地方,而不是花在"早就该关掉的那些东西"上。希望能对大家有所帮助,也欢迎在评论区补充你们项目里踩过的坑。
本文由@溯光 出品,转载请注明出处。