ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

第46章《控制》中的光线追踪

第46章《控制》中的光线追踪 第46章《控制》中的光线追踪尤哈·斯约霍姆¹保拉·尤卡莱宁¹塔图·阿尔托²¹英伟达²绿美迪娱乐摘要在本章中我们将描述光线追踪如何在《控制》中使用。我们将解释所有基于光线追踪的效果是如何实现的包括不透明和透明反射、近场间接漫反射光照以及接触阴影。此外我们还将描述为这些效果量身定制的降噪器。这些效果在游戏中实现了卓越的视觉质量同时保持了实时帧率。46.1 引言于2019年发布的《控制》是最早支持光线追踪的游戏之一。在此光线追踪以多种方式被用于实现更高的视觉质量。《控制》采用了混合渲染方法结合了光栅化和光线追踪。图46-1.《控制》使用光线追踪效果例如反射和近场间接漫反射光照以增强其独特的艺术风格。图片由 DeadEndThrills 提供。图46-2.在启用光线追踪效果的《控制》一帧的简化分解图展示了光栅化和光线追踪如何用于不同目的。光线追踪被用于不透明和透明反射、近场间接漫反射光照以及接触阴影。这些效果结合起来证明光线追踪可以在实时游戏中实现全新水平的真实感。一帧的简化分解如图46-2所示。本节引言将介绍游戏引擎中被各种光线追踪效果所利用的通用特性。本章其余部分将深入探讨《控制》中每种效果包括降噪的实现细节。我们将描述每种效果如何被优化以维持实时帧率。我们强调两个关键的、反复出现的策略它们是达到性能和质量目标的关键通过尽可能缩短光线长度来降低非相干性。以适当的精度水平对非相干光线进行着色这既能减少噪点又能提高性能。46.1.1 Northlight 引擎《控制》使用 Northlight 引擎开发这是 绿美迪娱乐 开发的内部游戏引擎。Northlight 引擎使用延迟光照和无绑定材质系统这简化和优化了光线追踪效果例如追踪和着色 secondary 光线的效果将在第46.2、46.3和46.4节讨论的实现。它还允许为每个像素追踪选定光源的阴影光线在第46.5节讨论。此外该引擎支持所有材质的近似统一参数化和着色模型这用于对光线命中点进行着色。这对于非相干光线尤其有效如第46.2.2节所述。46.1.2 预计算全局光照Northlight 引擎支持基于体素的预计算全局光照详见 Aalto [1]这在优化光线追踪反射和间接漫反射光照方面发挥了关键作用如第46.2.3和46.4节所述。预计算由路径追踪器执行基于静态物体和选定的光源。游戏关卡的全局光照数据存储在稀疏的体素纹理中。在给定位置上可用数据的分辨率本质上是艺术家设定的。可以使用世界空间位置和方向向量对数据进行采样。采样结果是给定位置上面向给定方向的半球上的辐照度。46.1.3 加速数据结构构建游戏中所有光线追踪过程都使用相同的加速数据结构。构建加速数据结构的一个重要原则是使用与光栅化相同的几何细节级别。这有助于避免自相交同时为光线追踪提供尽可能多的细节。光线追踪效果的一个设计目标是提供比在光栅化场景后执行的屏幕空间技术更精确的细节。为了选择要包含在光线追踪加速结构中的物体对场景物体应用了扩展的相机视锥体裁剪以收集可能对某些效果有贡献的物体。所有不透明和大多数 alpha test 的物体都被包含在内。一些带有 alpha test 的植被资源被排除因为包含它们带来的视觉增益微乎其微而增加的光线追踪成本却很高。被渲染为不透明网格的粒子也被包含在加速数据结构中。混合物体和粒子被排除但这不会导致严重的视觉问题。然而为了支持发现透明表面以在其上渲染反射混合物体被插入到结构中并带有一个特殊的剔除掩码。这将在第46.3.1节详细讨论。为了减少光线追踪期间的内存和缓存流量对所有静态加速数据结构执行了 DirectX Raytracing API 中提供的压缩操作。蒙皮网格通过计算着色器通道将蒙皮后的顶点输出到缓冲区从而表示为三角形几何体。加速结构的重建和更新被调制以实现更好的整体性能但光栅化和光线追踪的网格在每一帧都保持匹配。所有加速结构构建工作都在异步计算队列上执行如图46-2所示。46.1.4 光源聚类为了对高光反射或间接漫反射命中点进行着色我们需要知道哪些光源会影响给定的命中位置。评估所有场景光源的光照是不可能的仅仅因为评估成本太高。游戏关卡可能包含数千个动态光源。对于光栅化已经存在有效的基于屏幕瓦片的光源剔除实现但这并不直接适用于对光线命中点进行着色。对于光线追踪效果需要执行一个额外的光源聚类过程。它将场景光源剔除到视图空间中轴对齐的3D网格的单元格中。该网格的有限大小与光线追踪效果的范围相匹配。聚类过程将影响每个网格单元格的光源索引存储到一个纹理中。当对光线命中点进行着色时会处理与命中位置匹配的网格单元格所对应的光源列表。46.2 反射《控制》中光线追踪高光反射的实现是直接的。根据视图方向和存储在光栅化 G 缓冲区中的表面属性为每个像素生成反射光线。通过使光线追踪和光栅化中的几何 LOD 相匹配来避免自相交。此外由于从光栅化深度缓冲区重建世界空间位置的不准确性在光线起点处添加了一个沿相机方向并按像素深度缩放、同时沿表面法线的偏置值。为每个像素不包括天空像素追踪一条光线。游戏使用 GGX 高光双向反射分布函数并且表面具有空间变化的粗糙度级别。一个重要的设计目标是使反射在所有游戏内容中保持一致。图46-3展示了不同表面上的反射。以下章节描述了用于在期望的视觉质量和性能之间找到良好平衡的技术。设置光线长度、未命中光线的回退解决方案以及命中点的着色质量被证明是关键问题。图46-4说明了总体工作流程。图46-3.左图屏幕空间反射。右图光线追踪反射显示了更精确的细节。图像应用了第46.6节中解释的降噪过程。图46-4.光线追踪反射渲染的总体概览。46.2.1 使用可变光线长度追踪反射光线每个像素的光线方向是根据 GGX 分布进行重要性采样的。更高的表面粗糙度意味着相邻像素上的光线方向更加非相干这会导致渲染图像中出现更多噪点和更高的计算成本。为了避免产生无论如何都需要在降噪过程中从最终图像中去除的噪点并降低评估成本反射光线的长度根据表面粗糙度进行限制如图46-5所示。在最大粗糙度值1.0的表面上光线长度仅为约3米。当粗糙度降到最小值0.0时光线长度呈指数级增加到约200米。这些限制是通过实验选择的以使结果在视觉上合理。为了使反射物体在靠近反射表面时平滑地出现在图像中还应用了基于像素索引的随机变化到光线长度上。这隐藏了从光线未命中到光线命中的切换带来的视觉伪影。图46-5.反射光线长度如何根据表面粗糙度变化。左图当粗糙度较低且光线方向分布相干时生成长度较长的反射光线。右图当粗糙度较高且方向分布不相干时使用较短的光线。46.2.2 统一命中点着色为了对 G 缓冲区进行着色《控制》具有许多材质变体带有特殊的着色模型例如用于角色皮肤、眼睛和头发的模型。然而对于光线追踪效果中的命中点着色所有材质都使用一个基于简单参数化的物理着色统一变体如第46.1.1节所述即所有材质都使用单一参数化和着色模型。这使得《控制》能够在 DXR API 中仅使用一个任意命中着色器和一个最近命中着色器。统一任意命中着色器的概览如代码清单46-1所示。代码清单46-1. 用于执行 alpha test 的单个任意命中着色器的伪代码概览。 glsl uint material GetHitMaterialID(); uint3 vertexIndices GetHitVertexIndices(); float2 uv InterpolateHitUV(barycentrics , vertexIndices); float alpha SampleMaterialAlpha(material , uv); if (alpha 0.5f) IgnoreHit();统一路径只是一个近似但在实践中提供了视觉上合理的结果。它简化了光线追踪的开发并有助于特别是在处理非相干光线和 alpha test 时获得满意的性能。对于非相干光线它还减少了输出中的噪点。命中点着色的结果是到达 G 缓冲区表面的辐射亮度。在将其应用于接收表面之前会对其进行降噪如第46.6节所述。46.2.3 用于光线未命中的预计算全局光照当高光反射光线未命中时使用预计算的 GI 数据第46.4节来近似来自光线方向的辐射亮度。在未命中光线的终点处对数据进行采样。如图46-6所示采样结果提供的半球上的辐照度在被用作近似值之前会被转换为平均辐射亮度。显然这因多种原因而不准确。结果仅基于静态几何体和光源数据分辨率有限并且将辐照度转换为平均辐射亮度可能导致漏光。但尽管有这些限制这种方法在实践中提供了视觉上合理的结果。图46-6.预计算的 GI 数据如何用于未命中的光线。左图反射光线击中了一个物体并对命中点进行了着色。右图反射光线未命中距离过远的物体在光线终点处采样 GI 数据作为入射辐射亮度的近似值。采样 GI 数据得到半球上的辐照度。将其转换为平均辐射亮度。46.2.4 用于命中和未命中的统一全局光照采样为了进一步统一着色过程命中和未命中的处理使用了部分相同的代码路径。这是因为 GI 数据被用来近似命中的第二次光线反弹和未命中的第一次反弹。对于命中GI 数据直接在命中位置采样用于第二次反弹的近似。这意味着命中和未命中处理不是在命中或未命中着色器内部完成的。在《控制》中着色发生在一个单独的计算着色器通道中该通道在实际的光线追踪通道完成后被调度。单独的通道减少了缓存压力尤其是在光线非相干的情况下。它的实现方式是在命中着色器中解析命中点的几何法线和纹理坐标并将它们与命中位置和材质标识符一起存储到纹理中以供着色通道使用。命中位置以视图空间存储使得半精度浮点值足以容纳它。未命中的光线用一个特殊的材质标识符标记。使用统一 GI 采样处理命中和未命中的计算着色器概览如代码清单46-2所示。代码清单46-2. 对命中和未命中执行 GI 采样的着色通道的伪代码概览。 glsl float originDepth DecodeGBufferDepth(); float3 position DecodeHitOrMissPosition(); uint material DecodeHitMaterialID(); float3 normal DecodeHitNormal(); float2 uv DecodeHitUV(); bool isHit isMaterialHit(material); float3 rayDirection ReconstructRayDirection(originDepth , position); float3 irradiance SampleGI(isHit, position , normal, rayDirection); float3 radiance; if (isHit) { radiance ShadeHit(position , normal, rayDirection , material , uv, irradiance); } else { radiance ConvertToAverageRadiance(irradiance); } WriteOutput(radiance);46.3 透明反射游戏中的许多关卡包含相当多的玻璃窗、内墙以及其他带有玻璃表面的物品例如挂钟或画框。在这些表面上使用光线追踪反射感觉是对不透明表面反射的一个很好的补充。不同情况下的反射如图46-7所示。以下章节描述了如何识别接收光线追踪反射的透明表面如何生成反射光线以及如何将结果应用于接收表面。图46-8说明了总体工作流程。图46-7.左图透明表面上基于环境贴图的反射。右图光线追踪反射显示出明显更精确的细节。图46-8.光线追踪透明反射渲染的总体概览。46.3.1 发现透明表面《控制》使用光线追踪来寻找透明表面。初级光线仅针对透明物体进行追踪。光线的长度基于光栅化的不透明深度缓冲区进行限制以仅发现可见表面如图46-9所示。如果初级光线击中了透明表面则根据表面属性生成反射光线。否则该像素被标记为没有透明表面。初级光线方法的替代方案可能是光栅化一个透明的 G 缓冲区并基于它追踪 secondary 光线。然而光线追踪方法的性能被证明是具有竞争力的。初级光线的方向自然是相干的并且应用于它们的处理在此情况下是统一且相当简单的。透明物体与其他所有物体一起被插入到相同的加速结构中但用不同的剔除掩码标记。也曾尝试将它们存储在一个单独的结构中但由于在这种情况下两种方法之间没有显著的总体性能差异为了简单起见选择了使用公共加速结构。总体性能是加速结构构建成本和光线追踪成本的结合。图46-9.首先通过追踪针对透明物体的初级光线来发现透明表面然后为找到的最近表面追踪一条递归反射光线。初级光线的长度基于光栅化的不透明深度进行限制。在发现透明表面后可以继续初级光线以发现下一个透明表面。然而出于性能原因透明反射仅限于最近的表面。额外反射光线的成本会变得太高无法在某些场景中支持更多层。46.3.2 追踪透明反射光线反射光线基于初级光线击中的内容生成。每个像素使用一条光线与不透明反射相同。评估表面法线并且也应用法线贴图。然而忽略可能的表面粗糙度值反射光线方向总是被评估为完美的镜像反射没有任何随机化。这可以避免额外的降噪过程。游戏中的大多数透明表面实际上是镜面反射体因此在视觉上这样做效果很好没有令人困扰的问题。由于光线追踪中无法自动选择纹理 LOD 级别因此需要评估一个用于采样法线贴图的 LOD 近似值。否则在某些情况下法线贴图的反射光线方向会产生大量噪点。基于世界空间中像素大小的简单 LOD 评估在此情况下被证明是足够的。反射光线的长度被限制在60米。在主要为室内场景的游戏中这个限制通常不会达到但为了在所有场景下保持性能稳定仍然应用了该限制。达到距离限制后反射将基于与禁用光线追踪反射时使用的相同的环境立方体贴图。在距离限制附近结果会从光线追踪结果渐变到基于立方体贴图的结果以避免不同视觉效果之间的突然切换。反射命中点的着色方式与不透明表面上的反射相同。执行一个单独的计算着色器通道使用简化和统一的着色器根据视图空间聚类过程剔除的动态光源对命中表面进行着色。尽管透明表面不支持粗糙反射但丰富的法线贴图细节偶尔会导致非常非相干的反射光线方向这引起了大量的非相干内存访问和缓存压力。一个特殊的挑战性案例是碎玻璃这在射击游戏中相当常见。46.3.3 将反射添加到光栅化的透明表面评估得出的、从反射光线方向朝向透明表面的入射辐射亮度并不会立即应用于表面。透明表面的实际着色发生在一个单独的光栅化通道中。入射辐射亮度与透明表面的深度一起存储在一个纹理中。当光栅化和着色透明物体时将被着色表面的深度与存储在纹理中的深度值进行比较。当它们匹配时使用存储的辐射亮度而不是基于环境立方体贴图的反射。这种方法将反射的着色与反射表面的着色解耦并允许使用与禁用光线追踪时相同的正向着色光栅化方法来渲染透明表面。46.4 近场间接漫反射光照《控制》中光线追踪间接漫反射光照的实现方式与高光反射在许多方面相似。然而由于使用光线追踪的间接光照也取代了用屏幕空间遮蔽调制预计算的全局光照它有两个方面的作用。它除了实际评估动态间接漫反射光照外还起到了光线追踪环境光遮蔽的作用。结果如图46-10所示。图46-10.左图预计算的 GI 应用于不透明表面并由屏幕空间遮蔽调制。右图除了由光线追踪遮蔽调制的预计算 GI 外还将光线追踪的动态间接漫反射光照应用于表面。光线生成基于光栅化的 G 缓冲区使用由漫反射 Lambert BRDF 模型定义的余弦分布作为光线方向。光线长度限制为一米。短光线在解析遮蔽方面效果很好因为它们主要只需要覆盖动态遮挡物造成的遮蔽。来自更大距离的静态大型物体造成的遮蔽已经预计算到 GI 数据中当光线未命中时这些数据会被应用于表面如图46-11所示。当光线命中时则应用来自命中表面的辐射亮度。在评估辐射亮度时忽略高光 BRDF 以消除噪点。当用每个像素仅一条光线来近似半球上的漫反射积分时依赖于方向的高光高光可能会引入相当多的噪点。预计算的 GI 用于近似命中的第二次反弹。与高光反射的着色类似这允许对命中和未命中进行部分统一的处理。无论光线是命中还是未命中GI 采样代码都会执行。并且与高光反射类似着色在一个单独的计算通道中执行使用简化的、统一的材质参数化和着色模型以及视图空间光源聚类过程的结果。图46-11.基于 G 缓冲区生成的间接漫反射光照光线。光线长度是有限的。当光线未命中时预计算的 GI 应用于表面而不是来自命中表面的辐射亮度。这使得该效果既作为光线追踪环境光遮蔽又作为动态间接漫反射光照。46.5 接触阴影传统的阴影贴图可能会因为分辨率不足或阴影贴图偏置而出现阴影痤疮。虽然有缓解这些问题的方法但光线追踪首先是一种低工作量、避免这些问题的方法。图46-12.接触阴影渲染的总体概览。尽管光线追踪阴影能准确解决可见性问题但由于性能原因对于数百个光源来说这可能不是一个可行的解决方案尤其是在需要长光线来捕获大场景中的可见性时。由于阴影贴图在大范围内是一种良好且快速的解决方案而光线追踪阴影擅长短光线为什么不两者都用呢结合这些技术可以在高性能下提供出色的图像质量。阴影贴图处理大多数情况光线追踪阴影则填补细节。仅使用非常短的光线进行光线追踪阴影使其速度快并能提供我们可能从阴影贴图中缺失的精度。在《控制》中我们采用了以下方法如图46-12所示为所有投射阴影的光源渲染常规阴影贴图。选择少数光源用于接触阴影然后为它们追踪非半透明可见性。可见性缓冲区在使用前进行降噪在第46.6.2节中讨论。最后使用阴影贴图和降噪后的接触阴影进行着色。46.5.1 光源选择在《控制》中使用视锥体体积剔除光源光照是延迟的。在主光照通道期间每个像素会记录最大强度的点光源或聚光灯。然而并非所有像素都会被任何点光源或聚光灯覆盖。在这种情况下像素留空。这些光源在主光照中被忽略但稍后会通过光线追踪接触阴影和阴影贴图的可见性信息添加进来。为最大强度缓冲区中的光源如果有追踪接触阴影。图46-13显示了最大强度光源缓冲区的示例。黑色区域表示这些区域没有任何点光源或聚光灯的光照。图46-13.聚光灯索引存储在红色通道点光源索引存储在绿色通道。这些光源并不总是覆盖整个屏幕通常只有少数几个不同的光源占主导地位。46.5.2 追踪接触阴影追踪阴影是一个简单的任务从当前像素位置向选定的光源发射一条世界空间光线。为了获得软阴影需要使用光源半径内的偏移量来抖动光线方向。点光源和聚光灯都被视为球形光源。这将产生柔和的半影但代价是产生噪点最终在用于光照之前需要降噪。或者可以追踪更多光线但每个像素一条光线结合设计良好的降噪器以及短光线距离也能产生良好效果。接触阴影的降噪将在第46.6.2节讨论。首先对蓝噪声纹理进行采样来计算光线方向偏移量。该纹理提供一个随机种子用于对同心圆盘进行采样。将圆盘上的样本乘以光源半径并使用光源的正交基来抖动光线方向。接触阴影仅在相机视锥体内为不透明表面追踪。命中与未命中着色器返回二进制可见性和命中距离两者都被存储。来自点光源和聚光灯的可见性被写入同一缓冲区的不同通道。比较图46-14中的结果。在《控制》中光线追踪阴影仅针对最多两个最重要的光源计算使用有限的光线长度和每条像素一条光线。这在性能和视觉影响方面效果非常好。图46-14.左图基本阴影贴图的使用。右图接触阴影增加了地板和其他物体之间接触处的细节。46.6 降噪降噪是光线追踪效果中必不可少的部分。许多光线追踪效果基于蒙特卡洛积分这本身就会产生噪点结果。通常我们用性能换取质量但一个好的降噪器可以让我们两者兼得。我们的降噪过程首先为每个光线追踪效果实现一个光线生成着色器。例如用于抖动光线方向的随机噪声样式会影响最终的噪点图像看起来如何。选择不当的噪声生成器会在最终图像中留下可见的、可识别的模式。此外在着色光线时丢弃噪点源的能力可以简化降噪任务。例如避免在粗糙表面上使用非相干光线并使用平滑的、预计算的数据而不是稀疏采样的光线可以产生更清晰的图像更容易降噪。然而并非所有噪点源都可以避免不同的效果可能需要不同的滤波器。在《控制》中为反射、间接漫反射光照和接触阴影实现了单独的降噪器。¹ 每种效果的输入数据略有不同可以从不同的滤波方法中受益。《控制》中使用的滤波方法受到时空方差引导滤波的启发 [5]。46.6.1 反射和间接漫反射光照的降噪器为了减少反射输入中的噪点源我们在粗糙表面上使用更短的光线在光线未命中时采样预计算的 GI 数据并将透明反射视为镜面反射。光线方向使用基于世界空间位置的随机噪声进行抖动以减少屏幕空间相关性。类似地间接漫反射光照的光线生成着色器使用预计算的 GI 数据来最小化噪点并使用上述方案扰动光线方向。反射和间接漫反射光照降噪器最为相似并且共享大部分相同的代码但仍然作为独立的通道执行。作为第一步它们都有一个飞蚊滤波器嵌入在时域通道中它将钳制尖峰状的高强度值。飞蚊钳制是通过采样源纹理强度并用来自较低 mip 级别的平均亮度来钳制它实现的。我们为反射和间接漫反射光照使用不同的 mip 级别和钳制常数。强度钳制之后使用前一帧的空间滤波结果执行常规的时域累积如代码清单46-3所示。对于反射降噪器我们应用方差裁剪 [4]。代码清单46-3. 时域滤波器。 void TemporalFilter(...) { // Read source: reflections or indirect diffuse illumination. float3 finalColor sourceTexture.Load(uint3(position , 0)); // Filter high frequencies using lower mip levels of sourceTexture. finalColor clampIntensity(finalColor); float2 previousUv reprojectToPreviousFrame(position); float3 previousColor historyColor.SampleLevel(sampler , previousUv , 0.0f); float temporalWeight user-defined -maximum ; temporalWeight * isDepthValid(currentDepth , previousDepth); temporalWeight * getVelocityWeight(currentUv , previousUv); #if RELECTIONS previousColor doVarianceClip(previousColor); #endif finalColor lerp(finalColor , previousColor , temporalWeight); targetTexture[position] finalColor; }时域滤波器之后是空域滤波器。空域滤波的次数取决于效果。对于反射空域滤波器每个方向水平和垂直执行两次对于间接漫反射光照执行三次。空域滤波器采样当前像素颜色并取四个具有偏移量参见代码清单46-4中的extendOffset的样本每次滤波都会扩展该偏移量 [5, 6]。每个样本都有一个应用于它的权重该权重根据我们降噪的效果而变化。权重计算之后对所有样本进行加权并解析最终的降噪后颜色。在最后一次空域滤波迭代中我们将使用第一次时域累积的结果再次进行时域累积。空域通道之后我们向降噪后的信号添加一个菲涅尔分量——对于反射和间接漫反射光照都是如此。解调菲涅尔分量为我们提供了一个更干净的去噪信号。代码清单46-4. 空域滤波器。 void SpatialFilter(...) { // Initialize pixel position , pixel UV, previous position , etc. ... float currentWeight 1.0f; float sampleWeights[4] { 1.0f, 1.0f, 1.0f, 1.0f }; float3 currentColor sourceTexture.Load(position); currentWeight * getWeightsUsingBilateralFilter(...); currentWeight * getWeightsUsingFilterWeights(...); for (int i 0; i 4; i) { // Samples from neighborhood sampleColor[i] sourceTexture.Load(position extentOffset(i 1)); // Apply a number of filters depending on // which effect we are denoising. sampleWeights[i] * getWeightsUsingFilterA(...); sampleWeights[i] * getWeightsUsingFilterB(...); sampleWeights[i] * getWeightsUsingFilterC(...); ... } // Resolve samples. currentColor * currentWeight; for (int i 1; i 4; i) { sampleColor[i] * sampleWeights[i]; } float3 finalColor currentColor; for (int i 1; i 4; i) { finalColor sampleColor[i]; } // Normalize with total weight from this pass. finalColor * inv(currentWeight length(sampleWeights)); // Do one more temporal pass. if (isLastIteration) { // Same logic as before ... finalColor lerp(finalColor , previousColor , temporalWeight); } targetTexture[position] finalColor; }权重选项我们的空域滤波通道使用多种方法来对样本进行加权。除了时域累积我们还使用一组取决于效果的滤波器。我们已经为我们的应用程序调整了这些权重。我们主要是根据各种表面属性和手动挑选的常数来验证或加权输入数据。在反射降噪器中我们使用双边权重见第46.6.1节、滤波器权重 [5]、基于命中距离的权重、方差裁剪和基于平滑度的权重。由于我们主要使用预计算的 GI 数据来处理粗糙反射这减少了输入噪点我们可以使用表面属性例如深度、法线和粗糙度对反射进行双边滤波。间接漫反射信号比反射信号噪声大得多。因此我们需要在滤波时采取更宽松的方法。我们主要希望限制我们接受数据的距离范围而不是严格地根据表面属性丢弃数据。间接漫反射降噪器使用双边权重、滤波器权重和基于命中信息的权重。双边权重双边权重的计算方法是从深度、法线、平滑度和材质 ID 缓冲区中取四个具有偏移量的样本。然后从每个样本集计算一个权重最后将这些权重组合成一个权重并返回如代码清单46-5所示。代码清单46-5. 双边权重。 glsl void getBilateralWeight(...) { float depth0 depthBuffer.Load(position); float depth1 depthBuffer.Load(position offset1); float depth2 depthBuffer.Load(position offset2); ... // Repeat for other buffers. float4 weightDepth abs(depth0 - float4(depth1, depth2, depth3, depth4)); ... // Repeat for other sample sets. float4 finalWeight 1.0f; finalWeight * max(user-defined -min, saturate(1.0f - weightDepth * user-defined -multiplier )); ... // Repeat for other weights. return finalWeight; }降噪后的反射和间接漫反射光照结果分别如图46-15和图46-16所示。图46-15.有噪点的左图和降噪后的右图反射缓冲区比较。图46-16.有噪点的左图和降噪后的右图间接漫反射光照缓冲区比较。46.6.2 接触阴影降噪器接触阴影光线生成着色器做了两件事作为降噪的准备它使用蓝噪声作为光线方向偏移的随机种子并且它输出hitT以及可见性信息。随机种子用于采样同心圆盘。将圆盘上的随机样本乘以光源半径用于偏移光线方向。hitT值稍后用于降噪。阴影降噪器如代码清单46-6所示的构建方式与反射和间接漫反射光照的降噪器类似。首先执行时域滤波器如果重投影成功则将前一帧的可见性数据与当前帧的可见性累积起来。时域滤波器之后是空域滤波器它是为阴影量身定制的我们知道哪个光源击中了哪个像素并且可以访问与该光源相关的信息。这些信息可用于高效地对阴影进行降噪。代码清单46-6. 阴影滤波器。 glsl LightData centerLight; // Fill LightData struct. ... centerLight.sigma GetRadiusInWorld(worldPos , centerLight.worldPosition , lightRadius , centerLight.hitT) * 0.6666f; for (int i 1; i filterSize; i filterStepSize) { samplePosition position int2(-i,-i) * filterDirection; DenoisePixel(denoisedVisibility , sumOfWeights , centerLight , samplePosition , currentDepth , positionInWorld); samplePosition position int2(i, i) * filterDirection; DenoisePixel(denoisedVisibility , sumOfWeights , centerLight , samplePosition , currentDepth , positionInWorld); } denoisedVisibility / sumOfWeights; denoisedVisibility saturate(denoisedVisibility); // Write out denoisedVisibility.《控制》中使用的空域滤波器深受 Story 和 Liu [2] 的 Gameworks 空域阴影滤波器的启发。该滤波器是可分离的每个方向执行一次。空域滤波器的思想是检查每个像素确定哪个光源可能产生了阴影将光源半径近似投影到被阴影覆盖的表面上并使用该信息来加权相邻样本。对于样本加权我们使用了高斯权重其 sigma 根据世界空间中的光源半径计算得出 [3]。图46-17.可以使用 hitT 将光源半径投影到被阴影覆盖的表面上。首先初始化一个滤波器内核检索当前像素的光源数据我们称之为中心像素。我们使用光源的世界空间位置、半径、索引、可见性、hitT和高斯偏差来准备内核glslstruct LightData { float3 lightPositionInWorld; float hitT; float lightRadius; float sigma; float visibility; uint lightIndex; };高斯偏差根据投影的光源半径计算见图46-17。可以使用表面位置、光源位置和hitT来计算投影的光源半径。请参考代码清单46-7。代码清单46-7. 投影的光源半径。 float GetRadiusInWorld(float3 surfacePosition , float3 lightPosition , float lightRadius , float hitT) { float3 surfaceToLight lightPosition - surfacePosition; float3 hitPosition surfacePosition hitT * normalize(surfaceToLight); float hitDistance length(lightPosition - hitPosition); float lightHalfFov asin(lightRadius / hitDistance); return tan(lightHalfFov) * hitT; }初始化滤波器内核后我们可以开始采样。我们使用半径为8个像素的内核并采取步长为2个像素的步进。在每一步我们调用DenoisePixel它在像素处采样可见性并根据深度和光源索引进行验证。参见代码清单46-8。如果可见性数据来自与内核中心相同的光源我们可以使用它。代码清单46-8. DenoisePixel。 glsl void DenoisePixel(inout float denoisedVisibility , inout float sumOfWeights , LightData centerLight , LightData centerPoint , uint2 sampePosition , float centerDepth , float3 positionInWorld) { // Check point light and spotlight indices at sample pixel. uint sampleLightIndex GetLightIndex(sampePosition); float sampleDepth GetDepth(sampePosition); // Sample raw contact shadow buffer. float sampleVisibility GetVisibility(sampePosition); // Check sample validity in depth. uint isValid IsValid(sampleDepth , centerDepth) ? 1 : 0; float sampleWeight 0.0f; float distanceSampleToCenter length(positionInWorld - mplePositionInWorld); // We can use this sample if its visibility data is // from the same light as the center. if (sampleLightIndex centerLight.index isValid) { // Calculate Gaussian weight in world space. sampleWeight GetWeightInWorld(distanceSampleToCenter , centerLight) } denoisedVisibility sampleVisibility * sampleWeight; sumOfWeights sampleWeight; }仅当样本的信息与中心像素样本来自同一光源时我们才能使用该样本的信息。理论上在创建最大强度光源缓冲区时如果场景中有大量光源每个像素可能从不同的光源获得贡献。在这种情况下我们将无法降噪因为每个像素中的所有可见性信息都来自不同的光源。幸运的是这对于《控制》来说不是常见的光照设置。对于每个有效样本我们计算样本与内核中心在世界空间中的距离并用它来计算高斯权重glsl float GetWeightInWorld(float length, float sigma) { if (sigma 0.0f) return (length 0.0f) ? 1.0f : 0.0f; return exp(-(length * length) * rcp(2.0f * sigma * sigma)); }当所有滤波步骤完成后我们输出降噪后的可见性并将其用于光照。比较图46-18中显示的结果。图46-18.左图有噪点。右图降噪后。上图聚光灯阴影。中图几个点光源的阴影。下图合并后的阴影缓冲区。46.7 性能光线追踪性能因帧而异具体取决于屏幕被透明表面覆盖的程度以及屏幕被选中用于接触阴影的光源覆盖的程度。我们在 NVIDIA RTX 3090 上以 2560 × 1440 像素的分辨率捕获了两个具有代表性的帧以举例说明光线追踪性能。在我们的示例中如图46-19所示所有光线追踪效果在总帧时间 19 毫秒中占用约 8.9 毫秒在 16 毫秒的总时间里占用 7.1 毫秒。这个时间被划分为光线追踪、命中结果的着色和降噪。这是帧时间的很大一部分但请记住光线追踪能够实现其他方式不可能实现的效果并为场景增添了大量真实感。图46-19.在 NVIDIA RTX 3090 上以 2560 × 1440 像素分辨率捕获的两个帧用于性能测量。表46-1显示了光线追踪效果各个通道的时间。如我们所见降噪通常花费的时间几乎与光线追踪一样长甚至更长。追踪性能在很大程度上取决于光线数量和帧的几何内容而降噪仅取决于渲染分辨率。在降噪上花费与光线追踪一样多的时间听起来可能很多但实际上它在性能和图像质量之间提供了良好的平衡。如果我们增加光线数量性能将受到显著影响。从每条像素一条光线增加到两条通常会使追踪时间加倍但可能对图像质量的提升影响不大。我们认为相对较低的光线数量与特定领域的降噪器相结合是目前图像质量和性能之间的最佳平衡点。通道帧1 时间 (ms)帧2 时间 (ms)加速结构构建 (异步)0.61.0反射光线追踪1.01.4反射着色1.41.1反射降噪0.80.8透明反射光线追踪0.80.3透明反射着色0.70.1间接漫反射光线追踪0.80.7间接漫反射着色0.80.6间接漫反射降噪1.11.0接触阴影光线追踪0.80.4接触阴影降噪0.70.7总成本8.97.1表46-1.在 NVIDIA RTX 3090 上以 2560 × 1440 像素分辨率下不同光线追踪效果所花费的帧时间。加速结构构建时间未包含在总成本中因为它是异步执行的。46.8 结论在本章中我们展示了如何在混合渲染器中实现光线追踪反射、近场间接漫反射光照和接触阴影。通过仔细调整输入信号并设计特定领域的降噪器我们已成功地将这些效果适配到适合已发行游戏标题的视觉质量和性能水平。我们对《控制》的首次发布感到非常满意。我们证明了光线追踪可用于增强现有的渲染管线该管线也部署在没有硬件加速光线追踪的平台上。无需太多麻烦我们就能够呈现出传统光栅化技术无法实现的视觉细节和准确性。这些效果及其实现方式与游戏完美契合。我们期待光线追踪在未来项目中的应用。参考文献[1] Aalto, T. The latest graphics technology in Remedy’s Northlight engine. Presentation at Game Developers Conference,https://www.dropbox.com/s/ni32kzk0twjzwo0/RemedyRenderingGDC18.pptx?dl0, 2018.[2] Gruen, H., Roza, M., and Story, J. Shadows of the Tomb Raider: Ray tracing deep dive. Presentation at Game Developers Conference,GDC Vault - Shadows of the Tomb Raider: Ray Tracing Deep Dive (Presented by NVIDIA), 2019.[3] Mehta, S., Wang, B., and Ramamoorthi, R. Axis-aligned filtering for interactive sampled soft shadows. ACM Transactions on Graphics, 31(6):163:1–163:10, 2012. DOI: 10.1145/2366145.2366182.[4] Salvi, M. An excursion in temporal supersampling. Presentation at Game Developers Conference, 2016.[5] Schied, C., Kaplanyan, A., Wyman, C., Patney, A., Chaitanya, C. R. A., Burgess, J., Liu, S., Dachsbacher, C., Lefohn, A., and Salvi, M. Spatiotemporal variance-guided filtering: Real-time reconstruction for path-traced global illumination. In Proceedings of High Performance Graphics, 2:1–2:12, 2017.[6] Smal, N. and Aizenshtein, M. Real-time global illumination with photon mapping. In E. Haines and T. Akenine-Möller, editors, Ray Tracing Gems, chapter 24, pages 409–436. Apress, 2019.
返回列表