GDC 2022 - Performant Reflective Beauty: Hybrid Raytracing with Far Cry 6

GDC 2022 - Performant Reflective Beauty: Hybrid Raytracing with Far Cry 6

项目背景与动机

游戏场景特点

  • Far Cry 6 设定在虚构的热带岛屿 Yara,拥有全新的 全动态天气系统(详见同期 GDC 演讲 "Simulating Tropical Weather in Far Cry 6")
  • 频繁的降雨和暴风雨使得游戏中存在大量 潮湿、光滑、高反射率的表面
  • 游戏世界观设定:经济制裁导致居民保留大量 老式汽车,车身拥有突出的 镀铬特征,对反射质量要求极高
  • 硬件光线追踪(HW Ray Tracing) 是为 PC 玩家提供额外真实感和沉浸感的关键技术,特别是能提供 屏幕外反射(Off-screen Reflections),这对于还原经典车辆外观至关重要

反射管线总览(Reflection Pipeline)

Far Cry 6 的反射系统采用 混合方案(Hybrid Approach),整体管线分为以下几个阶段:

管线流程

  1. 光线生成(Ray Generation):生成用于所有不同类型反射追踪的光线
  2. SSLR 通道(Screen Space Local Reflections):执行屏幕空间反射,并 判定哪些像素需要硬件加速光线追踪(即 SSLR 失败/无法覆盖的区域)
  3. 硬件加速光线追踪与光照通道(不含粒子):对 SSLR 无法处理的区域进行 HW RT
  4. 粒子专用 HW RT 通道:粒子是特殊情况,最好在 单独的第二个 HW RT pass 中处理
  5. 最终整合通道(Integration Pass):将所有不同类型反射的结果进行合成

核心思路:先用廉价的屏幕空间方法覆盖尽可能多的区域,仅对 SSLR 无法解决的部分使用昂贵的硬件光线追踪。

屏幕空间反射基础

  • 基于 SIGGRAPH 2015"Stochastic Screen Space Reflections" 方法论
  • SSLR 作为第一道防线,能以较低成本处理屏幕内可见的反射信息

光线生成与重要性采样策略

反射波瓣与 BRDF

  • 使用 GGX BRDF 来确定 反射波瓣(Reflection Lobe)
  • 完整覆盖整个反射波瓣的光线追踪在性能上 代价过高,因此采用 重要性采样(Importance Sampling) 来优化光线放置

重要性采样 + Hammersley 序列

  • 重要性采样(Importance Sampling):根据 BRDF 分布集中将光线分配到贡献更大的方向,避免浪费
  • 结合 预生成的 Hammersley 序列(Pre-generated Hammersley Sequence):确保光线在采样域内的 均匀分布,避免聚集
  • 生成的光线存储在 光线缓冲区(Ray Buffer) 中供后续多个 pass 复用

每像素一条光线的策略

  • 目标:每个纹素(texel)每帧只发射 一条光线(1 ray per texel per frame)
  • 问题:单条光线不足以正确计算反射结果(除非是 完美镜面反射 的情况)
  • 解决方案——邻域复用(Neighbor Reuse)
    • 相邻像素使用 相似的重要性采样分布相同的 Hammersley 分布
    • 因此可以 收集邻居像素的光线命中点(hit points),并在 BRDF 积分 中复用这些值
    • 这是一种空间上的信息共享策略,用极低的光线预算获得足够的采样信息

半球裁剪优化

  • 观察发现:位于半球 边界区域 的光线几乎 不会对 BRDF 积分产生显著贡献
  • 优化措施收缩半球(Shrink the Hemisphere)
    • 当光线落在边界区域时,重新发射光线(Reshoot)
    • 只使用半球中 更有意义的部分 的采样值
    • 这样能让有限的光线预算发挥最大作用

光线缓冲区存储

  • 光线存储在 半分辨率缓冲区(Half Resolution Buffer)
  • 通过 时间累积(Temporal Accumulation) 进行上采样到全分辨率
  • 该光线缓冲区被所有不同类型的反射追踪共享使用

关键技术要点总结

技术要点说明
混合反射管线SSLR 优先 → HW RT 补充 → 粒子单独处理 → 最终整合
GGX BRDF用于定义反射波瓣形状
重要性采样 + Hammersley 序列在有限光线预算下最优化采样分布
每像素 1 条光线通过邻域复用实现低光线预算下的正确 BRDF 积分
半球裁剪丢弃边界低贡献光线,集中资源于高贡献方向
半分辨率 + 时间累积降低计算量,利用时域信息恢复全分辨率质量

核心设计理念

Far Cry 6 的混合反射系统体现了一个核心工程哲学:在实时渲染的严格性能预算下,通过多层次的降本策略(屏幕空间优先、重要性采样、邻域复用、半球裁剪、半分辨率+时间累积)层层削减光线追踪的开销,同时尽可能保持视觉质量。每一层策略都精准地针对一个具体的性能瓶颈,形成了一套完整的从光线生成到最终合成的高效管线。

屏幕空间局部反射(SSLR)与硬件光线追踪加速结构


屏幕空间局部反射(SSLR)概述

SSLR 的角色定位

  • 屏幕空间追踪(Screen Space Trace)在 屏幕空间 中进行,比在世界空间中的 硬件加速光线追踪(HW RT) 快得多
  • SSLR 作为 HW RT 结果 不可用时的回退方案(Fallback)
  • 绿色区域代表 仅使用屏幕空间反射 即可覆盖的部分

