实时光线追踪与混合渲染 · 第 五 篇

混合管线编排:光栅化与光追的接力赛

2026-09-2115,884 字29 张配图

核心问题:ep03 讲完了加速结构怎么建,ep04 讲完了命中着色器怎么组织——但光追管线不是独立运行的。

实时渲染里,光追和光栅化是两条管线混合使用的:G-buffer 用光栅化生成,反射和阴影用光追算,最终画面靠降噪器拼起来。

但什么时候用光栅化、什么时候用光追?光追的 raygen 从哪拿 G-buffer 数据?光追的结果怎么和光栅化的结果混合?一帧 16 ms 里光追占多少、降噪又占多少?

本篇来回答——混合渲染为什么是稳态、G-buffer 是数据接口、两种混合模式怎么选、一帧的流水线怎么走、性能预算怎么分、降噪器为什么不能省、ReSTIR 怎么用少样本骗过大方差、各效果的混合策略怎么组合。八件事合起来,才是"光栅化与光追怎么编排"的完整答案。

本篇不涉及降噪算法的数学推导(ep06 时域复用、ep07 神经降噪专讲);不写 ReSTIR 的详细算法原理(ep08 ReSTIR 专篇);不写光追硬件的内部实现(《GPU 架构剖析》12 篇已写透,本篇只引用结论);也不写具体引擎的编辑器操作(那是使用手册)。我们只把 Vulkan 规范给的接口、厂商公布的数据、以及第三方实测的数字,摆成一本能算的账。

本系列(实时光线追踪与混合渲染,共 12 篇)的前置阅读:第一篇《分界线》给出了三类效果判据与三条成本模型;第二篇《加速结构工程账(上)》给出了构建/更新/压缩三科的静态账;第三篇《加速结构工程账(下)》给出了动态场景的 TLAS/BLAS/CLAS/OMM 策略与内存预算;第四篇《命中着色器与 SBT》讲了找到三角形之后该调用哪段着色器。本篇承接 ep04 的结尾——"光追管线不是独立运行的,它和光栅化是接力关系"。其余前置专题为算法层《实时渲染管线探析》5 篇、约束层《移动端图形与功耗》7 篇、硬件实现层《GPU 架构剖析》12 篇。

本文由@溯光 出品,转载请注明出处。本文同步发布于公众号与个人博客:技术博客页。

光追渲染的焦散效果
题图光追渲染的焦散效果@ Metoc / Wikimedia Commons,CC BY-SA 2.5

点题:光追不是独立运行的

0.0 内容总览:本篇知识地图

先来看看一张总表,把八章各自回答的问题钉在这里。后面每一章都在展开其中的某一行。读者可以先从这张表里找到自己最关心的问题,然后跳到对应章节去看。

这张总览表横向覆盖了全篇的八个章节,纵向给出了每章的核心问题、结论和主要依据。其中第一到三章讲"是什么"——混合管线的结构、数据接口和工作模式;第四到六章讲"为什么"——性能怎么分、降噪为什么重要、ReSTIR 怎么省光线;第七到八章讲"怎么用"——各效果的策略组合和全文总结。

章 回答的问题 核心结论 主要依据
一 G-buffer 里有什么、光追怎么读 G-buffer 是光栅化和光追的数据接口,含深度/法线/颜色/材质/运动向量 Vulkan 规范 · 延迟渲染管线标准
二 两种混合模式怎么选 完整 RT pipeline 适合全光追效果,ray query 适合屏幕空间混合效果 Vulkan Ray Query 样例 · ARM Vulkanised 2024
三 一帧的流水线怎么走 G-buffer → 多个光追 pass → 降噪 → 合成 → 后处理,每步都有同步点 NVIDIA Turing 架构白皮书 · DXR 规范
四 16 ms 怎么分配 光栅化占大头(~60%),光追 ~20%,降噪 ~15%,后处理 ~5% NVIDIA RTX-OPS 白皮书 · 第三方实测
五 降噪器为什么不能省 1 spp 几乎全是噪声,降噪占预算可能比光追本身还多 SVGF 论文 · NVIDIA NRD 实测数据
六 ReSTIR 怎么回事 蓄水池时空复用,用 1 条光线得到 10+ 条的等效质量 ReSTIR SIGGRAPH 2020 论文 · UE5 MegaLights
七 各效果的混合策略 阴影/反射/GI/AO 各有不同的混合档位,从全光栅到全光追逐级提升 Unreal Engine Lumen · Unity HDRP
八 三句话判据 + 交棒 混合渲染是稳态 / G-buffer 是数据接口 / 降噪是成本大户 全篇总结

0.1 混合渲染不是过渡方案,是稳态

先讲一个最容易被误解的点:混合渲染不是"光追还不够快时的过渡方案",而是当前硬件下的最优稳态架构。

为什么这么说?来看一组数字。Turing 架构白皮书中给出了典型混合渲染帧的工作负载分布:DLSS 约占 20% 帧时间,剩下 80% 是传统渲染 + 光线追踪的组合 NVIDIA Turing 架构白皮书。而在更一般的场景下,行业共识是:光栅化处理 80-90% 的工作负载,光线追踪选择性地补充效果 ScienceArray · 光追硬件革命。

混合渲染管线的四层分工
图 5-1混合渲染管线的四层分工@ 本系列绘制

这背后的逻辑很简单:光栅化做"主可见性"这件事已经做到极致了——Z-prepass、early-Z、hierarchical Z、occlusion culling,一套组合拳下来,被遮挡的三角形几乎不浪费任何性能。而光追做可见性的成本是 O(log n) 每道光——听起来也不高,但每像素发射一道主光线、再算上各种二次光线,总量就上去了。

所以分工很明确: - 光栅化擅长:主可见性(哪些三角形会被看到)、填充 G-buffer、前向渲染透明物体 - 光追擅长:阴影、反射、折射、全局光照——这些光栅化要么做不对、要么只能做屏幕空间近似的效果 - 降噪器负责:把光追的低采样率输出"洗"成干净的画面

这三件事缺一不可。少了光栅化,主可见性的成本会高到跑不动;少了光追,某些效果(比如屏幕外的反射)就是做不对;少了降噪器,1 spp 的光追输出全是噪声,没法看。

0.2 口径声明

本文的性能数字基于以下口径: - 硬件:以 RTX 3080/4070 级别为参考(主流高端桌面 GPU) - 分辨率:1080p / 1440p(游戏常用分辨率) - 帧率目标:60 fps(16.67 ms 帧预算) - 场景复杂度:~1000 万三角形的典型游戏场景 - 光追效果:阴影 + 反射 + AO 组合(不是全路径追踪)

不同硬件、不同分辨率、不同场景下的数字会有差异,我们关注的是比例关系和取舍逻辑,不是绝对值。

带着这个口径,我们从混合管线的数据接口开始——G-buffer。


一、数据接力:G-buffer 里有什么

1.1 为什么是 G-buffer

混合渲染的核心数据接口是 G-buffer(Geometry Buffer,几何缓冲)。这个概念来自延迟渲染(Deferred Rendering)——把几何信息(深度、法线、颜色、材质参数等)先渲染到一组缓冲里,然后在屏幕空间做光照计算。

在混合渲染管线里,G-buffer 的角色更进一步:它不仅是延迟光照的输入,也是光线追踪管线的输入。光追的 ray generation shader(光线生成着色器)从 G-buffer 读取每个像素的世界位置、法线、材质参数,然后决定要不要发射光线、发射什么方向的光线。

G-buffer 的六个通道
图 5-2G-buffer 的六个通道@ 本系列绘制

为什么必须走 G-buffer 这条路?直接让光追管线自己做主可见性不行吗?

答案是可以,但不划算。如果让光追管线做主可见性,意味着每像素至少要发射一道主光线(primary ray),这道光线要遍历整个 BVH、做三角形求交、还要执行 closest-hit 着色器。而光栅化做同样的事——利用 Z-buffer 和 early-Z 硬件——成本要低得多。

