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),整体管线分为以下几个阶段:
管线流程
- 光线生成(Ray Generation):生成用于所有不同类型反射追踪的光线
- SSLR 通道(Screen Space Local Reflections):执行屏幕空间反射,并 判定哪些像素需要硬件加速光线追踪(即 SSLR 失败/无法覆盖的区域)
- 硬件加速光线追踪与光照通道(不含粒子):对 SSLR 无法处理的区域进行 HW RT
- 粒子专用 HW RT 通道:粒子是特殊情况,最好在 单独的第二个 HW RT pass 中处理
- 最终整合通道(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)
核心算法
- 线性追踪阶段:在深度缓冲区层级的 Mip 0 上执行线性追踪
- 首先取 64 个均匀分布的步进点,粗略检查是否存在交点
- 每个步进点检查是否落在该像素存储的 最小深度(Min Depth) 之后(即几何体背面)
- 细化阶段(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) 增大而增大,提供 动态性能调节 能力
- 具体实现:
- 第一个圆的面积约为 一个像素大小,对应 Mip 0
- 每一步迭代中,将屏幕空间中圆的面积 映射到最接近的 2 的幂次
- 然后采样对应 Mip 级别的深度图(Depth Map)
- 精度虽低,但实验表明粗糙表面通过添加这些反射仍获得了 明显的质量提升
硬件加速光线追踪(HW Ray Trace)加速结构
加速结构基础
- 即使有最新硬件,在世界空间中追踪光线的 成本依然极高
- 业界标准方案:使用 包围体层级(Bounding Volume Hierarchy,BVH) 来组织场景
BVH 的两层结构
| 层级 | 名称 | 说明 |
|---|---|---|
| 顶层加速结构 | TLAS(Top-Level Acceleration Structure) | 组织场景中各个物体实例 |
| 底层加速结构 | BLAS(Bottom-Level Acceleration Structure) | 描述单个物体/网格的几何细节 |
性能考量:选择性使用 HW RT
- BVH 覆盖距离有限——这是 性能优化的关键
- 不同距离采用不同反射策略:
| 距离范围 | 反射方案 | 原因 |
|---|---|---|
| 远距离 | 仅 SSLR | BVH 不覆盖远处,且远处 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)
四级置信度分层
系统根据 SSLR 和 HW 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)
插值流程
- 使用光线命中点的 重心坐标(Barycentric Coordinates) 对图元顶点进行 插值
- 根据插值结果计算 统一光照参数 的插值值
当前支持的光照因素
| 光照来源 | 说明 |
|---|---|
| 太阳光(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)
- 它们的朝向是 面向摄像机 而非面向 反射光线
- 这导致反射光线经常 无法与粒子平面相交
粒子硬件光线追踪流程
-
复用前一次追踪的光线长度:
- 利用之前 SSLR 或 HW RT 已确定的 光线长度(Ray Length) 来裁剪不可见的粒子
- 例如被遮挡物挡住的粒子可以被直接跳过
-
每个粒子一个 BLAS 实例:
- 沿着光线 收集所有相交的粒子,以便正确进行 混合(Blending)
-
性能提示:使用 独立的 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) 计算
重投影流程
-
反射点重投影到世界空间:
- 将反射结果通过 表面平面(Surface Plane) 反向投影,得到 世界空间位置
-
前一帧屏幕空间重建:
- 使用 前一帧的摄像机参数,将世界空间位置重建回 屏幕空间
-
运动向量计算:
- 当前帧与前一帧在屏幕空间中的两个点之间的差异即为 反射运动向量(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)
优化重点领域
- 着色器表管理(Shader Table Management)
- 命中着色器设计(Hit Shader Design)
- 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 分支 的串行执行:
- "如果线程命中绿色材质 → 执行绿色 Hit Shader"
- "如果线程命中青色材质 → 执行青色 Hit Shader"
- 依此类推...
- 必须逐一遍历 Wave 中 所有不同的 Hit Shader
- 这是一个 高度串行的操作,可能变得非常慢
- 类似于一系列 if-else 分支 的串行执行:
- 理想情况:如果 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标记 - 硬件被迫 循环遍历波前中所有唯一资源,逐个发出读取:
- 先为所有需要 纹理 A 的线程发出读取
- 再为所有需要 纹理 B 的线程发出读取
- 依次处理 纹理 D 及其他纹理……
- 这是一个 串行化过程(Serial),性能极差
- 在 HLSL 中通过
理想情况
- 如果所有线程 只读取同一个纹理,则硬件只需发出 一次读取,随后立即继续执行后续指令
- 核心优化思路:尽量保证波前内所有线程访问 相同的资源描述符
光线追踪命中执行流程(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 方案体现了 "不干扰现有管线" 的设计哲学:
- 解耦:顶点生成与几何渲染完全分离
- 确定性:一对一的线程-图元映射消除冗余
- 可扩展:为未来的阴影 Pass 复用留下空间
- 并行化:配合异步计算队列实现与主管线的重叠执行
异步 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 图元属性 |
| 尝试异步计算队列构建 BVH | BLAS 占用率较高,但 TLAS 占用率通常很低,异步重叠收益显著 |
| 限制 TLAS 中的几何体数量 | 减少遍历开销(如 100 单位半径限制) |
| 反射使用更低 LOD 的 BLAS | 更快的追踪速度 + 更低的 VRAM 占用,视觉质量仍然良好 |
GPUOpen 混合反射示例(Hybrid Reflections Sample)
与 Far Cry 6 的对比
| 方面 | Far Cry 6 | GPUOpen 示例 |
|---|---|---|
| 基本流程 | 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 → DXR | SSLR 低置信度时自然切换 |
| 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 资源
| 属性 | 详情 |
|---|---|
| API | DirectX 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 解决的不需要全路径追踪,每一步都在质量与性能之间寻找最优平衡点。