Far Cry 5 遗留问题

  • Far Cry 5 中使用的 SSLR 基于 层级 Z 追踪(Hierarchical Z Trace),但存在 不稳定 的问题
  • 根本原因:层级 Z 追踪的 迭代次数有限,可能会 用尽迭代步数
  • 表现症状:当相机缓慢旋转时,反射中会出现 闪烁(Flickering),原因是间隙在反射中不断形成和消失

解决方案:线性追踪 + 细化(Linear Trace with Refine)

核心算法

  1. 线性追踪阶段:在深度缓冲区层级的 Mip 0 上执行线性追踪
    • 首先取 64 个均匀分布的步进点,粗略检查是否存在交点
    • 每个步进点检查是否落在该像素存储的 最小深度(Min Depth) 之后(即几何体背面)
  2. 细化阶段(Refine):当检测到光线落在几何体后方时
    • 回退到 上一步
    • 在该步内使用 8 个均匀分布的细化步进 来精确寻找交点

效果

  • 线性追踪产生 稳定的结果,即使相机旋转也不会闪烁
  • 性能与层级 Z 追踪相同——既稳定又不增加开销

边缘遗漏问题(Edge Cases)

  • 问题:粗糙的线性步进可能会 跨过几何体的边缘
    • 线性追踪的两个采样点可能都处于 未被遮挡 的位置(不在深度缓冲的几何体后方)
    • 导致追踪 直接跳过边缘 继续前进,从而 丢失边缘处的反射信息
  • 解决方案——随机光线起始偏移(Random Ray Start Offsets)
    • 给每条光线的起点添加 随机偏移
    • 使边缘处的反射结果变得 更加稳定,有效缓解了边缘遗漏

屏幕空间 Tile 分类系统(SS Tile Classification)

动机

  • 高精度线性追踪 对于 光泽反射(Glossy Reflections) 来说 精度过剩,浪费性能
  • 需要找到一种方法:区分哪些区域需要高精度追踪,哪些可以使用更快的追踪方式

分类策略

  • 对屏幕进行 Tile(瓦片)分类,根据材质的 光滑度(Smoothness) 确定精度需求:
    • 高光滑度材质(如水坑、镀铬表面)→ 使用 高精度线性追踪
    • 其他反射表面(较粗糙的材质)→ 使用 更高性能的快速追踪
  • 可视化中,绿色区域 表示使用了快速追踪,高精度追踪仅保留给 镜面般的表面(如水坑)

快速锥体追踪(Fast Cone Trace)

  • 用于 光泽反射(Glossy Reflections) 的低精度但高性能方案
  • 锥体角度(Cone Angle) 随材质 粗糙度(Roughness) 增大而增大,提供 动态性能调节 能力
  • 具体实现
    1. 第一个圆的面积约为 一个像素大小,对应 Mip 0
    2. 每一步迭代中,将屏幕空间中圆的面积 映射到最接近的 2 的幂次
    3. 然后采样对应 Mip 级别的深度图(Depth Map)
  • 精度虽低,但实验表明粗糙表面通过添加这些反射仍获得了 明显的质量提升

硬件加速光线追踪(HW Ray Trace)加速结构

加速结构基础

  • 即使有最新硬件,在世界空间中追踪光线的 成本依然极高
  • 业界标准方案:使用 包围体层级(Bounding Volume Hierarchy,BVH) 来组织场景

BVH 的两层结构

层级名称说明
顶层加速结构TLAS(Top-Level Acceleration Structure)组织场景中各个物体实例
底层加速结构BLAS(Bottom-Level Acceleration Structure)描述单个物体/网格的几何细节

性能考量:选择性使用 HW RT

  • BVH 覆盖距离有限——这是 性能优化的关键
  • 不同距离采用不同反射策略:
距离范围反射方案原因
远距离仅 SSLRBVH 不覆盖远处,且远处 SSLR 质量尚可
近距离仅 HW RT近距离反射大量来自 屏幕外内容(Off-screen),SSLR 不可靠
中距离混合,取决于置信度根据置信度动态选择

置信度系统(Confidence System)

SSLR 置信度

  • 原理:读取深度缓冲区时,相邻像素之间的深度变化通常 很小
  • 低置信度判定:当相邻像素之间出现 突然且巨大的深度差异 时,说明存在 屏幕外缺失的细节
    • 这意味着屏幕空间追踪在该区域 不可信
    • 此时应优先使用 HW RT

HW RT 置信度

  • 近距离:HW RT 反射的置信度为 100%
  • 接近 BVH 外边界时:置信度 逐渐降低至 0
    • 这种渐变策略可以 防止突然切换导致的画面跳变(Popping)

置信度混合逻辑

  • 中距离区域的最终反射 = 根据两种方法的 置信度权重 进行混合
  • SSLR 置信度低 → 倾向使用 HW RT
  • HW RT 置信度低(靠近 BVH 边界)→ 倾向使用 SSLR
  • 通过这种 双向置信度系统,实现 SSLR 与 HW RT 之间的 平滑过渡

混合反射置信度系统与硬件光线追踪管线细节


混合反射置信度合成(Reflecting with Confidence: Hybrid)

四级置信度分层

系统根据 SSLRHW RT 各自的置信度评分,将屏幕上的每个像素归入不同的反射来源:

颜色标识置信度状态使用的反射方式
绿色SSLR 完全置信仅使用 SSLR
青色(Teal)混合区域比较两者置信度分数,取最高者
深蓝色SSLR 无置信仅使用 HW RT 反射
其他区域SSLR 和 HW RT 均无置信回退到 环境贴图反射(Environment Map Reflections)

环境贴图的详细内容参见同期 GDC 演讲 "Simulating Tropical Weather in Far Cry 6"。

设计思路

  • 整个反射管线是一个 分层回退系统(Layered Fallback):SSLR → HW RT → 环境贴图
  • 通过 置信度评分机制 实现各层之间的平滑过渡,避免突兀的切换伪影