所以行业的选择是:光栅化做主可见性 + 写 G-buffer,光追只做光栅化做不好的效果。这就是"混合"二字的真正含义——不是两种技术随便混在一起,而是各自做擅长的事,通过 G-buffer 完成数据接力。

1.2 G-buffer 的通道清单

典型的 G-buffer 包含以下通道(不同引擎的具体布局略有差异):

通道 格式 内容 光追侧用途
深度(Depth) D32F 或 D24S8 每个像素的深度值 反推世界空间位置,确定光线起点
法线(Normal) RGBA8 或 RG16F 世界空间法线 确定光线方向(反射、折射)、计算光照
反照率(Albedo) RGBA8(sRGB) 基础颜色 + Alpha 材质颜色,用于最终着色
金属/粗糙(Metal/Rough) RG8 或 RGBA8 金属度 + 粗糙度 + 其他材质标记 BRDF 参数,决定反射行为
运动向量(Motion Vector) RG16F 或 RG32F 前一帧到当前帧的像素位移 时间累积、降噪重投影、TAA
材质 ID(Material ID) R8 或 R16UI 材质索引 SBT 查找、决定调用哪个 hit shader

这里有个细节值得单说:光追管线不是从零开始的。它不需要重新做主可见性,因为光栅化已经把"哪些像素能看到哪些物体"这件事做完了,结果就存在深度缓冲和法线缓冲里。光追管线只要从这些缓冲里读取数据,然后发射二次光线就行。

1.3 数据流向:从光栅化到光追再到合成

跟着一个像素在混合管线中的完整旅程:

一个像素的四步旅程
图 5-3一个像素的四步旅程@ 本系列绘制

第一步:光栅化 Pass 写入 G-buffer

这一步和传统延迟渲染完全一样。场景中的所有不透明物体被光栅化,深度、法线、颜色、材质参数被写入各自的 G-buffer 通道。这一步结束后,我们知道了每个像素"看到了什么"。

第二步:光追 Pass 读取 G-buffer

光线追踪管线的 raygen shader 被调用。对每个像素,它先从 G-buffer 读取深度和法线,反推出世界空间的位置和朝向。然后根据效果类型(阴影、反射、AO 等)发射相应的光线。

注意这个细节:raygen 是逐像素调用的,和片段着色器很像。区别在于,片段着色器是光栅化器"喂"过来的,而 raygen 是主动从 G-buffer 读数据。这也是为什么光追可以只处理部分像素——你可以决定哪些像素需要发射光线,哪些不需要。

第三步:光追结果写回 UAV

光线追踪的结果(阴影因子、反射颜色、AO 值等)通过 Unordered Access View(UAV)写入输出缓冲。注意是"无序访问"——因为光线的执行顺序是不确定的,不同像素的光线可能以任意顺序完成,所以必须用 UAV 而不是传统的 render target。

第四步:合成 Pass 混合结果

最后,一个计算着色器或片段着色器把光栅化的基础光照结果和光追的结果混合在一起。比如,把光追阴影乘到直接光照上,把光追反射混合到镜面反射通道里。

这四步走完,一个像素的混合渲染就完成了。听起来不复杂,但里面藏着很多工程细节——比如同步、比如内存布局、比如多 pass 之间的资源转换。

1.4 同步与资源状态

还有一个细节容易被跳过:光栅化和光追之间需要同步。

在 Vulkan 里,G-buffer 是在光栅化管线中作为 color attachment / depth attachment 写入的,而光追管线需要把它作为 sampled image 来读取。这中间需要一个管线屏障(pipeline barrier)来保证: 1. 光栅化的写入全部完成 2. 缓存(cache)已经刷新 3. 资源的 layout 已经从 attachment 转换为 shader read

同样,光追的输出 UAV 在被合成 pass 读取之前,也需要屏障来保证写入完成。

这些同步不是免费的——每次屏障都会让管线停顿一下。所以混合管线的编排要在减少同步次数上下功夫:把多个光追效果合并到一个 pass,或者合理安排 pass 顺序让屏障最少。

1.5 G-buffer 的带宽账

前面说 G-buffer 是数据接口,但接口本身也要付账——这笔账不在算力上,在带宽上。G-buffer 这个概念来自延迟着色,它的通道设计原则(存什么、存多精)在延迟渲染时代就已经定型,混合渲染只是把它复用了一次。

先算体积。按 1.2 节那张表取常见的通道格式,一个像素要写进去 19 字节:深度 D32F 占 4 字节,法线 RGBA8 占 4 字节,反照率 RGBA8 占 4 字节,金属/粗糙 RG8 占 2 字节,运动向量 RG16F 占 4 字节,材质 ID R8 占 1 字节。听着零碎,加起来就是一个不小的数。

G-buffer 的带宽账
图 5-4G-buffer 的带宽账@ 本系列绘制

19 B/px 乘上分辨率,就是一帧写一遍的体积:1080p 约 39 MB,1440p 约 70 MB,4K 约 158 MB。单看这个数不惊人——现代显卡的带宽在每秒几百 GB 到 1 TB 量级,39 MB 只相当于几十微秒的传输时间。

麻烦在于它不止写一遍。G-buffer 写完,光追管线要读深度和法线来定光线起点(至少一遍读),降噪器要读深度、法线、运动向量做边缘感知(又一遍),合成 pass 再读一遍颜色。也就是说,写下去 19 B/px,实际的读写总量往往是它的三到四倍。1440p 下这就到了 200 MB 以上,按 60 fps 折算成每秒 12 GB 以上的持续占用——对一块 600 GB/s 的卡来说,这已经吃掉了两个百分点,而它换来的只是"把数据搬来搬去"。

这是为什么通道格式值得抠。把法线从 RGBA8 压成八面体编码的 RG16,能省下 2 B/px,1440p 下每帧省 7 MB,读写各算一遍就是 20 MB 以上。反照率如果不是逐像素变化(很多场景可以用调色板索引),还能再省。

深度缓冲与深度测试
图 5-5深度缓冲与深度测试@ -Zeus- / Wikimedia Commons,CC BY-SA 3.0

但深度缓冲是这 19 字节里最不该省的一项。光追要靠它反推世界坐标,精度不够会让光线起点整体偏移——偏移量在小尺度上正是接触阴影与镜面反射最容易穿帮的地方。省带宽可以从法线编码、从反照率格式上省,不要在深度上省——深度缓冲的精度直接决定光线起点的准确度。

1.6 G-buffer 布局的两种取法

同样六个通道,摆法有两种。

G-buffer 布局的两种取法
图 5-6G-buffer 布局的两种取法@ 本系列绘制

全分辨率直存是最省心的:每个通道一张纹理,命中着色时想取哪个取哪个,深度与法线的精度也最高。代价是渲染目标数量多、带宽压力最大,而且在移动端这类带宽吃紧的平台上,光是给这些 RT 分配内存就要动一番脑筋。

紧凑打包 + 半分辨率是省带宽的路子:把多个通道压进同一张 RGBA,减少渲染目标数量;法线走八面体编码压到两个字节;运动向量与 AO 这类最终会被降噪器平滑的信号直接走半分辨率。这套做法能把带宽省下三到五成,代价是精度损失与实现复杂度——半分辨率的运动向量在物体边缘会有半个像素的错位,降噪器的重投影要为此加一层保护。

两条路没有绝对优劣,只有"你的瓶颈在哪"。如果实测下来 GPU 时间充裕、带宽吃紧,往打包走;如果带宽宽松但降噪质量上不去(往往是半分辨率信号拖的),就回到全分辨率。

讲完了数据接口与它的带宽账,接下来看光追侧的两种工作模式——完整管线和光线查询,它们的架构差异直接影响管线编排。


