《刺客信条:影》中的光线追踪全局光照系统
Ray tracing the world of Assassin's Creed Shadows (Advances in Real-Time Rendering SIGGRAPH 2025)
项目背景与挑战
游戏世界的动态性
- 《刺客信条:影》 是育碧迄今为止 最具动态性的大规模开放世界 游戏
- 动态要素极为丰富:
- 动态时间过渡(Time of Day transitions)
- 14 种独立天气状态(unique weather states)
- 4 个静态季节,会影响天气、材质和时间表现
- 这些动态因素意味着:传统的 均匀探针分布方案(Uniform Probe Distribution) 无法良好适配如此高动态性的场景,扩展性(Scalability)不足
技术演进历程
- 《刺客信条:起源》 时代:从均匀探针方案转向 稀疏探针方案(Sparse Probe Solution)
- 《刺客信条:影》:进一步发展出 光线追踪漫反射与镜面反射方案(Ray Traced Diffuse & Specular Solution)
平台扩展性需求
- 扩展性不仅面向动态性,还需要 平台扩展性(Platform Scalability)
- 同一平台上,玩家可能选择 画质模式 或 性能模式,对应两套不同的渲染方案
- 因此系统设计必须灵活适配多种硬件配置
全局光照算法概览
技术基础
本方案建立在以下已有技术之上:
- DDGI(Dynamic Diffuse Global Illumination):动态漫反射全局光照,使用了最新的 逐像素光线追踪 Pass(Per-Pixel Ray Tracing Pass) 版本
- Probe Volume 作为间接光照缓存:将探针体积用作 多次弹射间接光照计算(Multi-bounce RTGI Computation) 的缓存
- 类似系统已在多款游戏中应用:
- 《地铁:离去》(Metro Exodus)
- 《阿凡达》(Avatar)
- 《星球大战:亡命之徒》(Star Wars Outlaws)(均使用 Snowdrop 引擎)
本作的独特挑战
在上述技术基础上,需要解决:
- 大型动态开放世界 的 GI 质量与性能
- 场景中包含 大量植被 等复杂几何
- 需在 多平台 上运行
算法流程详解
第一步:光栅化与直接光照
- 从摄像机视角 光栅化场景,构建 GBuffer
- 计算 直接光照(Direct Lighting)
目标:为每个像素计算 间接漫反射(Indirect Diffuse) 和 间接镜面反射(Indirect Specular) 光照
第二步:逐像素方向采样
- 对每个像素,根据 BRDF 随机选择一个采样方向
- 分为 两个 Pass:
- 漫反射波瓣(Diffuse Lobe) Pass
- 镜面反射波瓣(Specular Lobe) Pass
- 然后沿选定方向 投射光线(Cast Ray)
第三步:屏幕空间光线步进(Screen Space Ray Marching)
在进行硬件光线追踪之前,首先执行屏幕空间光线步进,原因有三:
- 性能优势:在某些平台(如主机)上,光线步进比光线追踪 更快
- BVH 世界表示非常粗糙:为了速度,光线追踪的 加速结构(BVH) 并不包含场景中所有物体
- 避免自相交(Self-Intersection) 并 补回缺失物体的信息:屏幕空间步进能提供 BVH 中缺失的几何信息
第四步:光线追踪回退(Ray Tracing Fallback)
- 如果屏幕空间光线步进 未找到交点,则将光线投入 BVH 加速结构 中进行真正的光线追踪
- 关键术语:BVH(Bounding Volume Hierarchy,层次包围盒) —— 光线追踪中用于加速光线-场景求交的空间数据结构
第五步:命中点着色
在光线步进或光线追踪找到交点后:
- 计算该命中点的 直接光照,包括:
- 太阳光(Sun Light)
- 所有局部光源(Local Lights)
核心架构设计思路总结
| 设计要点 | 说明 |
|---|---|
| 混合追踪策略 | 屏幕空间步进优先 + BVH 光线追踪回退,兼顾性能与质量 |
| 逐像素双 Pass | 漫反射和镜面反射分开处理,各自独立采样方向 |
| 粗糙 BVH | 为性能考虑,BVH 不包含所有物体,靠屏幕空间补偿 |
| Probe Volume 缓存 | 用于多次弹射的间接光照,降低逐帧计算开销 |
| 高动态适配 | 算法需应对时间、天气、季节的持续变化,不能依赖静态烘焙 |
演讲结构预告
演讲分为两大部分:
- 算法与实现深度解析(由 Luke 主讲)—— 即上述内容的展开
- 《刺客信条:影》特有的挑战、问题来源与解决方案(由 Millie 主讲)
- 最后讨论 性能、算法局限性 以及 未来工作计划
间接光照与探针体积(Indirect Illumination & Probe Volume)
命中点的间接漫反射计算
- 在命中点处计算 间接漫反射光照(Indirect Diffuse) 时,依赖摄像机周围分布的 探针体积(Probe Volume)
- 具体做法:选取距离命中点 最近的 8 个探针(8 Nearest Probes),对它们进行 插值,得到该点的间接漫反射光照值
- 对所有像素执行上述流程后,会得到一个 带噪声的结果(Noisy Solution)
- 最后通过 降噪(Denoise) 处理,得到最终的光照结果
探针体积的更新流程
探针体积需要在逐像素光线追踪之前完成计算,流程如下:
- 对探针体积中的 每个探针,向覆盖其球面所有方向 发射子光线(Cast Sub-rays)
- 在每条光线的命中位置,计算来自 所有光源的直接光照
- 然后计算该命中位置的 间接光照,此时同样使用探针体积——但使用的是 上一帧的探针结果(Last Frame's Probe Result),因为当前帧的探针正在计算中
这种 时序递归(Temporal Recursion) 的做法,实现了探针体积内的 多次弹射全局光照(Multi-bounce Global Illumination)
探针数据的卷积处理
完成所有探针的光照计算后,需要进行两种卷积:
| 卷积类型 | 输出 | 用途 |
|---|---|---|
| 辐射度缓存卷积(Radiance Cache Convolution) | 滤波后的辐射度缓存(Filtered Radiance Cache) | 当光线 未命中任何几何体 时,作为回退值(Fallback) |
| 漫反射 BRDF 卷积(Diffuse BRDF Convolution) | 辐照度漫反射缓存(Irradiance Diffuse Cache) | 用于计算空间中 任意点的间接漫反射全局光照 |
- 辐射度缓存的卷积 牺牲了一定精确性,换取更低的噪声和更稳定的结果
光线追踪场景定义(Ray Tracing Scene Definition)
设计原则:粗糙但快速
- 光线追踪使用的场景描述是光栅化版本的 粗糙表示(Coarse Representation)
- 核心目标:因为需要大量投射光线,所以场景描述必须尽可能 加速求交过程
几何简化策略
| 策略 | 说明 |
|---|---|
| 使用低级别 LOD | 尽可能使用最低 LOD,在不产生 可见瑕疵(Visible Artifacts) 的前提下降低几何复杂度 |
| 不支持顶点动画 | 无 蒙皮(Skinning) 支持,即 无角色、无运动中的植被 |
| 剔除小型几何 | 尽量剔除 小型植被、小型杂物(Cluttering Objects) 等 |
三角形数据预烘焙(Baked Triangle Data)
- 为了加速命中结果的获取,对三角形数据进行 预烘焙
- 传统流程:先获取 顶点索引 → 再获取 顶点数据 → 插值
- 优化后:直接获取 预烘焙的三角形数据,插值 UV 即可得到所有所需信息
- 减少了间接内存访问(Indirect Memory Fetches),显著提升性能
材质系统(Material System)
统一材质描述(Unified Material Description)
- 光栅化版本中,每种材质可拥有 各自的 Shader
- 光线追踪版本中,采用 统一的材质描述(Unified Description)
- 好处:可以使用 内联光线追踪(Inline Ray Tracing),在多平台上通常 性能更优
PBR 常量烘焙
- 对每种材质,计算其 PBR 常量(PBR Constants)
- 同时支持 Albedo 纹理 和 Alpha 纹理 以提升质量
材质烘焙流程
烘焙流程如下:
- 遍历每个 PBR 参数的纹理,计算其 平均值(Average Value)
- 对于复杂材质(如 树木 Planner):
- 先将材质结果 烘焙到一张纹理图集(Texture Atlas) 中
- 再将该图集作为输入进行后续处理
- 该纹理也可直接用于 Albedo 和 Roughness
Alpha 材质处理
- 在计算均值时,需 考虑 Alpha 通道的权重
- 还需为每个使用 Alpha 的 Mesh 计算一个 不透明度值(Opacity Value)
- 计算方式:
- 将 Mesh 的每个三角形 重投影到 Alpha 纹理 上
- 对每个三角形内部的纹素取平均
- 得到的 Opacity 参数后续用于 Alpha Test 材质的光线追踪
植被材质特殊处理
- 支持 颜色查找表(Color LUT),根据 季节 定义不同颜色
- 支持 双通道 Alpha 纹理:
- 一个通道:有叶子(With Leaf)
- 另一个通道:无叶子(Without Leaf)
- 根据当前季节选择正确的通道
BVH 更新机制
- 每帧更新 BVH 加速结构
- 更新过程简洁明了:
- 根据 流式加载(Streaming) 状态和 摄像机位置,添加或移除实例(Add/Remove Instances)
- 确保 BVH 始终反映当前可见范围内的场景状态
BVH 场景动态更新(BVH Scene Update)
动态物体支持
- 对场景中移动物体的 变换矩阵(Transformation Matrix) 进行逐帧更新
- 支持 超级动态物体(Super Dynamic Objects),但 不支持顶点动画(Vertex Animation)(即无蒙皮角色、无植被摆动)
地形的特殊处理
- 地形(Terrain) 是一个特殊案例,采用 分块(Patch) 方式管理
- 根据摄像机位置,动态 添加/移除地形块
- 地形的材质处理也不同:为每种 PBR 参数 单独使用一张 纹理图集(Texture Atlas)
实例剔除与时间切片
- 根据摄像机视角进行 实例剔除(Instance Culling)
- 场景中实例数量可能超过 10 万个,无法单帧全部更新
- 因此采用 时间切片(Time Slicing) 策略:每帧只选择一个子集进行剔除更新
- 完整场景更新大约需要 ~1 秒 完成一个完整周期
探针体积的数据结构与管理(Probe Volume Data)
探针网格结构
- 使用标准的 多级联 3D 均匀网格(Cascaded 3D Uniform Grid),跟随摄像机移动
- 《刺客信条:影》中共有 略超过 10,000 个探针
探针数据组成
每个探针包含以下数据:
| 数据类型 | 说明 |
|---|---|
| 数据缓冲区(Data Buffer) | 存储光照相关信息 |
| 元数据(Metadata) | 包括世界偏移等辅助信息 |
| 世界偏移(World Offset) | 允许探针在其定义区域内移动,以找到 更优的渲染位置 |
探针数据存储格式
- 探针数据存储在 纹理图集(Texture Atlas) 中,使用 八面体映射编码(Octahedral Encoding)
- 存储流程:
- 保存压缩后的 GBuffer 信息
- 基于 GBuffer 计算 临时光照缓冲区(Temporary Lighting Buffer)
- 对光照缓冲区进行 卷积(Convolution),生成:
- 可见性探针(Visibility Probe):用于计算探针插值权重
- 滤波辐射度探针(Filtered Radiance Probe):滤波后的辐射度缓存
- 辐照度探针(Irradiance Probe):用于间接漫反射计算
探针更新优化策略(Probe Update Optimization)
核心问题
- 每帧更新所有 10,000+ 探针 成本过高
- 需要通过多种优化手段降低开销
三种更新模式
| 更新模式 | 内容 | 特点 |
|---|---|---|
| 完整更新 - 全分辨率(Full Update, Full Resolution) | 投射光线计算 GBuffer → 计算光照 → 卷积结果 → 尝试重定位探针 | 最高质量,开销最大 |
| 完整更新 - 四分之一分辨率(Full Update, Quarter Resolution) | 与上述相同,但 GBuffer 更新在 1/4 分辨率 下执行 | 光线追踪部分获得 ~40% 加速 |
| 仅光照更新(Lighting Update Only) | 复用当前缓存的 GBuffer,仅重新计算光照 | 开销最低,适合静态几何区域 |
探针选择的贪心算法(三步策略)
每帧有固定的 探针更新预算:
- 60fps 模式:约 1,000 个探针/帧
- 低端平台(如 Xbox Series S):约 500 个探针/帧
第一步:新创建的探针(Newly Created Probes)
- 当摄像机移动时,网格边界处会 新建探针,对侧旧探针被删除
- 新建探针数量波动较大(0 ~ 500),为了 稳定帧时间,这些探针使用 完整更新 + 四分之一分辨率 模式
第二步:全分辨率轮转更新(Round-Robin Full Update)
- 在每个级联(Cascade)中,按 轮转(Round-Robin) 方式选取一定数量的探针
- 大约消耗剩余预算的 ~16%
- 使用 完整更新 + 全分辨率 模式
- 经过若干帧后,可以完成每个级联的 完整刷新
第三步:仅光照轮转更新(Round-Robin Lighting-Only Update)
- 同样在每个级联中按轮转方式选取探针
- 消耗 所有剩余预算
- 使用 仅光照更新 模式
- 当摄像机移动不大时,此步骤占据单帧预算的 80% ~ 84%
设计思路总结:通过三级优先级分配——新探针优先保证覆盖、全更新保证质量刷新、光照更新保证大面积时效性——在有限预算内最大化探针体积的整体质量和响应速度。
探针更新详细流程(Probe Update Pipeline Details)
元数据预处理(Metadata Pre-Update)
在对选中的探针执行任何光照计算之前,首先更新其 元数据(Metadata):
- 室内/室外分类(Indoor / Outdoor Classification):判断探针处于室内还是室外(后续会用到)
- 地形分类(Terrain Classification):
- 如果探针 略低于地形表面,则将其 上移至地形之上
- 如果探针 深埋于地形之下,则直接 禁用该探针
GBuffer 更新(Full GBuffer Update)
光线投射策略
- 对选中的探针执行完整更新:向各方向 投射光线 以更新 GBuffer
- 光线在 像素中心(Center of Pixel) 处追踪,没有抖动(No Jittering),没有时域累积(No Temporal Accumulation)
- 原因:抖动和时域方法与 GBuffer 缓存方案不兼容
光线最大距离
- 光线的 最大追踪距离 取决于探针所属 级联(Cascade) 的 区域定义大小:
- 大级联 → 光线追踪 更远
- 小级联 → 光线追踪 更短
命中处理
- 命中几何体后,提取 所有材质信息 写入探针的 GBuffer
- 同时记录 背面命中次数(Back-face Hit Count):
- 在更新结束尝试 重定位探针(Relocate Probe) 时,如果无法找到合适位置且背面命中过多,则 禁用该探针
太阳阴影计算(Sun Shadow in GBuffer)
阴影写入 GBuffer
- 在更新 GBuffer 的同时,计算太阳阴影并存入 GBuffer
- 这是一种 近似做法:在两次 GBuffer 更新之间可能执行多次光照更新,期间太阳位置可能发生变化
- 但根据探针选择算法,GBuffer 大约每 2 秒完整更新一次,而太阳在此期间移动不大,因此近似 足够精确,且带来显著 性能提升
阴影计算的分层策略
| 步骤 | 方法 | 条件 |
|---|---|---|
| 第一步 | 查询摄像机附近的 阴影贴图(Shadow Map) | 命中点在阴影贴图范围内 |
| 第二步(回退) | 投射 阴影光线(Shadow Ray) | 命中点在阴影贴图范围外,且三角形 朝向太阳 |
| 第三步 | 叠加 云层阴影(Cloud Shadow) | 始终执行 |
光照快速变化的探针重缩放(Probe Rescaling for Fast Sun Movement)
问题描述
- 当太阳快速移动时(如快速时间过渡),每帧只更新少量探针会导致 明显的逐帧光照不一致,能清楚看到探针被分批更新的视觉瑕疵
解决方案:全局光照重缩放(Global Lighting Rescaling)
对 本帧未被重新计算 的探针进行光照缩放:
- 基于 太阳位置 和 天空可见性(Sky Visibility),分别计算 上一帧 和 当前帧 的光照近似值
- 取两者的 比值(Ratio) 作为 缩放因子(Scaling Factor)
- 用此缩放因子 调整未更新探针的光照
实现细节
- 无需逐 Texel 缩放:每个探针只存储 一个缩放因子,应用于整个探针
- 额外好处:缩放因子使每个探针处于 局部光照空间(Local Lighting Space),提升了存储精度
探针光照计算的双 Pass 策略(Split Lighting Computation)
架构设计
完成 GBuffer 更新(或复用缓存 GBuffer)后,对所有选中的探针计算光照。计算被分为 两个 Pass:
| Pass | 处理对象 | 计算内容 |
|---|---|---|
| Pass 1:命中几何 | 光线 命中了几何体 的 Texel | 计算来自所有光源的 直接光照 + 使用 上一帧探针体积 计算 间接光照 |
| Pass 2:未命中几何 | 光线 未命中任何几何体 的 Texel | 优先使用 探针辐射度缓存(Probe Radiance Cache)(若光线终点在探针体积内);否则回退到 天空近似纹理(Sky Approximation Texture) |
性能收益
- 将命中/未命中拆分为两个 Pass,加上额外的 分类 Pass(Classification Pass),总共获得约 20% 的性能提升
- 天空近似纹理 分辨率较小,精度有限,但作为最终回退已足够
探针光照输出与缩放因子(Probe Lighting Output & Rescaling)
双值输出策略
光照计算最终为每个探针输出 两个值:
| 输出值 | 用途 |
|---|---|
| 计算后的辐射度(Computed Radiance) | 用于后续卷积生成探针的平均辐射度 |
| 局部光源光照值(Local Light Value) | 卷积后得到平均局部光源照明强度 |
- 卷积后得到的 平均辐射度 和 平均局部光源照明 将被用于 缩放那些本帧未被重新计算的探针(即前文提到的 Rescaling 策略)
探针卷积流程(Probe Convolution Pipeline)
可见性卷积:方差阴影贴图(Variance Shadow Map)
- 从探针的 深度缓冲区(Depth Buffer) 计算一张 方差阴影贴图(Variance Shadow Map, VSM)
- 用途:在插值探针时计算 权重(Interpolation Weights),防止光照泄漏
关键技巧:重缩放与钳制顺序
- 由于编码到 8 位精度,需要将数值 重缩放并钳制到 [0, 1] 范围
- 关键点:重缩放和钳制操作 在计算平均深度和平均深度平方之前执行
- 这种顺序有助于 减少某些场景下的光照泄漏(Light Leaking),例如 U 形房间的角落
辐射度与辐照度卷积
在可见性之外,还对探针执行:
- 辐射度卷积 → 生成 滤波辐射度探针(Filtered Radiance Probe)
- 辐照度卷积 → 生成 辐照度探针(Irradiance Probe)
卷积过程中计算的元数据
| 元数据 | 用途 |
|---|---|
| 天空可见性(Sky Visibility) | 用于光照分类与缩放 |
| 平均局部光源亮度(Average Local Light Luminance) | 缩放因子的一部分 |
| 平均亮度(Average Luminance) | 作为探针的 缩放因子(Scaling Factor) |
卷积 Shader 优化
线程组分配策略
- 每个 线程组(Thread Group) 负责计算 一个探针 的卷积结果
- 每个 线程(Thread) 负责计算输出探针的 一个像素
利用硬件双线性过滤加速
- 所有线程首先协作,将探针像素 加载到 LDS(Local Data Share / 共享内存)
- 加载时利用 硬件双线性过滤(Bilinear Filtering),一次采样获取 4 个纹素(Texel) 的平均值
- 然后对加载的值执行卷积
效果:需要卷积的纹素数量 减少为 1/4,该 Pass 获得约 4 倍加速
精度权衡
- 这种做法并非完全精确——实际上是对 平均值 做卷积,而非对 每个纹素单独做卷积后再平均
- 但差异 非常小,相比获得的性能提升,完全可以接受
探针重定位(Probe Relocation)
目的
探针更新的最后一步:将 嵌入几何体内部 或 过于靠近几何表面 的探针移动到合适位置
重定位算法
- 检查探针的 深度缓冲区 中所有深度值
- 找到 最小深度(Min Depth) 和 最大深度(Max Depth)
- 判断条件:
- 如果最小深度 为负 或 过小(说明探针嵌入或过于贴近几何体)
- 则尝试沿 最大深度方向 移动探针
- 如果无法找到 足够远离几何体 的有效位置:
- 禁用该探针(Disable),标记为无效
逐像素漫反射光照 Pass(Per-Pixel Diffuse Illumination Pass)
基本参数
| 参数 | 设置 |
|---|---|
| 分辨率 | 四分之一分辨率(Quarter Resolution) |
| 光线方向 | 从 余弦分布(Cosine Distribution) 中随机选取 |
| 最大追踪距离 | 取决于光线起点离摄像机的距离:越远则追踪越远 |
- 四分之一分辨率已足够产生良好质量,且获得 4 倍加速
光线求交的两阶段流程
阶段一:屏幕空间光线步进(Screen Space Ray Marching)
对每条光线先在当前屏幕空间中步进,结果分为三类:
| 结果 | 处理 | 可视化颜色 |
|---|---|---|
| 命中有效几何 | 加入 有效命中列表(Valid Hit List) | 🟢 绿色像素 |
| 步进完整距离无命中 | 加入 未命中列表(Miss List) | 🔵 蓝色像素 |
| 其他情况(不确定) | 需要进入第二阶段 | 其余像素 |
阶段二:BVH 光线追踪(3D BVH Ray Casting)
对屏幕空间步进未能确定结果的像素,投射光线到 BVH 中:
- 命中几何 → 加入有效命中列表(🟢),同时构建命中点的 GBuffer 信息(材质、法线、深度/距离等)
- 未命中 → 加入未命中列表(🔵)
命中点光照计算(两个 Pass)
与探针光照类似,采用 双 Pass 结构。与探针流程的 主要差异 在于 太阳阴影的计算方式:
太阳阴影分层策略(Per-Pixel 版本)
由于逐像素光线追踪是从 摄像机视角 发射光线,因此可以访问 级联阴影贴图(Cascaded Shadow Map, CSM):
| 优先级 | 方法 | 条件 |
|---|---|---|
| 第一优先 | 级联阴影贴图(CSM) | 命中位置在屏幕上可见且在 CSM 范围内 |
| 第二优先(回退) | 围绕摄像机的辅助阴影贴图(Secondary Shadow Map) | 命中位置不在 CSM 可见范围内 |
这与探针中的阴影策略不同——探针先查摄像机附近的阴影贴图,然后回退到投射阴影光线;而逐像素 Pass 依赖 CSM 优先,辅助阴影贴图作为回退。
逐像素漫反射光照 Pass 详细流程
阴影处理的简化策略
- 当命中点 落在阴影贴图范围之外 时:
- 对于 漫反射情况,直接判定该点为 完全阴影(Fully Shadowed)
- 这样做对最终光照效果 影响极小,且 不会产生漏光(Light Leaking)
命中几何体的光照计算
依次执行以下步骤:
- 计算阴影后,计算来自 太阳 和 所有局部光源 的 直接光照(Direct Lighting)
- 使用 辐照度探针体积(Irradiance Probe Volume) 计算 间接光照(Indirect Illumination)
未命中几何体的回退(Miss Pass)
对于未命中任何几何体的光线,处理流程与探针体积类似:
| 优先级 | 回退方案 | 条件 |
|---|---|---|
| 1 | 辐射度探针体积(Radiance Probe Volume) | 命中位置在探针体积覆盖范围内 |
| 2 | 天空近似纹理(Sky Approximation Texture) | 命中位置在探针体积范围外 |
输出编码:球谐函数(Spherical Harmonics)
与探针体积直接输出辐射度不同,逐像素 Pass 采用 球谐函数(Spherical Harmonics, SH) 编码输出:
- 不直接输出辐射度,而是将其 编码为球谐系数
- 目的:尝试 重建每像素的完整球面辐射度,后续再进行降噪和上采样
- 这种方式能获得 显著更高的质量
额外输出:命中距离
- 同时输出 命中距离(Hit Distance),也会被降噪和滤波
- 命中距离的两大用途:
- 生成 光线追踪环境光遮蔽(Ray Traced AO),后续与最终结果合成
- 帮助 降噪器 决定:
- 使用的 滤波核大小(Kernel Size)
- 镜面反射光照的 重投影(Reprojection) 策略
降噪与上采样(Denoising & Upscaling)
降噪器
- 使用 Snowdrop 引擎降噪器的修改版本,基于 NVIDIA 降噪器
- 针对《刺客信条:影》做了额外修改(后续详述)
- 核心架构:多 Pass 时域 + 空域滤波(Multi-pass Temporal & Spatial Filtering)
上采样流程
| 步骤 | 说明 |
|---|---|
| 输入 | 四分之一分辨率的 球谐系数 |
| 上采样算法 | 双边上采样(Bilateral Upscaling) |
| 权重依据 | 深度(Depth)、法线(Normal)、材质(Material) |
| 最终评估 | 将上采样后的球谐系数与 全分辨率法线 进行求值,得到最终逐像素光照 |
球谐 vs 直接辐射度滤波的对比
| 方法 | 效果 |
|---|---|
| 直接滤波辐射度 | 细节丢失,方向性信息弱 |
| 滤波球谐系数后重建 | 更多细节保留,更好的光照方向性 |
球谐方法的核心优势:保留了光照的 方向信息(Directionality),在使用高分辨率法线求值时能恢复出更丰富的光照细节
间接镜面反射光照(Indirect Specular Illumination)
开发背景
- 该功能 最初并未计划在本作中上线,而是计划用于后续游戏
- 因项目延期,团队获得 额外 3 个月 来实现、调试并使其达到发行质量
- 时间有限,且 无法修改已有数据
- 因此主要聚焦于 质量 和 正确性,优化工作相对有限
与漫反射 Pass 的异同
算法整体与漫反射逐像素 Pass 高度相似,但针对镜面反射增加了 特有处理。
分辨率选项
| 分辨率模式 | 说明 |
|---|---|
| 四分之一分辨率 | 支持但非最终选择 |
| 半分辨率(Half Resolution) | 宽度减半,高度保持全分辨率 |
| 全分辨率 | 支持 |
最终发行版选择 半分辨率,因其 质量-性能比最优
镜面反射的特化处理
采样方向选择
- 使用 GGX 可见法线分布的重要性采样(Importance Sampling with GGX Visible Normal Distribution)
- 而非漫反射中的余弦分布采样
光线长度自适应
| 粗糙度 | 光线长度 | 原因 |
|---|---|---|
| 高粗糙度 | 短光线 | 行为接近漫反射波瓣 |
| 低粗糙度(接近镜面) | 极长光线 | 需要捕捉远处细节 |
性能优化:复用漫反射结果
- 镜面反射至少投射 两倍数量的光线,性能开销约为漫反射的 两倍
- 核心优化策略:复用漫反射 Pass 已计算的信息
- 对于 粗糙度高于特定阈值 的像素,其镜面反射行为已接近漫反射
- 直接复用该像素的 逐像素球谐系数,在 目标反射方向上求值 即可
- 无需额外投射光线,大幅节省开销
间接镜面反射光照的优化与质量改进
低权重像素的加速策略
- 当光线方向 远离完美镜面反射方向 时,该像素的 权重非常低
- 此时直接使用 球谐函数求值(Spherical Harmonic Evaluation) 代替完整计算
- 视觉差异 几乎不可察觉,但获得 20%~30% 的加速
- 副作用:由于使用了更高质量的渲染,细节和瑕疵也更容易被看到
镜面反射中的瑕疵修复
核心问题
- 在漫反射 Pass 中做的 大量近似 在漫反射结果中 不可见
- 但在 高镜面反射材质(如水面反射)中,这些近似 开始显现为可见瑕疵
- 因此需要逐一修复
修复项一:Alpha Test 材质
| 漫反射 Pass | 镜面反射 Pass |
|---|---|
| 使用 近似 Alpha Test(更快) | 改用 真正的 Alpha 纹理测试(Alpha Texture Test)(更高质量) |
- 近似方案在反射中 清晰可见,必须替换
修复项二:材质定义的精度提升
- BVH 中使用的材质定义 较为简化,对漫反射足够,但在 接近镜面反射 时能看出差异
- 解决方案:屏幕空间重投影(Screen Reprojection)
- 每次光线命中 BVH 几何体后,将命中点 重投影到屏幕空间
- 如果重投影位置 有效且匹配,则用 GBuffer 中的高质量材质信息 替换 BVH 材质
- 由于两者差异不大,分界线几乎不可见,最终光照结果自然
修复项三:更复杂的光照计算
| 增加项 | 说明 |
|---|---|
| 镜面反射 BRDF 求值 | 在漫反射 Pass 中为提速被移除,镜面 Pass 中重新加入 |
| 阴影光线(Shadow Ray) | 当命中点 落在太阳阴影贴图范围外 时,投射阴影光线计算阴影——仅限 PC 平台 |
探针插值优化(Miss Pass)
问题
- 探针插值成本 占光照评估总成本的约一半
- 需要获取 8 个探针 的数据并计算权重,开销很大
单探针快速路径(Single Probe Shortcut)
1. 选取命中位置处的 最近探针(Nearest Probe)
2. 计算该探针的 权重(Weight)
3. if 权重足够高:
→ 仅使用该单个探针(快速路径)
else:
→ 回退到完整的 8 探针插值
- 效果:探针级联评估速度 提升约 2 倍
Miss Pass 的回退纹理升级
漫反射 Pass 的回退
- 使用简单的 天空近似纹理(Sky Approximation Texture)
镜面反射 Pass 的升级
- 替换为 完整的局部立方体贴图(Local Cubemap):
- 跟随摄像机位置
- 每帧更新
- 包含内容:地形 + 天空 + 所有 BVH 外的远距离几何体
原因:在高反射表面上,远距离缺失几何体 非常明显。升级后的回退纹理提供了 更丰富的远景信息
雾效评估(Fog Evaluation)
| 漫反射 Pass | 镜面反射 Pass | |
|---|---|---|
| 雾效 | ❌ 不计算(差异小,节省开销) | ✅ 计算雾效 |
- 在 水面等体积反射场景 中差异极大
- 加入雾效后 与场景整合度大幅提升
- 演示对比:左侧无雾效 vs 右侧有雾效
镜面反射的降噪挑战(Specular Denoising)
核心难点
- 镜面反射的滤波核 远小于漫反射
- 结果 更难降噪
- 需要额外技术辅助
BRDF 解调(BRDF Demodulation)
基于 NVIDIA NRD 文档 中描述的技术:
基本原理
- 计算辐射度时 不包含 BRDF 项
- 对 纯辐射度 进行滤波(避免 BRDF 引入的额外噪声)
- 滤波完成后,再乘以 预积分 BRDF(Pre-integrated BRDF) 恢复最终结果
问题:掠射角瑕疵
- 这是屏幕空间追踪 + 预滤波局部 Cubemap 的标准做法
- 但在 掠射角(Grazing Angle) 处会出现 可见瑕疵
- (演示中正在展示该问题)
后续预计会讨论针对此掠射角问题的解决方案
镜面反射降噪的进一步改进
BRDF 解调的改进方案
问题
- 简单的 BRDF 解调在某些情况下,当光照过强时会产生可见瑕疵(放大后尤为明显)
改进策略
| 步骤 | 操作 |
|---|---|
| 1 | 完整计算 辐射度 × BRDF 求值结果 |
| 2 | 滤波前,将结果 除以预积分 BRDF(Pre-integrated BRDF) |
| 3 | 对除法后的结果执行 降噪滤波 |
| 4 | 滤波完成后,乘回预积分 BRDF |
核心思想:将 BRDF 引入的噪声在滤波前分离出去,滤波在更平滑的信号上执行,最终再恢复 BRDF 贡献。比纯解调方案效果更好。
亮度空间压缩降噪(Luminance Space Compression)
问题场景
- 室内朝室外看时:部分光线命中 低照度的室内表面,部分光线命中 高照度的室外
- 两者亮度 差异极大,导致 极度噪声,降噪器难以处理
解决方案:在压缩空间中降噪
- 不在线性光照空间 中执行降噪
- 先对亮度进行 非线性压缩,再执行降噪,降噪后再解压
压缩函数选择
| 方案 | 评估 |
|---|---|
| Reinhard Tone Mapping | 常用选择,但 过度压制眼部适应级别的亮度,损失太多中间调细节 |
| 幂函数(Power Function) ✅ | 最终采用,指数为 0.0625(即约 1/16 次方) |
物理上不准确,但有效减少噪声,满足发行质量要求。
萤火虫去除(Firefly Removal)
-
在降噪器的某个 已有的滤波 Pass 中集成(该 Pass 本身就遍历周围像素)
-
算法:
- 在遍历邻域像素时,计算邻域平均亮度
- 将当前像素亮度与邻域平均亮度 比较
- 如果差异 过大,则 钳制(Clamp) 当前像素亮度至合理范围
-
几乎 零额外开销(复用已有 Pass 的数据)
Alpha Test 材质的性能挑战(Alpha Tested Materials)
问题
- Alpha Test 材质在光线追踪中 非常昂贵
- 每次光线与 BVH 相交时,需要调用 Any Hit Shader 检查 Alpha 值
各方案对比
| 方案 | 成本(相对参考) | 质量 | 说明 |
|---|---|---|---|
| 参考方案(无限制 Any Hit) | 100% | 最高 | 不限制 Any Hit 调用次数 |
| 限制 Any Hit 调用次数 | ~85% | 有瑕疵 | 简单但产生可见问题 |
| 进一步限制(N=2) | 更低 | 较好 | 用于 镜面反射 GI(其他方案瑕疵太明显) |
| 蓝噪声抖动 + 平均不透明度 | 无明显提升 | 中等 | 基于概率决定光线是否穿透;不限制时几乎无增益 |
| 不透明度近似(Opacity Approximation) ✅ | ~60% | 接近 N=2 | 漫反射 GI 和屏幕空间阴影的最终方案 |
不透明度近似方案(Shipped Solution for Diffuse)
1. 根据网格的 平均不透明度(Average Opacity)缩放三角形
2. 仅使用 完全不透明的交叉测试(Fully Opaque Intersections)
3. 完全跳过 Any Hit Shader
- 效果与 N=2 Alpha Test 限制 非常接近
- 成本仅为参考方案的 约 60%
半透明材质的间接光照(Translucency)
问题
- 缺少半透明材质的间接光照贡献会导致场景 明显偏暗
- 引入半透明间接照明后,场景 亮度显著提升
实现方案
二次命中的直接光照(Direct Lighting on Secondary Hit)
- 对命中点的 正面和背面 都评估直接光照
- 将两者的贡献 加和
二次命中的间接光照(Indirect Lighting on Secondary Hit)
- 同样需要评估正面和背面的辐射度
- 优化:不执行两次独立的探针查询,而是:
- 查找 8 个最近探针
- 根据 正面/背面 对这 8 个探针进行 分类(Classification)
- 分别用对应的探针子集评估正面和背面辐射度
主光线命中表面的直接光照(Direct Lighting on Primary Hit)
- 根据材质的 半透明度(Translucency) 调整选择 正面 和 背面 的概率
- 这改变了采样的 概率密度函数(PDF),需要相应 补偿(Accommodate)
采样概率调整:
P(front face) = f(translucency)
P(back face) = 1 - P(front face)
最终贡献 = sample_value / adjusted_PDF
通过概率调整而非两次完整计算,在保持物理正确性的同时节省了开销。
半透明材质的球谐编码
球谐编码的法线翻转
- 编码为球谐函数时,将采样方向 翻转为正面对齐(Front-face Aligned)
- 确保球谐系数在后续求值时方向一致
光照泄漏问题与解决方案(Light Leaks)
光照泄漏是实时全局光照方案中的 已知经典问题。本作将泄漏来源分为 直接光照泄漏 和 间接光照泄漏 两大类。
直接光照泄漏(Direct Illumination Leaks)
泄漏来源
| 来源 | 说明 |
|---|---|
| CSM 剔除优化 | 屏幕上不可见或不产生可见阴影的物体 不被渲染到级联阴影贴图中 |
| CSM 范围有限 | 距离摄像机过远的物体 没有阴影信息 |
| 强偏置(Bias) | 《刺客信条:影》中墙壁非常薄,需要较大 Bias,易导致漏光 |
解决方案:二级阴影贴图(Secondary Shadow Map)
由于物体可能在屏幕遮挡区域中被从 CSM 剔除,引入一张 低分辨率补充阴影贴图:
| 参数 | 设置 |
|---|---|
| 分辨率 | 1K × 1K |
| 覆盖范围 | 玩家周围约 64 米 |
| 分块 | 分为 16 个 Tile |
| 更新策略 | 时间分片(Time-sliced),16 帧完成一轮更新 |
| 渲染内容 | 始终渲染所有遮挡体(不做剔除优化) |
二级阴影贴图的应用
- 延迟光照(Deferred Lighting)阶段应用与 CSM 相同的 Bias
- 对探针执行 Min Gather,进一步压制阴影泄漏
- 双重范围外的回退策略:如果命中点同时在 CSM 和二级阴影贴图范围外,直接判定为 阴影状态,减少探针漏光
镜面反射中的副作用
- 反射中可能出现 屏幕外物体被错误判定为阴影 的情况
- PC 高质量模式 的解决方案:在光线追踪 Pass 中启用 阴影光线投射(Shadow Ray Cast) 缓解此问题
间接光照泄漏(Indirect Illumination Leaks)
核心问题
- 二次命中点周围使用 8 个探针插值
- 某些 室外探针 亮度远高于室内,即使权重较低仍会 泄漏大量光照到室内
- 权重系统 无法充分补偿 室内外巨大的亮度差异
探针查询的偏移策略(Query Position Offsets)
在探针过滤时,对查询位置进行三种 经典偏移:
| 偏移类型 | 说明 |
|---|---|
| 视线方向偏移(View Direction Offset) | 沿视线方向偏移查询点 |
| 采样方向偏移(Sampling Direction Offset) | 沿采样方向偏移 |
| 反向光线方向偏移(Inverse Ray Direction Offset) | 沿光线反方向偏移 |
- 所有偏移量 限制在光线追踪距离的一半以内
探针权重计算(Probe Weighting)
| 步骤 | 方法 |
|---|---|
| 1. 方向权重 | 使用 Wrap Shading 计算方向性权重 |
| 2. 可见性权重 | 评估探针的 方差阴影贴图(VSM) |
| 3. 权重压制 | 对小于 0.2 的权重进行 平方压制(Crush),增强遮挡效果 |
| 4. 三线性插值权重 | 最终乘以 三线性插值权重(Trilinear Interpolation Weight) |
问题:可见性测试 精度不够,室外探针亮度远超室内,仍然产生泄漏
室内体积分类(Indoor Volume Classification)
利用游戏中已有的 室内体积(Indoor Volumes)(原用于烘焙方案):
| 用途 | 说明 |
|---|---|
| 室内/室外分类 | 将二次命中点和探针分别标记为 室内(Indoor) 或 室外(Outdoor) |
| 地下区域识别 | 标识地形下方需要 保持有效 的区域(如地下室、隧道) |
| GI 遮挡器(GI Blockers) | 为 洞穴 等极低照度区域添加额外遮挡,阻止天空光泄漏(此场景中更为关键) |
探针环境光遮蔽(Probe AO)
作为最终的遮挡补偿步骤:
目的
- 表示 探针覆盖范围内缺失的遮挡信息
- 当光线追踪距离 小于探针网格间距 时,存在遮挡信息缺失
算法
if hit_distance < grid_spacing:
occlusion_scale = hit_distance / grid_spacing
// 按比例缩放遮挡贡献
else:
// 无需额外遮挡补偿
- 考虑 网格间距(Grid Spacing),根据命中距离与间距的比值进行缩放
- 补偿探针在近距离范围内 无法捕获的细节遮挡
光照泄漏解决方案总结
直接光照泄漏
├── 二级阴影贴图(1K×1K, 64m, 16-tile time-sliced)
├── 探针 Min Gather 压制
├── CSM + 二级阴影贴图范围外 → 默认阴影
└── PC 高质量:阴影光线投射
间接光照泄漏
├── 查询位置三重偏移(View / Sample / Inverse Ray)
├── 权重计算(方向 × VSM × 压制 × 三线性)
├── 室内体积分类(Indoor / Outdoor / Underground)
├── GI Blockers(洞穴等关键区域)
└── Probe AO(近距离遮挡补偿)
遮蔽处理(Occlusion)
多层遮蔽合成流程
遮蔽效果通过 多个 Pass 逐层叠加 构建最终结果:
| 层级 | Pass | 说明 |
|---|---|---|
| 1 | 镜面反射球谐结果 | 前文 Luke 展示的基础间接光照 |
| 2 | 探针 AO(Probe AO) | 影响 树木和树冠(Canopies) 等大尺度遮蔽 |
| 3 | 光线追踪环境光遮蔽(Ray Traced AO) | 类似 SSAO,由美术驱动调参(Artistically Driven) |
| 4 | GTAO(Ground Truth Ambient Occlusion) | 参数化用于恢复 微观细节(Micro Details),特别是 BVH 中缺失物体(如角色、地面杂物)的遮蔽 |
最终效果:基础球谐光照 → 加入探针 AO → 加入光追 AO → 加入 GTAO → 完整遮蔽结果
降噪的特殊挑战与解决方案(Denoising Challenges)
半透明材质的降噪问题
- 问题:同一物体上部分区域半透明、部分不透明时,半透明区域会 向自身泄漏光照
- 解决方案:添加 半透明遮罩(Translucency Mask) 用于降噪分类
角色与植被的降噪遮罩
- 角色和植被在 无限距离追踪 时产生大量噪声
- 添加 角色/植被遮罩(Character & Vegetation Mask) 辅助降噪
室内场景的高噪声问题
问题
- 室内场景 依然非常嘈杂
解决方案:探针辐射度缓存回退(Probe Radiance Cache Fallback)
1. 追踪较短光线(Short Rays)
2. 更早回退到辐射度缓存(Radiance Cache)
3. 缓存已滤波且时域稳定
| 优势 | 劣势 |
|---|---|
| 性能提升 | 引入更多 光照泄漏 |
| 减少高频细节噪声 | 精度降低 |
| 时域稳定性大幅提升 | — |
本质上是 精度与噪声之间的权衡
去遮蔽(Disocclusion)处理
| 尝试方案 | 结果 |
|---|---|
| 直接使用最近探针的 GI(ProbeGI) | 产生太多瑕疵,未采用 |
| 额外空间滤波 Pass ✅ | 使用 Poisson 滤波器,8 或 16 个采样点,用于去遮蔽像素 |
植被特殊处理
- 移除法线权重贡献:去遮蔽时植被不使用法线作为滤波权重
- 放松时域重投影标准:对运动植被使用更宽松的重投影匹配条件
植被去遮蔽仍然是 最嘈杂的场景,团队计划未来继续改进
性能数据(Performance)
场景复杂度(典型城市场景)
| 指标 | 数值 |
|---|---|
| BLAS 物体数 | ~2,000 |
| TLAS 实例数 | ~30,000 |
| GPU 加速结构内存 | ~320 MB |
探针内存
| 指标 | 数值 |
|---|---|
| 探针数量 | ~10,000 |
| 内存占用 | ~73 MB |
各平台计时
测试场景:大阪城的特定视角
| 说明 | 详情 |
|---|---|
| 异步计算 | 展示了 Pass 在 异步线程(Async Thread) 和 图形线程(Graphics Thread) 上的分布 |
| 无异步模式 | 橙色数据为所有 Pass 线性执行 时的耗时 |
各平台特殊设置
| 平台 | 特殊处理 |
|---|---|
| Xbox Series S | 每帧更新的探针数量 减半;漫反射 GI 使用 更低分辨率 |
| PC | 每帧更新探针数量也 减半(因更高帧率) |
| 镜面反射 GI | 仅在 PS5 Pro 和 PC 上发售,耗时仍然很高(优化时间不足) |
当前方案的局限性(Limitations)
工程与生产层面
| 局限 | 说明 |
|---|---|
| 维护两套 GI 系统 | 非常耗时 |
| 双重美术调参 | 需要为两套系统分别调整对比度和 GI 效果,成本极高 |
| 材质重追踪(Retracing Materials) | 简化复杂着色器的工作量巨大 |
| BVH 外物体缺失 AO | 非常明显,不得不用 GTAO 等方案补偿 |
| BVH 质量固定 | 近处和远处物体在光追世界中质量相同,无 LOD 概念 |
| 缺少 3D 运动向量 | 导致 更多去遮蔽问题 |
算法层面
| 局限 | 说明 |
|---|---|
| 探针网格分辨率固定 | 增加分辨率/减小间距可提升细节,但在均匀分布的级联中 扩展性差 |
| 探针插值始终泄漏 | 方差阴影贴图的可见性测试 精度不足,无法完全防止泄漏 |
| 屏幕追踪优化的副作用 | 光栅化世界与 BVH 几何体 差异过大 时(某些情况下 50cm 阈值不够) |
| 复杂场景仍然嘈杂 | 城堡地下室中不得不 移除室内体积分类,宁可接受泄漏也不要噪声和时域错误 |
| 双重上采样的累积损失 | GI 先滤波上采样一次,再经最终上采样器(如 TSR/FSR)二次上采样,噪声问题被放大 |
未来工作(Future Work)
降低生产支持负担
| 目标 | 计划 |
|---|---|
| 简化光追发行流程 | 降低使用光追的门槛 |
| 自动化 BVH 构建 | 减少手动标记和调整 |
| 淘汰烘焙 GI 遗留技术 | 最终统一为单一光追 GI 方案 |
| 扩展到低端平台 | Xbox Series S 性能已在可发行范围,但 内存规划未为此优化 |
噪声与泄漏改进
| 方向 | 说明 |
|---|---|
| 重要性采样(Importance Sampling) | 减少噪声、提升收敛速度 |
| 表面生成探针(Surface-generated Probes) | 在几何表面上放置探针,而非均匀网格 |
| 稀疏级联(Sparse Cascades) | 提升探针分布的灵活性 |
降噪器优化
- 当前降噪 Pass 占总耗时近一半
- 计划 显著降低降噪成本
总结
本次演讲完整展示了《刺客信条:影》从 烘焙 GI 向全光线追踪 GI 过渡 的技术路线:
- 探针体积系统(Probe Volume):多级联、时间分片更新、优化的卷积与插值
- 逐像素漫反射 GI:四分之一分辨率、球谐编码、高效降噪
- 逐像素镜面反射 GI:更高质量的材质/光照/雾效计算
- 多层遮蔽合成:探针 AO + 光追 AO + GTAO
- 防泄漏体系:二级阴影贴图、室内体积分类、探针权重压制
- 降噪体系:亮度空间压缩、BRDF 解调改进、萤火虫去除、特殊材质遮罩
- 平台适配:异步计算、分辨率/更新频率分级
核心权衡始终围绕 精度 vs 噪声 vs 性能 vs 泄漏 四个维度展开,团队在有限时间内做出了务实的工程决策。