光线追踪反射管线图(Raytrace Reflection Graph)

异步计算调度

  • 加速结构构建(Build AS)异步计算(Async Compute) 上执行
    • 仅当 GPU 瓶颈时才启用异步(通常在 2K 分辨率及以上 时触发)
    • 否则,Build AS 留在 主 GPU 管线 上执行
  • 原因:异步计算虽然能并行工作,但会引入 同步开销(Synchronization Overhead),在非 GPU 瓶颈的情况下反而可能更慢

粒子加速结构

  • 粒子拥有 独立的加速结构
  • 构建时间极短:低于 0.1 毫秒

BVH 加速结构管理策略

BLAS 组织原则

  • 遵循 一个 BLAS 对应一个物体(One BLAS per Object) 的原则
  • 避免 按材质(per Material)创建 BLAS,因为这会导致 BLAS 中 子网格之间存在大量空白空间,降低追踪效率

BLAS 更新策略

类型更新频率说明
静态 BLAS仅构建一次不需要更新
蒙皮 BLAS(Skinned)持续更新从旧到新的循环顺序 不断更新
第一人称角色/武器/附件每帧重建最高优先级
  • 性能约束:每帧最多构建 16 个 BLAS,可以是静态和/或蒙皮的混合

HW 光线追踪与光照的统一着色器架构

追踪与光照合一

  • 设计决策:不为每种材质使用单独的光照着色器
  • 光照计算在 追踪命中后立即在同一个着色器中完成(Trace & Lighting in the same shader)
  • 这避免了多次着色器切换带来的性能损耗

统一光照参数(Unified Lighting Parameters)

  • 创建了一套 统一的光照参数按顶点存储(Per Vertex)
  • 每种材质需要一个 专用着色器,将其材质特定的光照属性 映射到统一光照参数
  • 关键优化:材质的着色器变体解析在 BLAS 创建阶段 完成(一次性)
    • 将着色器变体的性能开销从 每帧光照计算 转移到 一次性的 BLAS 创建过程

光线追踪管线中的光照计算差异

传统光栅化 vs 光线追踪

方面传统光栅化管线光线追踪管线
插值方式光栅器自动提供 插值后的值 给像素着色器插值在 光照着色器中手动完成
操作对象像素(Pixels)图元(Primitives)
数据访问通过顶点/索引缓冲区通过 DXR 图元数据(DXR Primitive Data)

图元数据准备(Primitive Data)

  • 在 BLAS 创建之前,先对 顶点缓冲区(Vertex Buffer)索引缓冲区(Index Buffer) 进行 展开(Unwrap)
  • 展开结果
    • 图元顶点数据在 VRAM 中 连续排列
    • 虽然产生数据冗余(Duplication),但保证了 更好的缓存一致性(Coherency),有利于插值操作
    • 不再需要索引缓冲区 用于光照计算,消除了一次额外的间接寻址(Indirection)
  • 顶点数据经过 紧凑打包(Packed) 以最小化 VRAM 占用

图元池(Primitive Pool)

内存布局

  • 图元缓冲区(Primitive Buffer):存储游戏中 所有 BLAS 的图元信息
    • 固定大小:192 MB
  • 图元偏移缓冲区(Primitive Offset Buffer)
    • DXR API 提供 PrimitiveIndex 来标识 BLAS 实例中的图元
    • 偏移缓冲区将 PrimitiveIndex 映射到图元缓冲区中的正确数据位置

每个 BLAS 的几何体数量

  • 每个 BLAS 固定 最多 32 个几何体(Geometries)
  • 基于游戏实际数据验证,这个上限工作良好

性能优势

  • 整体结构只需要 一次间接寻址(通过图元偏移缓冲区)
  • 相比其他实现方案,减少了多层间接寻址的开销

图元光照计算(Lighting the Primitive)

插值流程

  1. 使用光线命中点的 重心坐标(Barycentric Coordinates) 对图元顶点进行 插值
  2. 根据插值结果计算 统一光照参数 的插值值

当前支持的光照因素

光照来源说明
太阳光(Sun)主方向光
全局光照(Global Illumination)间接照明
环境贴图(Environment Maps)远景反射与环境光
阴影(Shadows)遮蔽计算

未来工作

  • 动态光源(Dynamic Lights) 的支持是潜在的未来改进方向,当前版本尚未包含

硬件光线追踪图元光照、粒子反射与最终整合


HW 光线追踪重心坐标与图元光照

重心坐标插值(Barycentric Coordinates)

  • 硬件光线追踪命中图元后,返回的是 重心坐标(Barycentric Coordinates)
  • 结合之前提到的 统一光照参数(Unified Lighting Parameters),利用重心坐标在图元上进行 光照数据插值
  • 这与传统光栅化中硬件自动插值不同——在光线追踪管线中,插值需要 手动在着色器中完成

图元光照的特点与局限

  • 不进行纹理采样:反射中的光照仅基于图元级别的插值数据
  • 几何细节少的区域 会显得比较 平淡(Plain),缺乏纹理细节
  • 几何细节丰富的物体 则能充分发挥这种实现的 优势,呈现出良好的反射质量
  • 这是一种 性能与质量的权衡:放弃纹理采样换取更高的追踪性能

粒子反射系统(Particles in Far Cry 6)

粒子在 Far Cry 6 中的重要性

  • Far Cry 系列以 爆炸动态系统性火焰 著称
  • Far Cry 6 新增了 毒气(Poison) 系统,其标志性的 红色 在反射中同样显著
  • 粒子反射 对 Far Cry 6 的 视觉质量至关重要