二、两种混合模式:光追管线 vs 光线查询

说到混合渲染,有两种截然不同的架构选择。很多人以为"用光追 = 用光追管线",其实还有一种更轻量的模式——光线查询(Ray Query)。这两种模式的选择,是混合管线编排的第一个关键决策。

2.1 完整光线追踪管线

第一种模式就是我们前几篇一直在讲的完整光线追踪管线(Ray Tracing Pipeline)。它的特点是:

  • 有独立的管线对象(VkPipeline of type VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR)
  • 有专门的着色器阶段:ray generation、intersection、any-hit、closest-hit、miss
  • 通过 Shader Binding Table(SBT) 将加速结构中的几何体和着色器关联起来
  • 用 vkCmdTraceRaysKHR() 启动光线追踪
完整光追管线的五个阶段
图 5-7完整光追管线的五个阶段@ 本系列绘制

这种模式的优势是功能完整:你可以做任意复杂的光追效果,包括递归光线、自定义相交、多 hit group 等等。它适合需要大量光线、多种材质的复杂光追场景。

但它的劣势也很明显:管理成本高。你需要管理 SBT、管理管线库、管理 hit group 和材质的对应关系。而且因为光追管线是一个独立的管线,它和光栅化管线之间的资源共享和同步也更复杂。

2.2 光线查询(Ray Query)

第二种模式叫做光线查询(Ray Query),Vulkan 里对应的扩展是 VK_KHR_ray_query。

光线查询和完整光追管线最大的区别是:它不是一个独立的管线,而是在普通着色器(计算着色器、片段着色器等)内部直接调用的函数。你不需要 SBT,不需要独立的管线,只要在 shader 里写:

rayQueryEXT rayQuery;
rayQueryInitializeEXT(rayQuery, accelerationStructure, gl_RayFlagsOpaqueEXT,
                      0xFF, origin, tMin, direction, tMax);
while (rayQueryProceedEXT(rayQuery)) {
    if (rayQueryGetIntersectionTypeEXT(rayQuery, true) ==
        gl_RayQueryCandidateIntersectionTriangleEXT) {
        // 处理三角形候选命中
        rayQueryConfirmIntersectionEXT(rayQuery);
    }
}
两种混合模式的架构差异
图 5-8两种混合模式的架构差异@ 本系列绘制

NVIDIA 的 Vulkan 光追教程里专门有一个"Ray Query Screen-Space Effects"样例,展示了如何用光线查询实现环境光遮蔽(AO)这种屏幕空间效果 NVIDIA Vulkan Ray Query 教程。它的混合管线非常简洁: 1. 光栅化 Pass:生成 G-buffer(世界位置、法线等) 2. Ray Query Pass:计算着色器使用光线查询处理可见像素 3. 效果合成:将 AO 结果叠加到基础颜色 4. 后处理:色调映射与输出

2.3 怎么选:决策树

那什么时候用完整光追管线、什么时候用光线查询呢?可以画一棵决策树:

问题一:你需要自定义相交(intersection shader)吗? - 需要 → 完整光追管线(光线查询不支持自定义相交) - 不需要 → 继续下一个问题

问题二:你需要多种材质/多种 hit group 吗? - 需要很多种(>5-10 种) → 完整光追管线(SBT 管理更系统) - 只有一两种 → 光线查询(在 shader 里手动分支就行)

问题三:你的效果是屏幕空间的吗? - 是(只处理可见像素) → 光线查询(天然契合,性能更好) - 不是(需要处理屏幕外的东西) → 看情况

问题四:你需要递归光线吗? - 需要(折射、多次弹射) → 完整光追管线(TraceRay() 可以递归调用) - 不需要(阴影、AO 只需要一道光) → 光线查询

经验法则: - 阴影、AO、简单反射 → 光线查询,轻量高效 - 全路径追踪、复杂材质系统、自定义相交 → 完整光追管线 - 混合使用 → 可以!同一个应用里可以同时用两种模式

ARM 在 Vulkanised 2024 的演讲中特别提到了混合反射(SSR + RQ) 这种策略——先用屏幕空间反射(SSR),失败或质量不足时回退到光线查询 ARM Vulkanised 2024 演讲。这样大部分像素用 SSR(零光追成本),少部分像素用光线查询(保证质量),整体效果和成本的平衡最好。

2.4 性能差异

从性能角度看,光线查询通常比完整光追管线略快或相当,原因是: 1. 不需要 SBT 查找的开销(虽然这部分在硬件里已经很快了) 2. 可以只处理可见像素(深度测试已经在光栅化阶段完成了) 3. 和计算管线的集成更自然,不需要切换管线类型

但这个差异通常不大——因为光线追踪的主要成本在光线遍历和求交上,而这部分两种模式是共享的(都走 RT Core)。真正的差异在编程模型和管理成本上,而不是原始性能。

2.5 两种模式的工程对照

把两种模式的差别做成一张表,选型时对着看比记住结论更可靠。

两种模式的工程对照
图 5-9两种模式的工程对照@ 本系列绘制

差别集中在五处。调用位置上,完整管线由 raygen 主动派发,光线查询则是着色器内部的一次内联调用;着色器绑定上,前者需要 SBT,后者不需要;递归弹射上,只有完整管线支持在 TraceRay 里再发光线;状态切换上,前者要切换管线对象,后者融进现有 pass;典型用途上,前者接 GI 与复杂反射,后者接阴影、AO 与反射回退。

图形管线的阶段划分
图 5-10图形管线的阶段划分@ Soumyasch / Wikimedia Commons,CC BY-SA 3.0
光线追踪原理示意图(主光线与二次光线)
图 5-11光线追踪原理示意@ De Volder Killian / Wikimedia Commons,Public domain

三条链路都有规范定义:完整管线见 Vulkan 光线追踪管线,光线查询见 Vulkan 光线遍历,两者的能力边界在规范里写得很明确。

判断标准只有一条:这条光线需不需要弹射。不需要弹射的(阴影、AO)走光线查询,省下一条管线的管理与切换成本;需要弹射的(GI、多次反射)走完整管线,代价是换来递归能力。把两者混在同一条管线里也是常见做法——阴影用光线查询、GI 用完整管线,各取所长。

知道了两种模式的区别,我们把视角拉高,看看完整的一帧里,混合管线到底分几步跑。


三、一帧的流水线:从光栅到光追再到合成

了解了两种混合模式之后,来看完整的混合渲染管线一帧是怎么跑的。不同引擎、不同效果组合的具体阶段会有差异,但大体结构是一致的。

3.1 典型混合管线的阶段划分

一帧的混合管线 Pass 顺序
图 5-12一帧的混合管线 Pass 顺序@ 本系列绘制

一个典型的、包含阴影 + 反射 + AO 的混合渲染管线,一帧大致分为以下阶段:

阶段 做什么 管线类型 输入 输出
1. Z-prepass 预深度,减少后续 pass 的 overdraw 光栅化 场景几何 深度缓冲
2. G-buffer Pass 渲染几何信息到多通道缓冲 光栅化 场景几何 + 材质 G-buffer(深度/法线/反照率/金属粗糙/运动向量)
3. 直接光照 Pass 计算直接光照(方向光 + 点光 + 聚光) 计算/光栅化 G-buffer + 光源数据 直接光照结果
4. 光追阴影 Pass 光线追踪阴影 光追管线 / 光线查询 G-buffer + TLAS + 光源位置 阴影 mask
5. 光追反射 Pass 光线追踪反射 光追管线 / 光线查询 G-buffer + TLAS 反射颜色 + 命中距离
6. 光追 AO Pass 光线追踪环境光遮蔽 光追管线 / 光线查询 G-buffer + TLAS AO 值
7. 降噪 Pass 对各光追输出做时空降噪 计算 光追输出 + G-buffer + 运动向量 + 历史缓冲 降噪后的光追结果
8. 合成 Pass 将光追结果与直接光照混合 计算 直接光照 + 光追结果 + G-buffer 最终颜色(未后处理)
9. 后处理 TAA、色调映射、Bloom、景深等 计算/光栅化 最终颜色 + 深度 + 运动向量 最终画面