核心难点:粒子是 2D 广告牌(Billboard)

  • 粒子本质上是 面向相机的 2D 平面(Billboard)
  • 它们的朝向是 面向摄像机 而非面向 反射光线
  • 这导致反射光线经常 无法与粒子平面相交

粒子硬件光线追踪流程

  1. 复用前一次追踪的光线长度

    • 利用之前 SSLR 或 HW RT 已确定的 光线长度(Ray Length) 来裁剪不可见的粒子
    • 例如被遮挡物挡住的粒子可以被直接跳过
  2. 每个粒子一个 BLAS 实例

    • 沿着光线 收集所有相交的粒子,以便正确进行 混合(Blending)
  3. 性能提示:使用 独立的 TLAS 来存放粒子加速结构

    • 分离粒子 TLAS 在 遍历加速结构追踪光线时能获得性能提升

缺失精灵问题(The Case of the Missing Sprites)

  • 问题根源:粒子精灵(Sprite)面向 摄像机 而非反射光线方向

    • 反射光线可能完全 错过 粒子广告牌
  • 解决方案——额外四边形(Extra Quads)

    • 为每个粒子在其 局部坐标系的三个平面 上添加额外的四边形
    • 这样即使原始广告牌未被命中,反射光线也能与某个额外四边形 相交
  • 性能优化——按视空间轴方向遮罩四边形(Mask Quads by View Space Axis)

    • 对所有四边形进行 排序,基于 视空间轴方向 在 BVH 树中进行 遮罩
    • 只保留与当前视角相关的四边形,忽略不必要的平面(例如忽略蓝色和红色四边形,仅保留绿色)
    • 避免为所有额外四边形都做交叉检测带来的 性能浪费
  • 进一步优化:粒子反射使用 简化的光照模型

SSLR 粒子 vs HW RT 粒子对比

方面SSLR 粒子反射HW RT 粒子反射
质量受限于屏幕空间信息显著质量提升
覆盖范围仅屏幕内可见粒子包含 屏幕外粒子
适用场景毒云、爆炸的基础反射毒云、爆炸的 高质量反射

最终整合通道(Integration Pass)

邻域加权整合

  • 收集 邻居像素 的光线命中结果,按 BRDF 系数 进行加权
  • 每帧处理来自 9 个邻居 的光线数据
  • 中心像素始终包含在内,并给予更高权重——因为它是 完美镜面反射 所必需的

跨帧分摊(Split Across Two Frames)

  • 出于 性能考虑,整合操作被 分摊到两帧 完成
  • 每帧有特定的 邻域数据采集模式(Pattern)

时间累积(Temporal Accumulation)

  • 整合之后执行 时间累积
  • 累积后,总共利用了来自 17 个邻居 的数据
  • 通过时间上的信息累积,在极低的每像素光线预算下获得 高质量的反射结果

反射运动数据(Reflection Motion)

为什么需要反射运动数据

  • 时间累积(Temporal Accumulation) 依赖于 反射运动数据 来正确关联前后帧的反射信息
  • 大部分反射运动通过 重投影(Reprojection) 计算

重投影流程

  1. 反射点重投影到世界空间

    • 将反射结果通过 表面平面(Surface Plane) 反向投影,得到 世界空间位置
  2. 前一帧屏幕空间重建

    • 使用 前一帧的摄像机参数,将世界空间位置重建回 屏幕空间
  3. 运动向量计算

    • 当前帧与前一帧在屏幕空间中的两个点之间的差异即为 反射运动向量(Reflection Motion Vector)
    • 用于指导时间累积中的 历史数据查找与对齐

反射运动向量、时间累积与光线追踪性能优化


反射运动向量(Reflection Motion Vectors)

基本原理

  • 通过 取差值 来确定 反射运动向量(Reflection Motion Vector)
  • 反射运动向量用于后续的 时间累积(Temporal Accumulation),实现跨帧信息复用

特殊情况:随摄像机移动的物体

  • 对于 与摄像机一起移动的物体(如 第一人称玩家角色玩家驾驶的载具),重投影(Reprojection)会 完全失效
  • 解决方案:对这些物体使用 GBuffer 运动向量(GBuffer Motion) 代替反射运动向量

时间累积(Temporal Accumulation)

与传统 TAA 的关键区别

  • 传统 TAA:基于 运动向量长度 来决定是否拒绝历史样本
  • 反射时间累积不基于运动向量长度拒绝历史
    • 忽略大幅度的反射运动向量会导致粗糙表面出现 镜面爆炸(Specular Explosions)——即高光异常膨胀的视觉伪影

历史验证策略

  • 使用 运动导数(Motion Derivative) 来验证反射历史的有效性
  • 丢弃固有不连贯的反射运动(Discard Inherently Incoherent Reflection Motion)
  • 通过运动的变化率而非绝对值来判断历史数据是否可信

无 HW RT 时的视觉问题

问题描述
Peter Pan 问题卡车底部 异常明亮,看起来像在 漂浮——经典的接触阴影/反射缺失问题
建筑物部分消失建筑在反射中 部分缺失,视觉上非常违和
信息缺失至少 两个额外的反射细节来源 丢失
  • 通过叠加遮罩可以清楚看到:HW RT 正好填补了这些 SSLR 无法覆盖的区域

混合光线追踪反射最终效果(Hybrid Ray Traced Reflections)

  • 屏幕空间反射(SSLR) + 硬件加速光线追踪(HW RT) + 环境贴图 合成为 混合结果
  • 最终画面呈现出 高质量且性能友好 的反射效果

性能优化概述(Performance Optimization)