注意这个顺序不是固定的。有些引擎会把阴影和反射合并到一个光追 pass 里(一次 dispatch 发射多种光线),有些会先做降噪再合成,有些会把 AO 放在全局光照里一起算。但G-buffer → 光追 → 降噪 → 合成这个大方向是通用的。

3.2 为什么光追要分多个 Pass

你可能会问:为什么不把所有光追效果放到一个 pass 里?一个 raygen shader 里同时发射阴影光线、反射光线、AO 光线,不好吗?

答案是:技术上可以,但工程上通常分开做更好。原因有几个:

成本结构不一样。阴影可能只要 1 ray/pixel,反射可能要 0.5-1 ray/pixel(只对反光材质发射),GI 可能要 1 ray/pixel。混在一起的话,每个像素都要做所有判断,分支开销反而更大。

降噪参数不一样。阴影、反射、GI 的降噪算法和参数都不同:阴影用轻量降噪器就够,反射要保住边缘,GI 需要更大的滤波半径。分开 pass 才能各自用上最合适的参数。

开关的粒度不一样。玩家可以只开阴影、关掉反射,或者把反射质量调低。全挤在一个 pass 里,这种粒度的开关就很难做。

第四,带宽和内存局部性。一个 pass 只读写一种数据,缓存命中率更高。如果一个 pass 同时读写阴影、反射、AO 的数据,寄存器和共享内存压力更大。

3.3 渲染技术对比:为什么需要"混合"

为了更直观地理解混合渲染的必要性,来看不同渲染技术的视觉效果差异。

辐射度开与关的对照:间接光带来的差别
图 5-13辐射度开与关的对照@ Morn / Wikimedia Commons,CC0

Wikimedia Commons 上有一组非常经典的"Rendering techniques example"系列图,对比了不同渲染技术的效果 Wikimedia Commons · 渲染技术对比:

  • 光栅化(Rasterization):速度最快,但只能处理直接光照,阴影和反射都是近似的
  • 光线追踪(Ray Tracing + Radiosity):可以计算全局光照和软阴影,但光线弹射次数有限
  • 路径追踪(Path Tracing):最真实的结果,但每像素需要成百上千样本才能收敛

混合渲染的思路就是:用光栅化做基础,用光追做"补丁"——光栅化搞定 90% 的像素和效果,光追补上剩下 10% 光栅化做不好的部分。这样既保证了性能,又大幅提升了画质。

3.4 同步点与性能影响

混合管线阶段越多,同步点就越多,性能损失也就越大。每次管线切换、每次屏障,都会让 GPU 管线"气泡"(bubble)——也就是 GPU 某个单元闲着没事干。

怎么减少同步开销?几个常用技巧:

  1. 合并同类型 pass:比如把阴影和 AO 合并到一个光追 pass 里,减少一次管线切换
  2. 合理安排顺序:把相同管线类型的 pass 放在一起,减少光栅化 → 光追 → 光栅化 这种来回切换
  3. 用子通道(Subpass):在 Vulkan 里,同一 render pass 的不同 subpass 之间不需要全局屏障,可以利用 tile memory
  4. 异步计算(Async Compute):把一些计算 pass(比如降噪)放到异步计算队列里,和图形队列并行执行

这些都是混合管线编排中的"艺术"——没有标准答案,需要根据具体的效果组合和硬件特性来调。

3.5 队列重叠的可行边界

前面提到把降噪这类计算 pass 放到异步计算队列,和图形队列并行。但"能并行"不等于"可以随便并行"——两个队列之间有依赖点,重叠空间是争取出来的,不是设计出来的。

一帧内两个队列的重叠
图 5-14一帧内两个队列的重叠@ 本系列绘制

把一帧按时间铺开,能重叠的段其实只有两处:一是阴影与 AO 的光追可以和下一批材质分类重叠;二是降噪链可以和 UI 渲染重叠。而 G-buffer 写完到光追开始之间、光追写完到降噪开始之间,这两处是硬依赖,必须同步,抢不来时间。

异步计算在图形与计算队列之间的调度由驱动决定,应用能控制的是提交顺序与依赖声明——这也是为什么重叠空间要靠 Pass 拆分去争取,而不是靠一个开关。

想扩大重叠空间,只有两个办法:把 Pass 拆得更细(比如把 GI 分成两半,前半降噪时后半还在跑),代价是同步次数变多;把依赖关系改成流水(比如降噪只处理已经完成的那部分屏幕),代价是实现复杂度上一个台阶。

这里有个反直觉的结论:同步次数少不一定比同步次数多快。一次大范围的屏障会让两个队列都停下来等;拆成几次小范围屏障,虽然次数多,但每次等的人少。判断依据是"每次屏障让谁等多久",而不是"一共屏障了几次"。

结构清楚了,接下来算账:一帧 16.67 ms 的预算里,各部分到底占多少?


四、性能预算表:16 ms 怎么分

讲完了管线结构,来算一笔账:一帧 16.67 ms(60 fps)的预算里,各阶段分别占多少?光追到底有多贵?降噪又有多贵?

4.1 典型帧时间分配

60 fps 帧预算怎么分
图 5-1560 fps 帧预算(16.67 ms)怎么分@ 本系列绘制

根据 NVIDIA Turing 架构白皮书和多方实测数据,一个典型的 60 fps 混合渲染帧(开阴影 + 反射 + AO)的时间分配大致是 NVIDIA RTX-OPS 白皮书:

阶段 占比 典型时间(16.67 ms 预算) 说明
光栅化(含 G-buffer + 直接光照) ~60% ~10 ms 主可见性 + 材质填充 + 直接光照
光线追踪 ~20% ~3.3 ms 阴影 + 反射 + AO,约 2-3 rays/pixel
降噪 ~15% ~2.5 ms 各效果的时空降噪
后处理 + UI ~5% ~0.8 ms TAA、色调映射、Bloom、UI 渲染

注意这只是一个非常粗略的估计。实际数字受很多因素影响: - 硬件:高端卡(RTX 4090)光追占比可能更低,低端卡可能更高 - 分辨率:4K 下光追和降噪的时间都会膨胀 - 效果强度:开了 GI 之后光追时间会翻倍甚至更多 - 场景复杂度:三角形越多、材质越复杂,光追越慢

但有一个结论是确定的:降噪的时间占比往往比人们直觉的要高。1 spp 的光追输出噪声很大,要把它洗干净,需要相当的计算量。在某些配置下,降噪的时间甚至可能超过光追本身。

4.2 不同光追效果的成本

不同光追效果的成本差异很大。来列一个对照表:

五种光追效果的相对开销
图 5-16五种光追效果的相对开销@ 本系列绘制
效果 每像素光线数 相对成本 说明
RT 阴影 1 ray/pixel 1×(基准) 通常是 shadow ray,只做遮挡测试,不需要着色
RTAO 1-4 rays/pixel 1-2× AO 光线短、距离近,BVH 遍历快
RT 反射 0.5-1 ray/pixel 1-2× 只对金属/光滑材质发射,但反射光需要做 closest-hit 着色
RT GI(1 弹射) 0.5-1 ray/pixel 2-3× GI 光线可能走得更远,命中后还要计算间接光照
全路径追踪 1-4+ rays/pixel,多次弹射 5-10×+ 每道光都可能产生新的光线,弹射次数越多成本越高

数据来源:多种行业资料综合 Kinda Technical · 光追与现代渲染

这里有三个反直觉的点。

阴影虽然只要 1 ray/pixel,但它是"全像素"的——每个被照亮的像素都要发射阴影光线。而反射只需要对反光材质发射,可能只有 30-50% 的像素需要。所以总开销上,阴影和反射可能差不多。