测试环境

  • GPU:Radeon RX 6800 XT
  • CPU:Ryzen 3970X Threadripper
  • 合作团队:AMD GPU DevTech(Zhuo 和 Ihor)+ CPU DevTech(John Hartwig)

优化重点领域

  1. 着色器表管理(Shader Table Management)
  2. 命中着色器设计(Hit Shader Design)
  3. BVH 构建(BVH Building)

Radeon GPU Profiler(RGP)概述

RGP 界面组成

窗口功能颜色编码
波前占用率窗口(Wavefront Occupancy)显示 GPU 在任意时刻被工作填充的程度黄色 = Compute Shader,绿色 = Vertex Shader,蓝色 = Pixel Shader
高频计数器(High Frequency Counters)显示 GPU 各级缓存的 命中率(Cache Hit Rates)
事件时间线(Event Timeline)显示 Draw、Dispatch、Barrier 等事件
事件详情窗口(Event Details)显示执行时间、最大理论占用率(Max Theoretical Occupancy) 和实际生成的波前总数

Far Cry 6 在 RGP 中的关键区域

  • BVH 构建(左侧,红色波形)
  • DXR 光线追踪(右侧,红色波形)

光线追踪中的三种发散问题(Divergence)

核心认知:光线追踪是 延迟敏感(Latency-Heavy) 的操作,极易受发散影响

光线发散(Ray Divergence)

  • 定义:同一个 Wave 中的不同光线需要 不同数量的遍历步数 才能找到交点
  • 产生原因
    • 不同光线命中 场景中不同的物体
    • 某些光线命中了 BLAS 中 几何细节更复杂的区域,需要更多遍历
  • 执行模型
    • 每个线程(Thread)对应一条光线
    • 由于 Wave 是 SIMD 单元,程序执行必须 同步
    • 即使某些线程已找到交点,仍必须 等待最慢的线程完成
    • 示例:如果三条光线分别需要 1、2、4 次遍历,整个 Wave 必须等待 4 次遍历完成 才能继续处理交点
  • 本质:Wave 的执行效率受限于 最坏情况线程(Worst-Case Thread)

着色器表发散(Shader Table Divergence)

  • 定义:同一个 Wave 中不同光线需要执行 不同的着色器表条目(Hit Shader / Miss Shader)
  • 产生原因:不同材质使用 不同的命中着色器(Hit Shader)
  • 执行模型——串行分支
    • 类似于一系列 if-else 分支 的串行执行:
      1. "如果线程命中绿色材质 → 执行绿色 Hit Shader"
      2. "如果线程命中青色材质 → 执行青色 Hit Shader"
      3. 依此类推...
    • 必须逐一遍历 Wave 中 所有不同的 Hit Shader
    • 这是一个 高度串行的操作,可能变得非常慢
  • 理想情况:如果 Wave 中所有线程使用 同一个 Hit Shader,则所有线程 同时执行,然后立即继续后续工作

三种发散的影响总结

发散类型核心问题性能影响
光线发散同一 Wave 内遍历次数不均快线程 空等 慢线程
着色器表发散同一 Wave 内命中不同材质着色器Hit Shader 串行执行
资源发散(Resource Divergence)(后续内容,待补充)

核心优化目标:尽量让同一 Wave 内的线程执行 相同路径、相同着色器、相同遍历深度,减少 SIMD 利用率浪费。

资源发散、着色器表优化与 Far Cry 6 命中着色器设计


资源发散(Resource Divergence)

定义与成因

  • 资源发散 发生在着色器发出资源读取指令时,同一波前(Wave)中不同线程试图访问不同的描述符(Descriptor)
  • 硬件层面:缓冲区读取或纹理采样使用 SGPR(标量通用寄存器) 来确定描述符
    • 这意味着硬件 假设整个波前中所读取的资源是统一的(Uniform)

非统一访问的执行过程

  • 当不同线程需要从 纹理描述符数组(Texture Descriptor Array) 中读取不同纹理时:
    • 在 HLSL 中通过 NonUniformResourceIndex 标记
    • 硬件被迫 循环遍历波前中所有唯一资源,逐个发出读取:
      1. 先为所有需要 纹理 A 的线程发出读取
      2. 再为所有需要 纹理 B 的线程发出读取
      3. 依次处理 纹理 D 及其他纹理……
    • 这是一个 串行化过程(Serial),性能极差

理想情况

  • 如果所有线程 只读取同一个纹理,则硬件只需发出 一次读取,随后立即继续执行后续指令
  • 核心优化思路:尽量保证波前内所有线程访问 相同的资源描述符

光线追踪命中执行流程(Ray Tracing Hit Execution)

命中着色器执行伪代码

if (ray intersects) {
    hitTableIndex = calculateHitTableIndex(); // 不一定在线程组内统一
    switch (hitTableIndex) {
        case shaderTableEntry0: ...
        case shaderTableEntry1: ...
        // ...
    }
}

理想的汇编行为

  • 着色器表条目的函数调用应被 内联(Inlined)
  • 如果不同条目之间存在 公共代码路径,编译器应将它们 合并优化

着色器表过大的问题

  • 当着色器表条目 过多 时,编译器 无法全部内联
  • 此时编译器会在 GPU 上 模拟函数调用约定(Function Calling Convention)
    • 通过 LDS(Local Data Share) 进行函数参数的加载和存储
    • 产生大量 额外指令额外 LDS 流量
    • 这些开销 并非在执行 HLSL 中的实际逻辑,纯粹是管线衔接的额外工作

如何检测着色器表是否被内联

  • 使用 Radeon GPU Profiler(RGP) 抓取帧
  • 选中光线追踪事件,如果出现 "Shader Table" 标签页,则说明着色器表 未被内联
  • 在该标签页中可以看到:
    • 所有着色器表条目的名称
    • 不同光线追踪着色器的 实际执行流程图
    • 点击 ISA 可查看各条目的汇编代码,会发现大量 冗余指令