AO 光线虽然多,但每道光很短——AO 只需要查询半球范围内有没有东西遮挡,光线长度通常只有几米,BVH 遍历很快。所以 AO 的成本并不像光线数看起来那么高。

全路径追踪不是"多几道光线"的事——它的成本是指数级增长的。每多一次弹射,光线数量乘以场景中的材质数量(漫反射的话几乎每个命中都产生新光线)。所以 1 弹射和 4 弹射的成本差了好几倍。

4.3 带宽约束

光追的性能瓶颈不只是计算,还有内存带宽。每道光需要: - 遍历 BVH:读取 BVH 节点数据(ep02 算过,~10-100 bytes / triangle) - 命中后读取:顶点数据、纹理数据、材质参数

而且光追的内存访问模式比光栅化更随机——光栅化的访问是空间连贯的(相邻像素访问相邻的纹理坐标),而光追的光线方向是随机的,缓存命中率更低。

Utah 大学的一项研究详细测量了光追的带宽消耗,发现 DRAM 带宽占用在不同场景下差异很大——场景越小、光线相干性越好,带宽利用率越低;随着弹射次数增加,光线越来越发散,带宽压力会上升 Utah 大学 · 光追带宽消耗研究。

这也是为什么显存带宽是光追性能的关键约束之一。RTX 4090 有 1 TB/s 的带宽,而 RTX 4070 只有 ~600 GB/s,光追性能差距远大于光栅化性能差距,带宽是重要原因之一 CSDN · RTX 带宽对比分析。

4.4 光线预算的分配策略

既然光线是有成本的,那怎么分配才最划算?这其实是一个"性价比"问题:每道光线能带来多大的画质提升?

从性价比角度排序(从高到低):

  1. 阴影——光栅化的阴影贴图问题太多(走样、透视偏差、级联切换),光追阴影一上,阴影质量直接拉满,1 ray/pixel 的成本非常值
  2. 反射——SSR 的屏幕空间限制太明显(屏幕外的东西反射不出来),光追反射是质的飞跃
  3. AO——SSAO 已经不错了,但 RTAO 更准确、没有漏光问题,提升是有的但不如前两者明显
  4. GI——全局光照是效果提升最大的,但成本也最高,而且需要好的降噪器才能用 1 spp 跑
  5. 透明/折射——光追透明的成本很高(排序、多次命中),但效果提升相对有限

大多数游戏的光追设置因此都是"先开阴影、再开反射、最高开 GI"——按性价比从高到低排列。

4.5 预算超支时,砍的顺序

预算表好看,但项目里更常见的情形是反过来——效果定完了,一测发现超支。这时候砍什么、按什么顺序砍,比预算本身更能拉开差距。

预算超支时,砍的顺序
图 5-17预算超支时,砍的顺序@ 本系列绘制

砍的顺序应该按「每单位代价换来的画质」从低到高排,而不是按实现难度排,也不该按"哪个看起来最贵"排。

先砍效覆盖范围。GI 从 2 弹射降到 1 弹射,反射只对低粗糙度像素发射——这两个动作省下的是光线数,而视觉损失远小于数字上的损失,因为 2 弹射与 1 弹射的差异主要出现在颜色渗透的细节上,不是每个像素都看得出来。

再砍分辨率。光追与降噪走半分辨率,靠降噪器补回细节。这一刀省得很痛快(算力与带宽同时减半),代价是细小物体的边缘会出现轻微的拖尾——因为半分辨率的运动矢量在薄结构上本来就不准。

然后砍降噪质量。空域滤波从 5 级降到 3 级,省下的是降噪里最大的那一块(见 5.6 节)。代价是画面细腻度下降,但通常还能接受。

最后才砍光线数。阴影与 AO 的每像素光线数从 2 降到 1,噪点会明显变多,必须靠降噪器硬扛——这是最伤画质的一刀,所以排最后。

还有一条不走这条顺序的:绝不先砍同步与屏障。省下的那点时间会以偶发卡顿的形式还回来,而且这种卡顿最难定位。

4.6 一套可复用的预算估算流程

4.1 到 4.5 给的是结论,但每个项目的数字都不一样。真正能用的是流程——按这五步走,不用等美术资源到位也能把预算估到能用的精度。

第一步,先量基准,不要先猜。 拿一个已有场景,把光栅化部分单独跑一遍(关掉所有光追效果),记下 G-buffer + 直接光照的时间与带宽。这是你的基准线,后面每一项效果都是在它上面加出来的。跳过这一步直接估光追,十有八九会把光栅化的时间算少——它的占比通常比直觉更高。

第二步,把每项光追效果单独加进来测。 一次只开一个效果,记录它带来的增量。这样做的好处是能得到每项效果的边际成本,而不是一堆叠加后的总数——项目后期要砍的时候,需要知道砍掉哪一项能省多少,这个信息只能从边际成本里拿。

第三步,把降噪单独测。 降噪的增量要按效果分别测(5.6 节说过它是按效果跑的),并且要在"最坏场景"下测——快速运动、大量 disocclusion 的镜头会让降噪比平均情况贵不少。

第四步,留出预算缓冲。 上线的帧预算不该贴着 16.67 ms 排满。留 15% 到 20% 的余量给三类消耗:不同硬件的性能差异、玩家的分辨率选择、以及后期才加进来的 UI 与特效。把预算排到 16.5 ms 的方案,在低端卡上就是 40 fps。

第五步,按场景分类定档。 室内静态场景与室外动态场景的最优配置通常不同。与其找一个全局折中值,不如做两到三套配置,按场景类型切换——这在工程上比参数微调更有效,代价只是配置管理的一点复杂度。

这五步里最有价值的其实是第一步和第四步:前者决定你的账本可不可信,后者决定你的方案能不能活着上线。测量工具与方法论可以参照 GPUOpen 的性能测量指南——尤其注意区分「GPU 时间」与「帧时间」,前者才是能被优化掉的那部分。

预算分配清楚了,但有一个大项我们还没展开——降噪器。它有多重要?又有多贵?


五、降噪器的位置与作用

混合渲染管线里,降噪器(Denoiser)是一个关键但常被低估的角色。很多人以为"光追负责生成画面,降噪只是修饰一下",但——没有降噪器,实时路径追踪根本不可能。

5.1 为什么必须有降噪器

来算一道简单的数学题:路径追踪要得到干净的图像,每像素需要多少样本?

  • 离线渲染:通常 256-4096 spp(samples per pixel)
  • 实时 60 fps:假设我们有 3 ms 的光追预算,RT Core 每秒能处理 ~1 G 光线
  • 1080p 像素数:~2 M
  • 每像素光线数:1 G / 2 M = ~500 rays/pixel——等等,这是理论峰值,而且只是遍历和求交,还没算着色器

实时光追每像素只能发射 1-4 道光线。1 spp 是什么概念?几乎全是噪声——你看到的不是画面,是一堆随机闪烁的彩色噪点。

所以降噪器不是可选的,是必须的。它的工作是:把 1 spp 的噪声输入,通过时空滤波和先验知识,"推断"出干净的图像。我们可以用一组对比图来直观感受这件事——左侧是只有 1 个样本每像素的原始输出,右侧是经过降噪后的结果。

如果没有降噪器,左边那张满是噪点的图就是你能看到的全部——你甚至分辨不出画面里有什么。而经过降噪器处理之后,右边那张干净的图像才成为可能。NVIDIA 把"AI + 光线追踪 = 实时光追"当作核心宣传点,道理也在这里——两者缺一不可。

5.2 降噪在管线中的位置

降噪器在混合管线中的位置是:光追之后、合成之前。

具体来说,每个光追效果的输出都要经过降噪: - 光追阴影 → 阴影降噪 → 合成 - 光追反射 → 反射降噪 → 合成 - 光追 GI → GI 降噪 → 合成

为什么不合成之后一起降噪?因为不同效果的噪声特性不一样,需要不同的降噪参数: - 阴影的噪声是高频的(黑白颗粒),需要比较轻的滤波 - 反射的噪声包含颜色信息,需要保持边缘 - GI 的噪声很大而且是低频的,需要更大的滤波半径

如果混在一起降噪,就会出现"有的地方糊了、有的地方还有噪声"的情况。

5.3 SVGF 基本原理

目前工业界最主流的实时降噪算法是 SVGF(Spatiotemporal Variance-Guided Filtering,时空方差引导滤波)。它由三个主要步骤组成:

SVGF 的三步
图 5-18SVGF 的三步@ 本系列绘制

第一步:时间累积(Temporal Accumulation)

利用运动向量,把前一帧的结果重投影到当前帧,然后和当前帧的结果做指数移动平均(EMA)。相当于"跨帧复用样本"——如果历史累积了 10 帧,等效采样数就是 10 spp。

但这一步有很多坑: - 相机移动时,新暴露的区域没有历史数据,需要重置累积 - 物体移动时,遮挡关系变化,可能出现"鬼影"(ghosting) - 不连续的区域(物体边缘)不能随便平均

所以时间累积需要配合重投影验证(reprojection validation)——深度、法线、颜色差异太大的像素,说明历史不可靠,要降低历史权重甚至重置。

第二步:方差估计(Variance Estimation)

分析当前图像的噪声水平。噪声大的地方,滤波强度要大;噪声小的地方,滤波强度要小(避免过度模糊)。

方差估计本身也是从噪声图像里算的,所以也需要滤波——通常用一个 3x3 的双边滤波器先把方差图平滑一下。

第三步:A-Trous 小波滤波(Spatial Filtering)

这是空域降噪的核心。A-Trous("with holes")小波滤波用逐步增大步长的方式,在保持边缘的同时实现大范围的滤波。普通的大核高斯模糊是 O(r²) 的(r 是半径),而 A-Trous 是 O(log r) 的,性能要好得多。

滤波用方差来引导——方差大的地方(噪声大)滤波更强,方差小的地方滤波更弱。同时用 G-buffer 的深度和法线做边缘停止(edge-stopping),保证物体边缘不会被模糊掉。

SVGF 最早由 Schied 等人在 2017 年提出,之后成为了实时光追降噪的事实标准。原始论文给出的性能数据是 FullHD 下约 10 ms(NVIDIA TITAN X),现代硬件上更快,但仍然是一笔不小的开销 SVGF 论文(JCGT)。

5.4 降噪的成本

降噪到底有多贵?我们来看几组实测数据:

NVIDIA NRD(NVIDIA Real-Time Denoisers) 在 RTX 4080 @ 1440p 下的性能 Brightcoding · NVIDIA NRD 实测:

降噪器 用途 时间
SIGMA_SHADOW 阴影降噪 0.40 ms
REBLUR_DIFFUSE_SPECULAR 漫反射+高光降噪 2.50 ms
RELAX_DIFFUSE_SPECULAR 漫反射+高光(更高质量) 3.20 ms

可以看到,反射/ GI 降噪的时间(2.5-3.2 ms)和光追本身的时间是同一个量级的。如果光追花了 3 ms,降噪也花 3 ms,那降噪占了总光追相关预算的一半。

这也是为什么我说"降噪占预算可能比光追还多"不是夸张——如果你开了多个光追效果,每个都要降噪,加起来的时间确实很可观。

5.5 降噪的边界条件

降噪不是万能的,它有失效的时候: - 快速运动的物体:时间累积用不上,只能靠空域滤波,质量下降 - 高对比度的细节:降噪容易把小细节"洗"掉 - 萤火虫(firefly):极亮的小光源会在周围产生大噪点,需要专门的萤火虫抑制 - 离焦模糊和运动模糊:这些效果会让时间重投影更困难

理解降噪的边界很重要——它不是"给我 1 spp,还你 4096 spp"的魔法。它是一个在性能和质量之间取平衡的工程工具,知道它什么时候好用、什么时候会出问题,才能正确地把它放进混合管线里。

5.6 降噪那 2.5 ms 花在哪

4.1 节给出降噪约占 15%(约 2.5 ms)。这个数很容易被当成一个整体,但拆开看才知道钱花在哪。

降噪那 2.5 ms 花在哪
图 5-19降噪那 2.5 ms 花在哪@ 本系列绘制

SVGF 的三步在原论文里有完整定义,这里只算它的成本分布。

SVGF 的三步里,空域滤波占了大头(约 1.6 ms,64%),因为它要跑五级 A-Trous,每级五个抽头,还要在每级读取法线、深度与方差做边缘感知;时间累积只占约 0.6 ms,因为它就是"读历史、算 α、写回"三步;方差估计约 0.3 ms。

这个分布直接给出了优化的着力点:想把降噪砍掉一半,降低空域滤波级数(5 级降到 3 级)比动 α 有效得多。但代价不对称——降级数会让边缘更容易被糊,而降 α 只是让噪点变多。前者伤细节,后者伤干净度,选哪个取决于你的场景里哪个更显眼。

含噪测试图像
图 5-20含噪测试图像@ Bautsch / Wikimedia Commons,CC0
实拍图像中的彩噪
图 5-21实拍图像中的彩噪@ Smalljim / Wikimedia Commons,Public domain

这两张说的是一件事:降噪要面对的噪声不止一种。上面是合成测试图的颗粒噪声,下面是实拍照片里的彩噪——后者的空间相关性更强、更成块,滤波时更容易留下痕迹。而无论哪一种,把噪声压下去的同时,纹理与边缘也常常被一起磨平。时域降噪的相对优势正在于此——它用多帧信息替代单帧邻域,所以能少牺牲一点细节;代价则是引入滞后与鬼影这类只属于时间维度的问题(ep06 会展开)。

还有一笔常被忽略的账:降噪是按效果分别跑的。阴影、AO、反射、GI 各有一套时域与空域数据,如果全部独立降噪,上面这 2.5 ms 要乘上效果数量。所以工业实现里常见做法是把 AO 并进阴影、把反射的镜面通道与漫反射通道分离处理,用通道复用摊薄成本。NVIDIA 的 NRD 就是把不同信号的降噪器做成可组合的组件,让阴影、AO、漫反射、镜面各用一个实例但共享同一套时域框架;AMD 侧的降噪器则把 disocclusion 掩码与颜色矩形修正做成公共前置步骤,同样是为了复用。

降噪器是从"输出端"提升质量,那能不能从"输入端"就让光线更有效?ReSTIR 就是干这件事的。


六、ReSTIR:小样本骗过大方差

如果说降噪器是"用光追结果 + 历史 + 滤波"来提升画质,那 ReSTIR 就是另一个思路——在光线发射的阶段就用聪明的采样方法,让同样数量的光线产生更高质量的结果。

6.1 问题:光源太多,随机采样效率太低

直接光照(direct lighting)是渲染里的一个经典问题:场景里可能有成千上万个光源,每个像素随机选一个光源发射阴影光线,结果大概率是——这光源根本照不到这个像素,这道光线就浪费了。

传统的解决方案是重要性采样——挑更可能照亮当前像素的光源。但光源太多的时候,重要性采样的提升也有限。

ReSTIR(Reservoir SpatioTemporal Importance Resampling,时空蓄水池重要性重采样) 的思路完全不一样:不是让每个像素自己选光源,而是从邻域和历史帧里"借"好的样本。

6.2 核心思想:蓄水池采样 + 时空复用

要理解 ReSTIR 为什么能用这么少的光线达到这么好的效果,我们得先从它的核心数据结构和两个关键机制说起。下面这张图展示了 ReSTIR 的整体工作原理——蓄水池是怎么存样本的,以及时空复用又是怎么一回事。