性能影响

Radeon RX 6800 XT 上以 4K 分辨率 运行 Far Cry 6 时,强制着色器表不内联 会使光线追踪操作 慢 2~3 倍


Far Cry 6 的着色器表设计

极简着色器表结构

条目类型数量说明
命中着色器(Hit Shader)1 个插值顶点属性,存储所有材质属性(统一光照参数方案)
未命中着色器(Miss Shader)1 个完全为空,无任何代码
  • 因为着色器表极小,所有条目都被 成功内联
  • 空的 Miss Shader 被编译器 完全优化掉,不产生任何汇编指令

三种发散问题的应对

发散类型Far Cry 6 的应对策略
着色器表发散(Shader Table Divergence)只有 一个命中着色器,波前中所有命中线程执行 完全相同的着色器代码;Miss Shader 为空,命中/未命中混合时也 无额外延迟
资源发散(Resource Divergence)所有 DXR 图元存储在 全局缓冲区(Global Buffers) 中;不同光线命中不同几何体时,只是计算 不同的偏移量(Offset),但始终对 相同的资源 发出缓冲区读取
光线发散(Ray Divergence)BVH 构建阶段 部分缓解(详见后续 BVH 优化章节)

命中着色器核心流程

// 伪代码概述
offset = calculateOffset(hitGeometry);   // 根据命中几何体计算偏移
primitive = loadDXRPrimitive(globalBuffer, offset);  // 从全局缓冲区读取图元
materialProps = barycentricInterpolate(primitive);    // 重心坐标插值材质属性
lighting = calculateLighting(materialProps);          // 计算光照
payload.result = lighting;                            // 写入光线负载
  • 关键设计:所有线程读取的是 同一个全局缓冲区,仅偏移不同,从而避免资源发散
  • 材质差异通过 顶点属性中存储的统一光照参数 来表达,而非通过不同的着色器或不同的资源绑定

性能建议参考

  • AMD 在 GPUOpen 上发布了完整的 光线追踪性能建议列表
  • 涵盖内容包括:
    • 着色器表内联策略
    • 资源访问模式优化
    • 波前占用率最大化
    • 其他未在演讲中详述的优化技巧

BLAS 顶点生成与 BVH 异步构建优化


BLAS 顶点生成策略(BLAS Vertex Generation)

核心决策:使用 Compute Shader 而非几何着色器

Far Cry 6 选择使用 Compute Shader 来生成 BLAS 所需的顶点数据,而非传统的 VS/GS/HS/DS 管线。

为什么不使用几何着色器管线?

如果在现有几何 Pass 中使用几何相关着色器(如 Vertex Shader 末尾添加 UAV 写入):

问题说明
必须禁用顶点/物体剔除(Culling)BVH 需要 世界空间信息,不能局限于屏幕空间;但禁用剔除会导致 几何 Pass 大幅变慢
顶点缓存未命中导致冗余写入不同波前可能因 顶点缓存未命中(Vertex Cache Miss) 而对 同一顶点重复写入 UAV

Compute Shader 的优势

优势说明
不影响现有几何 Pass几何管线保持原样,无任何性能损失
消除冗余写入每个线程与每个 DXR 图元保持 一对一关系(One-to-One),保证每个图元只写入一次
潜在的数据复用流出的顶点位置数据可以 复用于阴影 Pass,避免重复执行动画计算

注意:阴影 Pass 的数据复用在 Far Cry 6 中 未来得及实验,属于未来优化方向。

动画顶点流出

  • 由于存在 蒙皮动画物体(Animated/Skinned Objects),每帧都需要将 应用动画后的顶点 流出供 BLAS 构建使用
  • Compute Shader 独立执行此任务,不干扰主渲染管线

BVH 异步构建(BVH Building on Async Compute)

异步计算队列的优势

在 RGP 追踪中可以观察到,BVH 构建被放置在 异步计算队列(Async Compute Queue) 上执行:

优势说明
与图形管线并行BVH 构建与主图形管线中的其他工作 同时执行,隐藏延迟
适合 GPU 瓶颈场景前面提到在 2K 分辨率及以上 GPU 瓶颈时启用异步构建
填充 GPU 空闲气泡利用图形管线中的 空闲周期 来完成 BVH 构建

同步开销的权衡

  • 异步计算会引入 同步屏障(Synchronization Barriers) 的额外开销
  • 只有在 GPU 受限 的情况下,异步执行带来的并行收益才能 超过同步开销
  • 非 GPU 瓶颈时,BVH 构建保持在 主 GPU 管线 上执行

粒子加速结构回顾

  • 粒子拥有 独立的 TLAS 和 BLAS
  • 粒子 BVH 构建时间 < 0.1 ms,开销极小

整体管线时序概览

主图形管线:   [几何Pass] [光照Pass] [后处理...]
                  ↕ 不受影响
异步计算队列: [CS顶点生成] → [BVH构建(Build AS)] → [同步] → [DXR光线追踪]
  • CS 顶点生成BVH 构建 在异步队列上与主管线 并行执行
  • DXR 光线追踪需要等待 BVH 构建完成后才能开始
  • 整体设计目标:最大化 GPU 利用率,最小化管线气泡

小结

Far Cry 6 在 BLAS 顶点生成上的 Compute Shader 方案体现了 "不干扰现有管线" 的设计哲学:

  1. 解耦:顶点生成与几何渲染完全分离
  2. 确定性:一对一的线程-图元映射消除冗余
  3. 可扩展:为未来的阴影 Pass 复用留下空间
  4. 并行化:配合异步计算队列实现与主管线的重叠执行