ReSTIR 的四步
图 5-22ReSTIR 的四步@ 本系列绘制

从图中可以看到,ReSTIR 的核心数据结构是加权蓄水池(weighted reservoir)。一个蓄水池里存着: - y:当前选中的样本(比如一个光源) - M:已经看过的样本总数 - w_sum:所有样本的权重之和 - W:控制权重

蓄水池采样的妙处在于:不管 M 是多少,你只需要存一个样本,就能保证每个样本被选中的概率和它的权重成正比。这在内存上非常高效。

ReSTIR 在此基础上加了两个复用机制:

空间复用(Spatial Reuse):从相邻像素的蓄水池里"借"样本。如果邻居的光源质量好,我也可以用。

时间复用(Temporal Reuse):从上一帧同一个位置的蓄水池里"借"样本。如果光源和几何都没怎么动,历史样本仍然有效。

通过这两层复用,ReSTIR 可以用 1 道阴影光线,实现等效几十甚至上百道光线的质量——这就是"小样本骗过大方差"的含义 Advances in Realtime Rendering · MegaLights。

6.3 ReSTIR 在混合管线中的位置

ReSTIR 通常用在直接光照 + 阴影这一环。它的位置是: - G-buffer 之后 - 光追阴影 Pass 之前(或者说,ReSTIR 决定了"这一帧每个像素该朝哪个光源发射阴影光线")

Unreal Engine 5 的 MegaLights 技术就是基于 ReSTIR 的——它可以处理成千上万个动态光源,而性能开销却很低。Epic 的 GDC 演讲里提到,MegaLights 是 UE5 实时光照的核心技术之一。

ReSTIR 不是"替代"光追,而是提升光追的效率——它让你用更少的光线得到更好的直接光照结果,但仍然需要光追来做阴影可见性测试。

6.4 限制与边界

ReSTIR 虽好,但也不是没有边界: - 它主要解决直接光照的采样问题,间接光照(GI)需要 ReSTIR GI 等变种 - 空间复用需要邻域像素的样本是可复用的——如果邻域的法线和深度差异太大,复用会引入伪影 - 时间复用需要场景相对稳定——快速运动的光源或物体可能导致质量下降 - 实现复杂度比较高——需要管理蓄水池、时空复用、偏差校正等

但尽管有这些限制,ReSTIR 仍然是近五年实时光追领域影响最大的算法进展之一——它让"万光源场景的实时阴影"从不可能变成了可能。

6.5 ReSTIR 的工程代价

ReSTIR 的效果很诱人,但把它放进项目里要考虑的东西不少。

反射方向的采样示意
图 5-23反射方向的采样示意@ Optimsmoose / Wikimedia Commons,CC BY-SA 3.0

蓄水池的状态要存。每个像素一份蓄水池(选中的样本、样本数 M、权重和 w_sum、控制权重 W),按分辨率换算就是几张全屏缓冲,和降噪器的显存账是一个量级。这是实打实的常驻开销,不是"算法换性能"。

算法的原始出处见 ReSTIR 论文,这里只讲工程侧要准备什么。

时空复用会引入偏差。空间复用从邻居借样本的前提是"邻居与我的可见性差不多",这个假设在几何边缘、阴影边界、运动物体上都不成立。所以实际实现必须配一套可见性回退检测(复用前先验证那个样本在当前像素上是否可见),这套检测本身要发光线,吃掉一部分收益。

实现复杂度集中在调试上。蓄水池的权重公式一旦有偏差,画面不会崩,只会慢慢地变暗或偏色——这种错误极难定位,因为每一步看起来都对。

所以 ReSTIR 的现实定位是:它解决的是"光源多到没法逐光源采样"这个特定问题,不是普适的性能优化。如果你的场景里主要光源就几个,用传统的重要性采样加降噪可能更省事;如果场景里有成百上千个光源(大量局部光源的室内场景、夜晚城市),ReSTIR 才是不可替代的那个。

讲完了所有组件,最后一个问题:实际项目中,各个效果的混合档位怎么选?


七、常见混合策略组合

讲完了混合管线的组件,来看实际项目中,各个效果是怎么选择混合策略的。每个效果都不是非黑即白的"光追 or 光栅",而是有多个档位可以选。

7.1 阴影:从阴影贴图到光追阴影

阴影的四个档位
图 5-24阴影的四个档位@ 本系列绘制
档位 技术 质量 成本 适用场景
1 硬阴影贴图(Shadow Map) 低(锯齿硬边) 很低 性能优先
2 PCF / PCSS 软阴影 中(边缘软化但走样) 低 中端硬件
3 混合阴影(关键光源光追) 高(主光源光追,其他阴影贴图) 中 平衡选择
4 全光追阴影 最高(像素级精确) 高 高端硬件

阴影是最适合用光追提升的效果之一——因为阴影贴图的问题实在太多了:透视偏差、走样、级联切换、软阴影质量差。光追阴影一上,这些问题基本都解决了。

但全光追阴影也不是每个光源都要追——通常只有太阳/方向光这种最主要的光源用光追,点光/聚光还是用阴影贴图。这就是"混合阴影"的思路。

7.2 反射:从 SSR 到光追反射

档位 技术 质量 成本 适用场景
1 屏幕空间反射(SSR) 低(只能反射屏幕内的东西) 很低 性能优先
2 SSR + 平面反射探针 中(平面物体有反射) 低 中端硬件
3 混合反射(SSR + RQ 回退) 高(屏幕内用SSR,屏幕外用光追) 中 平衡选择
4 全光追反射 最高(精确反射) 高 高端硬件

反射是另一个光追提升非常明显的效果。SSR 的问题大家都知道——屏幕外的东西反射不出来,物体边缘有明显的截断痕迹。

混合反射(SSR + Ray Query 回退) 是目前性价比最高的方案:先用 SSR 试,如果命中了(反射的物体在屏幕内),就直接用 SSR 的结果(零光追成本);如果没命中(屏幕外或深度不连续),再发射光线查询。这样大部分像素走 SSR,少部分像素走光追,整体成本很低但质量提升明显 ARM Vulkanised 2024 演讲。

7.3 全局光照:从 SSAO 到光追 GI

档位 技术 质量 成本 适用场景
1 SSAO(屏幕空间 AO) 低(只有局部遮蔽) 很低 性能优先
2 光照探针 + SSAO 中(有全局感但不动态) 低 中端硬件
3 VXGI / 体素 GI 中高(动态但低精度) 中 特定场景
4 光追 GI(1 弹射) 高(真实的间接光照) 高 高端硬件
5 全路径追踪 GI 最高 非常高 发烧友 / 离线

全局光照是效果提升最大但成本也最高的光追效果。为什么?因为 GI 需要计算光线弹射到其他物体后再弹回来的光照——这意味着更多的光线、更远的距离、更复杂的着色。

目前实时光追 GI 通常只做1 次间接弹射——也就是光从光源照到物体 A,然后从 A 弹到物体 B,我们计算 B 受到的间接光照。再多弹射的话,成本就太高了,需要 ReSTIR GI 或其他高级算法。

7.4 环境光遮蔽:从 SSAO 到 RTAO

档位 技术 质量 成本 适用场景
1 SSAO 中 很低 默认选项
2 HBAO+ / GTAO 中高 低 质量优先的光栅方案
3 RTAO 高(精确、无漏光) 中 光追硬件

AO 是一个比较特殊的效果——光栅化方案(SSAO/HBAO/GTAO)已经做得相当好了,光追 AO 的提升不如阴影和反射那么明显。所以 RTAO 通常是"有多余光追预算再开"的选项,而不是必开项。

7.5 策略组合建议