异步 BVH 构建、TLAS 遍历优化与 LOD 策略


异步 BVH 构建的深入分析

为什么 BVH 构建适合异步计算?

特性说明对异步重叠的影响
极低的波前占用率(Low Occupancy)尤其是 TLAS 构建阶段,生成的波前数量很少留出大量 ALU 硬件空闲,可被主管线利用
高内存流量(High Memory Traffic)BVH 构建是 内存密集型 操作ALU 与内存带宽的使用 互补,适合重叠
高延迟(High Latency)构建过程涉及大量内存访问等待不希望阻塞主管线上的其他工作
无依赖性帧内除了光线追踪外,没有其他工作需要 BVH 数据可以与 GBuffer、阴影等 早期帧工作 广泛重叠

实测性能收益

在 Far Cry 6 中,将 BVH 构建放到异步计算队列相比放在图形队列,平均带来 0.4 ~ 0.8 毫秒 的加速。

未完成的优化:顶点生成异步化

  • 未能实现:将 BLAS 顶点生成(Compute Shader)也移到异步计算队列
  • 原因:需要 跨队列屏障(Cross-Queue Barriers) 来同步顶点/索引缓冲区,复杂度过高
  • 展望:作为未来项目的实验方向

命令列表与同步设计

极简同步架构

异步计算队列:  [Build BVH 命令列表 (所有 BVH 构建调用)]
                                                    ↓ 单次同步
图形队列:      [GBuffer] [Shadows] [...]  ←——等待——→ [DXR 光线追踪]
  • 一个 Compute 命令列表包含所有 BVH 构建调用
  • 一个 Compute 队列提交
  • 一次 与图形队列的同步

最小化同步的重要性

  • PC 平台上跨队列同步的开销 高于主机平台
  • 因此需要尽量 减少同步点数量
  • Far Cry 6 的方案是几乎 最简形式的同步,易于集成

BVH 构建时间的帧间方差

问题描述

  • 不同帧之间,BVH 构建时间可能有 超过 1 毫秒的差异
  • 取决于每帧需要构建的 资产类型和数量

应对策略

策略说明
提前提交异步工作在帧的 足够早期 提交 Compute 队列,预留充足的重叠时间窗口
避免同步等待空转防止图形队列全部完成后异步队列仍在构建,导致实质上变为同步执行

异步的额外好处:隐藏方差

  • 如果 BVH 构建在 图形队列 上,帧间方差会 直接体现在帧时间
  • 放在 异步计算队列 上,构建时间的波动被 重叠隐藏
  • 结果:帧率更加稳定,这是异步方案的一个非常重要的附加属性

TLAS 遍历优化

未命中光线的遍历代价

  • 即使光线 不与任何几何体相交,仍然需要 遍历 BVH 树 来确认"未命中"
  • 如果 TLAS 非常密集(Dense),未命中光线的遍历代价 显著增加

摄像机位置的影响

  • 摄像机通常处于 TLAS 树的深处——靠近世界中的大量几何体
  • 这意味着在 常见情况下,大多数光线都需要进行 深层遍历
  • 不是鸟瞰视角,而是 沉浸式第一人称/第三人称 视角

光线发散的实际表现

  • 在遍历计数器可视化中可以直接观察到:
    • 红色光线(高遍历次数)紧邻 亮色光线(低遍历次数)
    • 如果这些光线被分组到 同一个波前,就会产生前面提到的 光线发散问题
    • 快速光线被迫等待慢速光线完成

Far Cry 6 的 TLAS/BLAS 优化方案

TLAS 半径限制

参数
TLAS 包含范围摄像机周围 100 单位半径 内的物体
世界的大部分不在 TLAS 中
地形特殊处理延伸更远,但使用 Clip Map 逐步降低分辨率

效果

  • 大幅减少 TLAS 中的节点数量
  • 未命中光线的遍历代价显著降低
  • 与粒子使用独立 TLAS 的思路一致——精简遍历结构

BLAS 低 LOD 策略

策略说明
使用截断的低 LOD所有 BLAS 物体使用 低于最高细节等级的 LOD
从不使用最高 LOD即使近距离物体也使用 受限的 LOD

带来的好处

好处机制
缓解光线发散更浅的 BVH 树 → 最坏情况下的遍历深度降低 → 最差光线与最快光线的差距缩小
降低遍历代价几何体三角形更少 → 每次交叉测试更快
适合反射场景反射中的物体不需要最高细节,低 LOD 不会产生明显视觉伪影
几何过滤效果远距离物体使用低 LOD 相当于 对几何体进行过滤,减少走样

地形 Clip Map

摄像机
  ↓
[高精度地形] → [中精度] → [低精度] → [极低精度]
←— 近距离 ——→←————— 远距离 ————→
  • 地形虽然延伸超出 100 单位半径,但通过 Clip Map 技术逐步降低分辨率
  • 确保远距离地形在 TLAS 中的开销极小

关键经验总结

优化方向具体措施收益
异步 BVH 构建一个命令列表、一次同步、尽早提交0.4~0.8 ms 加速 + 帧率稳定性
TLAS 精简100 单位半径限制 + 粒子独立 TLAS减少遍历代价,尤其是未命中光线
BLAS 低 LOD截断最高 LOD,统一使用低细节缓解光线发散,降低最坏情况代价
地形 Clip Map远距离逐步降分辨率在扩展范围内控制遍历成本
未来方向顶点生成异步化 + 阴影 Pass 数据复用潜在的进一步性能提升

混合反射 BLAS/TLAS 最佳实践、GPUOpen 示例与总结