根据硬件档次,推荐的组合是:

  • 入门级:光追阴影 + SSR(反射不开光追) + SSAO → 阴影是性价比最高的
  • 中端级:光追阴影 + 混合反射(SSR+RQ) + SSAO → 反射也上光追但用混合方案
  • 高端级:全光追阴影 + 全光追反射 + RTAO → 主要效果全开
  • 旗舰级:上面全部 + 光追 GI → 效果拉满,但成本也拉满
四类效果的混合策略矩阵
图 5-25四类效果的混合策略矩阵@ 本系列绘制

7.6 组合策略的落地顺序

档位选完,还有一个问题:先做哪个、后做哪个。顺序的依据是单位光线的画质回报。

组合策略的落地顺序
图 5-26组合策略的落地顺序@ 本系列绘制

档位的划分参照了 UE5 的降噪与超分文档与厂商公布的效果清单,但落地顺序要按回报而不是按文档顺序排。

先定画质目标与预算,再按回报排序。阴影与 AO 排在最前:它们的单位光线回报最高——阴影解决的是"有没有"的问题(光栅阴影的走样与偏差是硬伤),AO 解决的是"暗部有没有细节"的问题,两者各 1 ray/pixel 就能拿到明显的画质提升。

反射排在中间:它只在光滑表面上起作用,覆盖率取决于场景(室内比室外更值得开)。做法上优先用 SSR 覆盖屏幕内能解决的部分,屏幕外的部分再用光线查询回退——这样每像素的期望光线数就降下来了。

GI 排在最后:它的画质提升最大,但成本也最高,而且必须配一套成熟的降噪器才能用 1 spp 跑起来。上 GI 之前先问一句:现有的降噪器能不能扛住间接光的方差?扛不住的话,GI 会变成一堆会动的噪声。

光追渲染成品:光线弹射组成的几何构图
图 5-27光追渲染成品@ Rob su / Wikimedia Commons,CC BY-SA 4.0

这张图里的每个像素都由光线的多次弹射得出——反射与阴影之间的关系因此天然自洽,没有屏幕空间近似那种「看起来对、边界处却会断」的问题。这也是混合策略要保住的东西:把光线花在肉眼能分辨正确性的地方,剩下的交给光栅化与屏幕空间近似。

7.7 混合管线的五个常见坑

前面讲的都是"该怎么配",这一节讲"哪里最容易配错"。这五条都是混合管线里反复出现的坑,共同点是:出问题时不会崩,只会慢或画质不对。

坑一:把 G-buffer 当免费资源。 需求方提一句"能不能再加个通道",实现方就在 G-buffer 里加一张 RT——单张看不多,但 1.5 节算过,每多一个字节/像素,1440p 下每帧就是几 MB 的读写。通道要加之前先问:这个信息能不能从已有通道推出来?能不能打包进现有 RT 的 Alpha 通道?加通道是最贵的那种"方便"。

坑二:光追 Pass 拆得太细。 每个效果一个 pass 看起来清晰,但 pass 之间都要同步。3.5 节说过,同步的代价取决于"让谁等多久"——把一个 pass 拆成四个,屏障次数翻两番,收益可能还不如把它们合并起来。建议先按"能否共享同一批 G-buffer 读取"分组,而不是按效果名称分组。

坑三:降噪按效果独立跑。 阴影一套、AO 一套、反射一套、GI 一套,四套降噪叠加,时间直接乘四。工业实现里几乎都会做通道复用:AO 并进阴影的降噪链、反射的漫反射与镜面分量分开但共享时域数据。这一条是"知道就能省一半"的典型。

坑四:半分辨率信号没做边缘保护。 把运动向量或 AO 降到半分辨率能省带宽,但半分辨率的运动矢量在物体边缘会有半个像素的错位。如果不做保护(比如在边缘处回退到全分辨率的运动矢量,或者用一个边缘掩码降低该像素的时间累积权重),降噪器就会在边缘拖出细长的鬼影——而且这种鬼影只在运动镜头下可见,静态截图测不出来。

坑五:在异步队列上挂太长的依赖链。 把计算 pass 放异步队列的初衷是重叠,但如果这条链上的每一步都依赖上一步的输出,那么"并行"就成了"排队",还额外付了一次跨队列同步的成本。判断方法很简单:画出两个队列的时间线(3.5 节那张就是),如果计算队列的段之间没有空隙可以塞图形队列的工作,那这条异步就没有意义。

这五条有个共同的排查思路:先看时间线,再看带宽,最后才看参数。混合管线的绝大多数问题不在参数上,在结构上。


八、收束与交棒

8.1 三句话判据

到这里,混合管线编排的八章就讲完了。我们用三句话总结一下:

三句话判据
图 5-28三句话判据@ 本系列绘制

第一句:混合渲染不是过渡方案,是稳态。 光栅化做主可见性、光追补光栅做不好的效果、降噪器把低采样率结果洗干净,三件事各司其职。在可预见的未来,这种分工不会改变——不是因为光追不够快,而是因为光栅化做可见性实在太快了,光追没有必要在这件事上和它竞争。

第二句:G-buffer 是数据接口。 光追管线不是凭空运行的,它的输入来自光栅化的 G-buffer。深度、法线、颜色、材质、运动向量——这些数据由光栅化写入,由光追读取,再由合成 pass 混合。G-buffer 的格式和布局,直接决定了混合管线的效率和灵活性。

第三句:降噪占的预算可能比光追还多。 1 spp 的光追输出几乎全是噪声,必须靠降噪器才能看。SVGF 三步(时间累积 + 方差估计 + A-Trous 滤波)每一帧都要跑,而且每个光追效果都要单独降噪。在很多配置下,降噪的时间确实和光追本身是同一个量级的。做性能预算的时候,千万别忘了把降噪算进去。

8.2 交棒:降噪(上)

ep05 讲了混合管线的整体编排——光追和光栅化怎么配合、数据怎么流、性能怎么分、降噪器为什么重要。但降噪器本身我们只讲了个大概——SVGF 的三步骤、时间累积、空域滤波。

降噪是一个比看起来深得多的话题。时间累积怎么处理鬼影?空域滤波怎么保持边缘?神经降噪和传统降噪有什么区别?时域复用的失效模式有哪些?这些问题,ep06 来回答。

下一篇(预告):【实时光线追踪与混合渲染】(六) 降噪(上):时域复用的原理与失效模式


资源清单

本文引用的核心参考资料与工具:

类型 名称 说明
规范 Vulkan Ray Tracing (VK_KHR_ray_tracing_pipeline) 光追管线 API 规范
规范 Vulkan Ray Query (VK_KHR_ray_query) 光线查询扩展规范
论文 SVGF (Spatiotemporal Variance-Guided Filtering) 实时降噪算法,Schied 等 2017
论文 ReSTIR (SIGGRAPH 2020) 时空蓄水池重要性重采样
白皮书 NVIDIA Turing Architecture In-Depth Turing 架构光追性能数据
白皮书 NVIDIA RTX-OPS Description RTX-OPS 性能模型
演讲 ARM Vulkanised 2024 移动端混合渲染与 SSR+RQ 策略
演讲 Epic Games MegaLights (GDC 2025) ReSTIR 在 UE5 中的应用
研究 Utah 大学光追带宽分析 光追 DRAM 带宽消耗测量
工具 NVIDIA NRD (Real-Time Denoisers) 工业级实时降噪器实测数据

本篇作者:溯光 本系列(实时光线追踪与混合渲染,共 12 篇):分界线 → 加速结构工程账(上)→ 加速结构工程账(下)→ SBT → 混合管线编排 → 降噪(上)→ 降噪(下)→ ReSTIR → 光线预算表 → RT 资源生命周期 → 透明与体积 → 反射与阴影混合 → 跨平台差异 前置专题:《实时渲染管线探析》5 篇 · 《移动端图形与功耗》7 篇 · 《GPU 架构剖析》12 篇 下一篇预告:【实时光线追踪与混合渲染】(六) 降噪(上):时域复用的原理与失效模式

希望能对大家有所帮助。