BLAS/TLAS 优化总结(Tips Summary)

最佳实践清单

优化建议说明
使用 Compute Shader 生成顶点Far Cry 6 在 单次 CS Dispatch 中同时导出 BLAS 位置和 DXR 图元属性
尝试异步计算队列构建 BVHBLAS 占用率较高,但 TLAS 占用率通常很低,异步重叠收益显著
限制 TLAS 中的几何体数量减少遍历开销(如 100 单位半径限制)
反射使用更低 LOD 的 BLAS更快的追踪速度 + 更低的 VRAM 占用,视觉质量仍然良好

GPUOpen 混合反射示例(Hybrid Reflections Sample)

与 Far Cry 6 的对比

方面Far Cry 6GPUOpen 示例
基本流程SSLR → 低置信度时射 DXR 光线相同:SSLR → 低置信度时射 DXR 光线
近摄像机优化近摄像机反射像素 始终射 DXR 光线,跳过 SSLR
反馈机制引入 反馈计数器(Feedback Counters),帧间重投影
适用范围近摄像机像素的 SSLR 跳过优化任何满足启发式条件的表面 都可以跳过 SSLR

GPUOpen 示例可以看作是 Far Cry 6 混合反射方案的 迭代进化版本

核心创新:反馈计数器

反馈计数器尝试 记住 每个像素区域在先前帧中射出了哪种类型的光线(SSLR、DXR 或混合),从而在后续帧中做出更智能的决策。


反馈计数器详解(Feedback Counters)

数据结构

属性
粒度8×8 像素 Tile 一个计数器
格式单个 u32(32 位无符号整数)
内容包含 两帧 的统计数据,每帧有 命中计数器(Hit)未命中计数器(Miss)

Tile 重投影问题与解决

  • 问题:一个 8×8 Tile 内有 多个不同的运动向量,无法确定统一的重投影方向
  • 解决方案随机选取 Tile 内一个像素的运动向量,作为整个 Tile 的运动向量

双向切换机制

仅有 SSLR → DXR 的切换路径是不够的,否则计数器会随时间 收敛为全部 DXR,失去 SSLR 的性能优势。

切换方向触发机制
SSLR → DXRSSLR 低置信度时自然切换
DXR → SSLR(回退)随机 将部分 DXR-only 像素变为 Hybrid,验证 SSLR 是否能独立完成交叉检测

完整工作流

┌─────────────────────────────────────────────────────────────┐
│  1. 重投影(Reproject)上一帧的反馈计数器(使用运动向量)      │
│                          ↓                                   │
│  2. 随机掷骰:将部分 DXR-only 像素 → Hybrid                  │
│                          ↓                                   │
│  3. Hybrid 像素执行 SSLR:                                   │
│     ├─ 命中 → 递增 Hit 计数器 → 使用 SSLR 结果               │
│     └─ 未命中 → 递增 Miss 计数器 → 进入 DXR 管线            │
│                          ↓                                   │
│  4. 合并 SSLR 结果 + DXR 结果                                │
│                          ↓                                   │
│  5. 合并 Hit/Miss 计数器 → 重投影到下一帧 → 重复             │
└─────────────────────────────────────────────────────────────┘

这是一个 自适应的闭环系统

  • 频繁需要 DXR 的区域会被自动标记,直接跳过 SSLR,节省时间
  • 不再需要 DXR 的区域会通过随机探测 逐步回退到 SSLR,恢复性能

演讲总结(Conclusion)

混合光线追踪反射的核心价值

要点说明
显著的质量提升超越屏幕空间信息的限制,反射中包含 屏幕外内容
保持混合方案SSLR + DXR 混合使用让实时性能成为可能,SSLR 覆盖能省则省
提前规划资产系统材质处理和资产系统集成需要 预先规划,以适配光线追踪着色器
理解硬件特性着色器表大小、三种发散类型等 直接影响性能,需在管线设计阶段就考虑

GPUOpen 资源

属性详情
APIDirectX 12
文档包含完整文档
源码公开可用
许可证MIT License
用途可立即下载实验

整个演讲的完整技术栈回顾

Far Cry 6 混合光线追踪反射 - 完整管线

[分类阶段]
  反射像素分类 → SSLR / HW RT / 粒子 RT / 环境贴图

[光线追踪阶段]
  ├─ SSLR(屏幕空间光线步进)
  ├─ HW RT(硬件加速)
  │   ├─ 统一光照参数(无纹理采样)
  │   ├─ 最小着色器表(1 Hit + 1 Miss)
  │   ├─ 重心坐标插值光照
  │   └─ 100 单位半径 TLAS + 低 LOD BLAS
  └─ 粒子 RT
      ├─ 独立 TLAS
      ├─ 额外四边形解决 Billboard 朝向问题
      └─ 复用光线长度裁剪不可见粒子

[反射运动向量]
  屏幕空间深度重建 → 逆投影 → 世界空间反射 → 重投影 → 求差

[时间累积]
  运动导数验证 → 丢弃不连贯历史 → 跨帧积累

[整合阶段]
  邻域加权(BRDF) → 分两帧处理 → 最终合成

[BVH 构建]
  CS 顶点生成 → 异步计算队列 BVH 构建 → 单次跨队列同步

[GPUOpen 进化版]
  反馈计数器 → 自适应 SSLR/DXR 切换 → 闭环优化

这整个演讲系统地展示了一个 AAA 级游戏引擎 如何在保持 实时帧率 的同时,通过多层混合策略实现 高质量光线追踪反射。核心哲学始终是:在对的地方用对的技术——SSLR 能解决的不用 DXR,DXR 解决的不需要全路径追踪,每一步都在质量与性能之间寻找最优平衡点。