前言
在上一篇中队虚幻5引擎的Lumen全局光照粗读进展到了
软件光追和表面缓存的部分,这周会从硬件光追的部分开始。 开头简单谈谈从读这些资料我个人简单总结出的UE引擎的“高精度间接光照魔法”的一些因素:
首先,通过一些手段来合并渲染调用,例如虚拟纹理(及类似结构),例如网格LOD整体化(Nanite框架)——最大限度避免机器渲染指令的切换与绑定带来的影响
其次,设计细节上充分利用GPU连续处理与并行处理方面的优势,例如在一切需要对数据作压缩的地方作压缩,以保证连续性;在追踪步骤时还能通过前缀和(prefix sum)来兼顾并行与顺序
最后,对极值情况(例如抖动的树叶)作一些取舍,必要时用一些trick来避免破坏整体方案
并且,渲染技术方案往往是一个行业内不断互相优化的过程。文末会附一篇它们的参考引用,其中可以看到究竟集成了多少前人的智慧。
本文还是以翻译原文PPT页及解说稿为主,打星号的部分则是我个人的补充。这是两篇中的下篇,相比上篇来说理论信息的密度会低一些,图片和数据会更多,可能阅读负担就没有那么大了。
1 硬件光线追踪
动机
我们在使用硬件光追时主要关注几方面:
概述
让我从列出我们最初的硬件光追和Lumen集成的提纲开始:
我也将介绍一些我们系统中的不透明度描述(representing opacity)的经验性方案,并讨论Lumen的GPU-driven管线如何改变了硬件光线追踪的调用。
我也将着重介绍在“The Matrix Awakens”案例中遇到的遍历相关的复杂度(traversal-related complexities)问题,例如整合Nanite的备选网格并将追踪距离扩展到远场(far-field)。
最后,我会描述UE5中完整的硬件光追管线以作为总结。(*后续硬件光追部分会缩写为HWRT)
*其实原文使用traversal一词的时候是兼顾遍历与追踪两个含义的,但在更接近“调用”与“执行”的地方我还是更多翻译成遍历。
尝试HWRT反射
Lumen的HWRT从对反射的尝试开始。对于这一任务,看起来最合适的方法使从UE4的光线追踪反射模型开始。通过集成UE4的光线追踪反射,产生了一个对于动态材质和光照计算的快速方案——这在最初很明朗,但其中的缺点需要在我们开发整合方案之前被指出。
*这里没有UE4的做法的上下文,还是要结合缺点及改进的部分一起看。
尝试HWRT反射
其中最值得注意的问题是,UE4的反射模型缺少适当的高光剔除(specular occlusion)。图中的镜像球清楚展现出了这一情况,其中(不投影的)天空光源使内部结构染上了一抹不自然的蓝色。
图中标出位置也缺乏高光剔除。
表面缓存管线
与此同时,我们也意识到完全放弃动态光照计算,并全部依赖表面缓存来计算会是一个好的尝试。
我们也决定了为管线增加一些额外的限制,并通过强制所有BVH加速结构中的物体都是不透明,来排除any-hit shader的使用。(*这里应该是避免透明类检测拖慢硬件光追过程)
For this model, we replaced the set of material-dependent closest-hit shaders with a single closest-hit shader, and this shader only fetches only the data necessary to extract the geometric normal and surface cache parameterization. The ray-generation shader then applies lighting as a normal-weighted surface-cache evaluation.
对于这一模型,我们以单个的closest-hit shader替换了原先是一组的材质相关的closest-hit shader——这个shader只抓取得出几何体法线和表面缓存参数需要的数据。之后通过射线生成shader提交光照结果,并通过一个法线作为权重的表面缓存计算光照(normal-weighted surface-cache evaluation)。
我们把这一管线称为表面缓存管线(Surface-Cache Pipeline)。
表面缓存光照(未应用时)
表面缓存光照
利用表面缓存,我们能在一个光照计算过程中得出直接和间接光照。这使我们得以克服之前提到的天空光照问题。(*两图前后对比)
也能纠正前面提到的高光剔除问题。(*两图前后对比)
表面缓存的有效载荷
With the aggressive optimizations to the Surface-Cache pipeline, we created a new payload structure to minimize bandwidth pressure. The high-quality payload used in the UE4 model requires 64-bytes to store GBuffer-like parameters for dynamic lighting. This includes parameters such as BaseColor, Normal, Roughness, Opacity, Specular, and more. In comparison, the Surface-Cache payload only requires 20-bytes to store the necessary parameters for a Surface-Cache lighting lookup.
通过表面缓存管线中的激进优化方案,我们创建了一个新的有效载荷(payload 后文保持原词)结果以最小化带宽压力。UE4模型中采用的高质量的payload需要64字节以存储类似GBuffer的参数以用于动态光照——它包含了例如基础色、法线、粗糙度、不透明度、高光等;相对的,表面缓存的payload只使用20字节来存储需要的参数,用于表面缓存光照的查找。
表面缓存的payload参数组在图中下方列出。对于表面缓存管线来说材质ID的字节数指定原本是不需要的,不过后续我会解释其中的原因。
表面缓存管线
表面缓存管线也简化了shader绑定表(binding table)的结构。通过这个新模型,绑定循环不再需要获得与材质有关的数据资源。对于大量的实例来说,这能带来可观的GPU时间的节省。
不幸的是,我们无法完全去除整个绑定循环,因为顶点和索引缓冲绑定(index buffer bindings)在DXR中重构表面法线时仍是需要的,而UE5目前也没有使用无绑定的(Bindless)着色资源。
*关于Bindless,常被和虚拟纹理一起拿来谈。简单来说Bindless是一种新的提交资源描述符的方式,能带来渲染时更灵活的内存结构组织方式。
混合管线?
After constructing the two models, we anticipated that mixing the two strategies could provide a natural scaling control to govern the performance vs quality tradeoff. We accomplished this by conditionally separating surface-cache terms, such as albedo, direct lighting, and indirect lighting depending upon the desired level of dynamic evaluation. This also presented a mechanism to incorporate the surface-cache indirect lighting with the dynamic evaluation of UE4 model, eliminating a fundamental problem with unshadowed SkyLight.
在构造了两套光照模型后,我们预期中混合这两种策略可以提供一种自然的规模缩放,以平衡性能和质量的tradeoff。我们通过有条件地拆分表面缓存的表达式来实现这一点,例如漫反射系数、直接光照、以及基于设计好的层级来计算的间接光照。这也提出了一种机制,以合并表面缓存的间接光照与UE4的模型,并消除了无阴影的天空光源方面的基本问题。
尽管我们能分别控制是否需要动态计算材质或光照,我们发现局部动态计算的开销和全部动态计算一样高。
光线追踪模型
基于这一发现,我们只为艺术家提供了两种光照选项——表面缓存模型完全按前文所述的方式来实现,而Hit-Lighting模型代表整合了表面缓存间接光照的UE4模型。同时,我们也只开放Hit-Lighting模型用于反射。
需要着重指出的是,Hit-Lighting模型实现了之前发布过的Sorted-Deferred追踪管线的修改版。由于这项优化需要导出材质ID,因此这是为什么表面缓存的payload需要它的原因。
*Sorted-Deferred tracing pipeline原始方案,网上找到的资料源自《RAY TRACING IN FORTNITE》这篇文章——后来被收录进了《Ray tracing gems》书中。文末会附一篇PDF地址。
Hit-Lighting管线
This allows us to repurpose the Surface-Cache tracing stage as the prerequisite to the Hit-Lighting pipeline. It also grants us the flexibility to optionally invoke dynamic evaluation on a per-ray basis. For instance, we have experimented with this idea to optionally invoke Hit-Lighting on meshes which have no Surface Cache parameterization.
这允许我们重新调整表面缓存阶段的用途,使它成为Hit-Lighting管线的先决条件(prerequisite)。这也为我们提供了有选择地调用逐射线动态光照计算的灵活度。例如,我们已经尝试了有选择地在没有表面缓存参数数据时,在网格上调用Hit-Lighting。
图中展示的就是完整的Hit-Lighting管线,并添加了初次执行表面缓存追踪pass后的排序和重新追踪的步骤。
在(源自UE4模型的)Lyra的例子中,我们可以看到Hit-Lighting为镜像球上的骨骼化(skeletal meshes)的网格提供了直接光照反射。由于骨骼化的网格没有表面缓存参数,这一效果只能通过Hit-Lighting模型来实现。
*之前也介绍过表面缓存几乎无法精确处理动态物体。这里的skeletal meshes说的比较绕,其实就是可动的角色。
整合不透明度
表面缓存管线在处理局部透明的几何体时提出了额外的挑战。如前所述,当采用表面缓存模型时,我们严格地把BVH中的所有几何体都视为完全不透明——这样做的原因是调用any-hit着色会导致客观的运行时开销。(*原文用了penalty 这个词,直译是惩罚)
一种尝试克服这一问题的方向是不透明遍历(opaque traversal),我们启用了两个材质相关的策略。对于透光(translucent)材质,我们选择完全跳过这类网格;对于alpha遮罩(alpha-masked)材质,我们计算表面缓存的不透明度并跳过值在50%以下的网格。(*注意这里是跳过网格而不是丢弃像素)
Since we chose to omit an any-hit shader, we must iteratively traverse through the scene in the ray-generation shader whenever we encounter a partially opaque surface. This iteration count is governed by a MaxTranslucentSkipCount shader parameter.
由于我们选择省略了any-hit shader,我们必须在射线生成shader中迭代地遍历整个场景——在遇到任何一个局部透明表面时。迭代次数受MaxTranslucentSkipCount参数控制。
MaxTranslucentSkipCount参数
图中的例子展示了MaxTranslucentSkipCount参数的应用——当处理一个局部是部分透明的几何体时。(*图中左侧是Lumen Reflection管线,右侧是光栅化管线)
Alpha遮罩
图中的例子展示了计算表面缓存中的不透明度,并在遍历时重构几何体的alpha mask的例子。
派遣调度
Lumen的GPU-driven管线需要额外加入UE4中不具备的派遣控制。相比于直接操作屏幕空间像素,Lumen的pass的计算基于探针(probe)、表面缓存纹素(texel)、屏幕块(tile)。在许多实例中,硬件光追是作为第二级的追踪类型——以备选技术的规格来运行。基于这些原因,本质上就是我们在支持的各处都利用间接派遣。因此,Lumen在PC上更倾向于DXR 1.1的语法。(semantics,直译是语义学)
We should also point out another useful feature in DXR 1.1: Inline ray tracing. The RayQuery interface avoids the complexity of the shader binding table and this allows for the hardware traversal in standard compute and pixel shaders. Utilizing ray queries also offers the compiler significant opportunity to optimize. In the ray-generation case, it is strongly recommended to minimize the amount of "live state" spanning a TraceRay() call. With Inline ray tracing, the compiler can minimize this without developer intervention.
我们也需要指出DXR 1.1中的另一个有用的特性,串联射线追踪(Inline ray tracing)。其中的RayQuery接口避免了shader binding table的复杂度,并支持标准计算中的硬件遍历以及像素shader。利用射线查询也为编译器带来了重要的优化机会:在射线生成时,最小化一个TraceRay() 调用所贯穿的"live state"数量是被强烈建议的;而通过inline ray tracing,编译器可以在不需要开发者干预的情况下最小化这个值。
Except for the need to supply mesh-varying vertex and index buffer data to hit-group shaders, the Surface-Cache pipeline could use inline ray tracing. This auxiliary buffers are only a requirement for PC; however, as console ray tracing intrinsics already provide access to the geometric normal as part of the ray-tracing Hit structure. Because of this, we actually do leverage inline ray tracing on consoles and benefit from a noticeable speedup on certain platforms. To learn more about this console specialization, please see Aleksander and Tiago's talk.
除了需要支持网格变化的顶点和索引缓冲数据(用于hit-group shader)之外,表面缓存管线也可以使用inline ray tracing。这些辅助的缓冲数据只在PC上是需要的;而在主机上内建的光线追踪架构中,已经提供了对几何体法线的访问——作为光追命中结构(Hit structure)的一部分。因此,我们在主机上利用inline ray tracing在多个平台获得了客观的速度提升。对于主机专题优化的部分,可以参照Aleksander和Tiago的讲座。(*由于主机开发隔得比较远,我短期内可能不会读到这篇讲座,有兴趣可以按图中的出处搜索)
2 整合案例——The Matrix Awakens
*虽然这里拆分了一节,但原文语境中主要还是在说硬件光追。
矩阵觉醒
“The Matrix Awakens”是一个包含了很多高难度挑战的用以验证光追模型的UE引擎实验。开放世界场景对大量的实例数量提出了要求,它可以轻易地超出我们最顶层加速结构的重构建时间。场景中的大量活动的汽车和行人,也需要大量对于底层加速结构的动态改变(refits 改装)。汽车油漆和玻璃材质也不能离开镜面反射(以表现其真实性)。
DEMO的目标发布平台是Xbox Series X/S和PS5。这些主机有比较不成熟的光追支持,有着相对更低的算力以及更慢的遍历速度——相对于高端PC来说。然而,主机API提供了极大的光追管线灵活度,使其可以发挥我们的优势。例如,对静态网格的加速结构构建可以被预构建和流式加载,显著地降低每一帧中改变底层加速结构的时间。对于更多主机平台优化的细节,仍是再次推荐Aleksander和Tiago的讲座。
不幸的是,DEMO中的材质复杂度使得对于Hit-Lighting模型的依赖变得不可行。伴随着高指令数,我们的主要材质调用了数十种虚拟纹理的获取(fetches)。动态材质和光照计算无法有很好的性能。起初我们对于调用Hit-Lighting来支持动态物体有所期待,但我们轻易地就耗完了性能预算。
图中的耗时报告显示了在PS5上调用Hit-Lighting(计算我们的复杂材质)的惊人的计算开销。有趣的是,Hit-Lighting也没有带来实质上的质量提升(substantial qualitative benefit)。这是由于我们的DEMO中对于天空光源强依赖——而它在两种管线中都无法从表面缓存中获益。
通过Nanite进行追踪
Matrix Awakens中的绝大多数资源是通过Nanite渲染的。这立刻产生了一种基于硬件光追的并发症——因为我们的加速结构没有能力支持原生的Nanite几何体精度。这其中有很多理由,我着重列出了其中一部分(图中展示了,例如:底层加速结构BLAS的存储太大,顶点和索引缓冲的提交问题等)。不过最简单的回答是,高质量的Nanite光追支持还是一个实际的研究区域(未完成工业化)。
Instead, we must make some concessions with approximate geometric representations. We use Nanite fallback meshes as simplified representations of the rasterized geometry and store them in bottom-level acceleration structures. These fallback meshes bring their own set of challenges, as they do not provide any topological guarantee with the base mesh.
相对的,我们必须采用近似的几何描述来做出一些让步。我们使用Nanite的后备网格作为简化的栅格化的几何体的描述形式,并将它们保存在底层加速结构中。这些后备网格也带来了其自身的挑战,因为它们并不能提供基本网格能保证的拓扑关系。
直接从GBuffer追踪提出了一些明显的挑战——当与Nanite后备网格共同运作时。采用近似形体产生了潜在的自相交问题,无法被传统的射线偏移量(bias)轻易克服。
避免自相交
图中展示了DEMO中渲染车辆时常见的一种自相交故障。我必须手动关闭屏幕空间追踪,以展示仅依赖光线追踪场景时带来的故障现象。(*图中就展示了应该有反射却没能正确追踪射线的情况)
避免自相交
图中展示了叠加到LumenScene(场景编辑模式)的截图,以提供一个待解决的几何体错配问题的参照。
避免自相交
Ray tracing against multiple geometric levels-of-detail is not a new problem, however. Tabellion and Lamorlette presented a solution for this issue back in 2004 [Tabellion et al 2004]. Unfortunately, a proper implementation was too expensive for our budget. Instead, we modified our traversal algorithm to first cast a short ray, to some defined epsilon-distance, ignoring back faces. Only after successfully traversing this distance did we cast a long ray, without any sidedness properties. This figure illustrates the process.
然而,对于不同几何体LOD层级的光线追踪不是一个问题。Tabellion和Lamorlette在2004年就提出了一个解决方案。不幸的是,一个正确的实现对于我们的性能预算太说太昂贵了。因而,我们修改了遍历算法以在最初发出一个短射线,以某些预定义的epsilon距离(作为最大距离),并忽略网格背面。仅在成功通过这段距离后,我们再射出一个长射线——不需要任何偏移型的参数。图中展示了这一过程。(*常有的跳过初始自相交区间的trick)
避免自相交
应用这项技术移除了之前提出的自相交问题。
屏幕空间追踪
Screen traces also overcome self-intersection artifacts, by providing hardware traversal with a starting t-value that aligns with the bounding view frustum.
屏幕空间追踪也能克服自相交故障——通过为硬件遍历提供一个初始的与视锥轴对齐的T值。
屏幕空间追踪
但视锥固有的边缘使这项技术在图像的边缘处变得失效。
屏幕空间追踪+重新追踪
所以最终我们采用了两项技术整合的方案。
处理实例和几何体复杂度
如之前提到的,DEMO中动态实例的大数量对硬件光追模型带来了可观的压力。我们的系统重构了顶层加速架构,并且早期的实验表明我们的模型需要限制100K的实例数量上限。在实验的最后,内容寻址数接近了500K的动态实例——以接近1M为目标。每帧重构建的开销不是唯一的问题,射线遍历也变得开销巨大——由于巨量的三角形数。
显然我们需要控制最大的可追踪距离以达成性能指标。通过将最大追踪距离与射线追踪网格的剔除距离相对齐,我们可以限制(原文是throttle,掐死)这个系统规模以符合我们最初的需求。我们法线追踪要实现这一需求,追踪最大距离要限制在200米。
However, limiting trace distance had a profoundly negative impact on the overall look. Car reflections no longer showed the skyline in the distance. But more importantly, accurate sky occlusion from the global illumination solver was completely gone.
然而,限制追踪距离在整体看来也会产生深刻的负面影响——车辆的反射不再出现在地平线处远距离的汽车上。更重要的是,源自全局光照解决方案的精确天空遮挡也完全失去了。
我们需要为这些问题提出一些补偿方案。
处理实例和几何体复杂度
我们选择利用世界坐标系统以及它的HLOD描述方式以完成这个目标。在HLOD系统中,网格是简化并被合并的,生成了一个远距离合并后的几何体。
In the typical case, HLOD representations are direct replacements for rasterized geometry; however, due to limitations with performant trace distance, we needed to incorporate the HLOD representation before the rasterizer would typically need this substitution. Because of this, we were often presented with two different mesh representations occupying the same space in our top-level acceleration structure.
在典型的情况中,HLOD描述方式被直接替换栅格化的几何体;然而,由于追踪距离上的性能限制,我们需要在光栅化真正需要这一替换前都将其合并入HLOD的描述方式中。因此,我们通常在顶层加速结构中为占据同一空间的网格准备了两套不同的描述方式。
要克服其中的不一致问题,我们接受了(同时存在)两种描述方式,但在射线遍历过程中强制使其互斥——通过一个ray-mask。我们标记出其中属于“远场”的HLOD网格。(*这主要是说不允许同一个位置的网格既采用栅格化网格又采用HLOD,需要在追踪时人为将其规避掉)
*representation单独翻成描述方式(或表达式)没有问题,但结合到上下文会使阅读别扭。其实只需要理解任何3D物体都是以一定的方式存储数据的,这种描述方式代表的是内存中的数据——如三角形、SDF、体积公式等。
远场追踪
射线遍历现在包括了对“近场”和“远场”几何体遍历操作的集合。对于有序遍历,我们首先在一定距离内对近场几何体做追踪——基于我们的网格剔除距离值。在近场未命中的射线会被重新排序并对远场做追踪。
对于阴影射线我们翻转了操作顺序,因为我们不需要保证遍历的顺序了。之所以这么选择的原因是,远场的描述方式包含(比近场)相对更少的实例和更少的几何体,这使得远场遍历的速度会更快。
分级的追踪管线
Incorporation of far-field traces into ordered traversal places a new set of stages into our original hardware traversal pipeline. We add an intermediary compaction step, collating near-field misses into new ray-tiles, and then a subsequent indirect dispatch to trace the ray-tiles against the far-field representation.
将远场追踪整合到顺序遍历的流程,产生了一组新的阶段,插入原本硬件追踪管线中。我们加入了一个中间的压缩步骤,收集近场未命中的射线放入新的射线块(ray-tiles)中;在随后的步骤,对远场物体的描述方式(通过ray-tiles)做间接派遣的追踪。
到目前为止,我暂时的把Hit-Lighting从我们的管线中移除了。
远场可视化
图中的可视化展示了进场和远场几何体之间的界限。不包含表面缓存的几何体也被高亮显示了。
*注意不是图中斜向的线是界限,而是右边不同颜色代表几何体之间的界限——棕色的部分则是没有表面缓存的区域。
Submitting both near-field and far-field representations to the same top-level acceleration structure is NOT ideal. Doing so creates needless geometric overlap. While the ray-mask removes unnecessary traversal, the damage to the top-level acceleration structure build is substantial. Our early experiments with overlaying the far-field representation revealed a show-stopping 44% penalty to all near-field traversal costs.
在同一层级的顶层加速结构中同时提交近场和远场的描述方式是不合适的——这样会产生不需要的几何体的重叠。尽管ray-mask可以移除不必要的射线遍历过程,但对顶层加速结构构建的伤害还是很大。在我们早期的实验中,重叠远场描述方式对所有近场追踪产生了44%的额外开销。
A proper solution would be to support multiple top-level acceleration structures, which would also avoid the need to use the ray-mask mentioned previously. We were hesitant to take on this architectural change mid-production under the already aggressive development schedule, but we needed to do something.
一个合适的解决方案是支持多种不同的顶层加速结构——这也能同时规避之前需要使用ray-mask来过滤射线的问题。在已经很激进的开发计划的产品中期做这项技术调整,其实我们也很犹豫,不过我们确实需要做一些改变才行。
*这段其实不复杂但是我留了原文,可以看到措辞明显是带情绪了的——既体现了好的架构师的个人追求,也解释了为什么有更好的方案但是却改不动的情况。(做过项目内开发的人应该都能理解,无论是不是游戏项目)
按照早期的假设,有人建议:也许对远场几何体应用一个全局的平移偏移(translational offset)能缓解性能上的负担。所以我们尝试了一下这个方案。最终,我们仍承受了在同一个加速结构中整合两类几何体的负担,但偏移值确实显著缓解了性能上的开销。
*遇事不决转trick,性能搞不定转offset。这不是揶揄,而是实时渲染领域很多时候就是如此来提升性能——以牺牲绝对意义上的正确性为前提。
近场
近场+远场
前两张图展示了Lumen的pass中远场追踪应用的过程。由于反射剔除有最重要的视觉影响,你可以看到这对远距离全局光照产生的效果。
图中是另一处展示出远场效果对整体质量提升的对比。
分级的追踪管线
到这里为止,我已经介绍了我们最终的分级追踪管线所包含的必要模块。
这里提供一个简要回顾:
分级的追踪管线
With some reordering of stages, we can minimize the dispatch costs by first resolving all Surface-Cache stages before optionally requeuing results for Hit-Lighting. We do this by cascading through both geometric representations. Hits are then compacted and optionally requeued for Hit-Lighting, while misses cascade to apply SkyLight evaluation.
通过某种对渲染阶段的重排,我们可以最小化派遣的开销——通过首先处理所有表面缓存阶段,之后(可选地)将备选队列重排序以用于Hit-Lighting。我们通过串联两种几何体描述方式来实现这一过程;命中结果之后会被压缩并有选择地重排以用于Hit-Lighting,然后未命中的射线会被串行提交至天空光照计算。
总结
我们的硬件光追模型在UE4模型的基础上改变相当多:
通过表面缓存,我们可以构建一个极简的遍历方案以实现性能目标。
添加一个远场几何体描述方式使我们克服了超量的几何体实例复杂度问题,同时也允许我们显著地增加射线追踪的距离。
我们也展示了如何处理与Nanite网格资源集成时,由于内在几何体LOD错配导致的问题。
3 光追性能
追踪的trade off
如果我们按开销和精度来绘制图标,我们可以看到全局距离场追踪(Global Distance Field tracing)是最快的,但也是最不精确的,因此它需要一个更精确的方案作为补充——例如屏幕空间追踪,或是网格距离场(Mesh Distance Field)追踪。
硬件光线追踪是非常精确的,但也很昂贵,并且也并不真的有方法来降低规模(与开销)。使用Hit-lighting的硬件光追方案甚至超出了图标轴的范围。
*其实这一页比后面的很多纯数据显得更精华。
决定性的因素
使用Lumen的工程必须选择一种追踪方式。软件光追适合需要绝对快速的追踪的项目,它能在(当时的)次世代主机上以60帧运行。使用了kitbashing产生了大量重叠的网格结构的工程也应使用软件光追(这里kitbashing是一款3D工具软件),正如官方例子“Lumen in the land of Nanite”与“Valley of the Ancients”之中那样。
硬件光追适合需要绝对高质量的项目,例如建筑视觉(Architectural Visualization)。以下类型的项目也应使用硬件光追:需要镜面反射的项目,例如“The Matrix Awakens”;或是蒙皮网格需要明显反映到间接光照中的项目(可动角色的反射之类)。
让我们看看两种追踪方式在不同场景的性能表现——从“Lumen in the land of Nanite”开始,它包含了巨量的重叠网格。这是一个为Nanite压力测试而制作的内容,洞穴中表面上的每个点都可能包含上百个重叠网格。使用硬件光追时,射线必须遍历每一个重叠的网格,而软件光追则有一个更快的融合版本。在这个内容上硬件光追的开销是我们无法承受的。
在“Lyra”——UE5的样本游戏中,则是完全不同的情况。这里的几何体基本没有重叠,因此硬件光追的表现非常好。两种追踪方式的开销差距不大,质量差距也不大。因而两种方式都是可取的,最终取决于硬件是否支持。
在“The Matrix Awakens”的DEMO中,两种追踪方式的开销也几乎一样。硬件光追能提供高质量的反射,并支持远距离的GI,因此是更好的选择。
*就像没有大一统的“全局光照”一样,目前也完全还弄不出来大一统的“光线追踪”——应用光追也还需要一事一议。
4 Final Gather
*这一节的大部分内容都涉及光照缓存
,在之前已经读过这个方案的细节分享了,有些页内容都是一样的。不过考虑到本篇阅读的完整性,以及部分内容确实有了迭代,因此每页还是会提供翻译和解释。 如果解决光传播计算的噪声问题?
第三个在实时间接光照中需要解决的基础问题是——光传播计算中的噪声问题。
我们甚至无法负担每帧每像素一射线。图中左侧展示了每像素一根射线会是什么结果,而高质量的室内GI需要上百个高效的采样。
早期实验:预过滤的锥体追踪
One of our early experiments was prefiltered cone tracing. It’s very difficult to implement, but if you can pull it off, tracing a single cone gives you the results of many rays. Cones can be very effective at solving noise and they essentially make the Final Gather trivial.
我们的一个早期实验是预过滤的锥体追踪。这很难实现,不过如果能完美地完成它,那么追踪一个锥体就能提供追踪很多射线的效果。锥体追踪可以使噪声处理变得高效,也就使Final Gather过程变得不重要了。
网格SDF锥体追踪
We implemented cone tracing against Mesh Signed Distance Fields. Whenever the cone intersects a surface, we calculate the mip of the surface cache to sample using the size of the cone intersection, giving prefiltered lighting for cones that intersect.
我们实现了针对网格距离场(Mesh Signed Distance Fields)的锥体追踪。但锥体与一个表面相交时,我们使用锥体相交的尺寸来计算表面缓存的mip,为这一相交提供预过滤光照。
When the cone has a near miss with the surface, it’s only partially occluded so this becomes a transparency problem. We can approximate how much the cone was occluded using the distance from the cone axis to the surface, which we can get from the distance field.
当锥体在近处表面追踪失败时,它只被部分剔除并带来了一个半透明显示的问题。我们可以基于锥体轴到表面的距离来估算锥体被闭塞的程度——这个值可以从距离场中得出。(*这里说的是使用距离场方案可能导致无法正确处理障碍遮挡关系)
这使我们需要合并不同网格的多个局部命中——以无序的方式。我们选择混合权重的顺序无关半透明渲染(Weighted Blended OIT,Order Independent Transparency )策略,这在初始射线经过远距离时会有显著的漏光问题,但在近距离的漫反射间接射线上会有较少的漏光。
从图中右侧可以看出锥体追踪是有效的,因为它不会产生明显的(会产生噪声的)硬边缘。
锥体追踪很适合处理噪声问题,但
锥体追踪在处理噪声问题上是很高效的,但直到最后我们都无法处理全部情况下的漏光问题。我们只能在漏光和过度剔除之间做选择,并且也无法在一个小的距离窗口上解决光照问题。
而且它也只能在软件光追中生效。
蒙特卡洛积分而不是预过滤的追踪
作为替代我们选择使用蒙特卡洛积分方式(Monte Carlo integration,一种用采样策略近似积分的方案)。这使得质量能提到最高的同时,也支持所有类型的光追(硬件、软件),不过这把所有降噪问题留给了Final Gather来处理。
之前的实时方案:辐照度场
*之前的文章也提到过,Irradiance和Radiance这组概念翻译一次后我就尽量保持原文了。
The most popular approach for solving diffuse light transfer is the Irradiance Field. Irradiance Fields trace from probes placed in a volume, then pre-integrate irradiance at the probe position, and then interpolate that to the pixels on your screen.
之前最流行的解决漫反射光传播的方案是辐照度场(Irradiance Field)。辐照度场追踪放置在一个体积(volume)中的探针,在探针的位置对irradiance做预积分,并通过插值得到最终屏幕位置的值。
这项技术的最大问题是irradiance是在探针上计算的,而不是像素上。这会导致漏光或过度剔除的问题,使得探针的放置变得很困难。
并且这也是一项体积描述方式,因此只能承受很低的空间精度,使得GI看起来很“平”。(*例如之前提到的球谐光照SH的低频,或是低精度的cubemap之类)
之前的方案:从像素追踪+屏幕空间降噪
在这个问题谱系(spectrum)的另一端,我们可以从具体的屏幕像素进行追踪,并尝试通过一个屏幕空间的降噪器来解决噪声问题。
这也有其自身的一些问题。降噪器需要一组不相关的射线以得到全面的半球覆盖,这意味着它们是不连续的,并且追踪会很慢。
这使得降噪操作变得非常昂贵——因为它运行在屏幕空间上,并且没有将采样过滤(downsampled filtering)的机会。降噪器也有解除遮挡(disocclusion)时的问题,因为在新显示的区域往往没有足够的样本数量。(*这里基本是逐像素分时降噪方案的常见问题)
需要找到介于两者之间的方案
我们需要找到介于两者之间的方案。我们希望有追踪屏幕像素的精确性——例如间接阴影,但也要能显著的降低开销。
我们的方案:屏幕空间光照缓存
*到这里就和之前一个系列联动上了。
我们的方案是屏幕空间光照缓存( Screen Space Radiance Caching)。我们通过放置在屏幕空间像素的探针进行追踪——这被我们称为屏幕探针。
这是有效的自适应降采样过程。图中左侧的探针是统一化放置的(uniformly 按固定距离),但在有更多细节的几何体处,我们放置了更多探针。
在放置探针并进行追踪后,我们对其radiance做插值,并得出同一平面上其它像素的radiance——这也能限制同一平面上像素插值的漏光问题,虽然本身就很难注意到。
我们会分帧“抖动”放置探针的逻辑格子(grid 这类概念都不翻译成网格,避免混淆),并分帧积累以得到好的覆盖率。
去年(2021)介绍的用深度表达的不透明Final Gather
在去年的讲座中我介绍了Lumen的不透明Final Gather,不过之后我们在“The Matrix Awakens”的技术DEMO中进行了压力测试。它成为了我们其它领域的原型,例如Volumetric Final Gather和纹理空间收集(texture space gather),因此我想分享一些那之后我们的洞察。
The opaque Final Gather has three main parts, first there’s the Screen Space Radiance Cache which is operating at 1/16th resolution in each dimension. It’s backed up by the World Space Radiance Cache, which is handling distant lighting at a much lower resolution. At full resolution there’s the interpolation, integration, the temporal filter and Contact AO.
不透明的Final Gather有3个主要部分,首先是屏幕空间光照缓存——在每个维度上以1/16的分辨率来操作;作为它的后备的是世界空间光照缓存(World Space Radiance Cache),以较低的分辨率处理远距离的光照问题。在全分辨率上计算的有:插值、整合(积分)、分时过滤器和邻接AO。
*resolution很多时候也兼具分辨率和精度的意思,前者更多是一个显示规模概念,后者更多是一个存储计算位数概念,虽然翻译时只能用一个词。
天空光照
图中场景展示了一个在使用Final Gather时,从最远到最近应用光照的例子。这里从天空光照开始。
世界空间光照缓存
之后我们添加世界空间光照缓存,用以处理2米外的光照——图中只有左侧墙壁的部分(用到了)。
*虽然原文稿如此,但配图中显示其它部分例如床和地面也应用了光缓存效果,只是不那么精确。
世界空间光照缓存
让我们看看其中的探针。可以看到墙边的探针非常精确地处理了窗户位置,但右侧的探针(采样)却透过了墙壁。
屏幕空间光照缓存
之后添加屏幕空间光照缓存,它负责抓取所有近处的光照,因此我们能得到间接阴影——但是在降采样的空间中。
邻接AO
最后是加上邻接AO的样子,用来补充我们在屏幕空间光照缓存中因为降采样而丢失的一些阴影细节。
让我们详细看看屏幕空间光照缓存。
光照缓存空间过滤
因为我们在光照缓存空间而不是屏幕空间进行过滤,这使大空间的过滤开销很低。探针空间中一个很小的3x3过滤核能带来屏幕空间过滤48x48的核等同的效果。我们只需要加载探针的位置,而不是所有像素的位置和法线——以计算误差权重。
*之前一个系列也提到过,广义的“过滤”(filtering)可以理解为输入一些东西,并按一定规则输出一些东西。
对入射光的重要性采样
We importance sample the incoming lighting. Importance sampling is only as good as the importance estimate, and we have a very accurate estimate of the incoming lighting from last frame’s Screen Space Radiance Cache, reprojected into the current frame. We can very efficiently find all of last frames’ rays because they’re indexed by direction as well as position in the radiance cache. Where the reprojection fails, like the edges of the screen, we fall back to the World Space Radiance Cache and still have effective importance sampling.
我们对入射光做重要性采样。重要性采样和重要性估计类似,并且我们基于上一帧的屏幕空间光照缓存能对入射光有一个精确的估计——通过重投射(位置)到上一帧的方式。我们可以高效地找到上一帧的所有射线,因为它们是按方向加光照缓存空间的位置组合作为索引。当重投影失败时——例如屏幕边缘处,我们以世界空间光照缓存作为备选,并且仍然能有高效的重要性采样。
*这里不如上个系列介绍的详细,重要性采样主要是解决射线预算有限时的权重问题——基于上一帧对目标入射光位置的定位。
乘积重要性采样
We’re operating in a downsampled space, so we can afford to launch a whole threadgroup per probe to do better sampling. Product Importance Sampling is normally only possible in offline rendering, but we can do it in real-time.
我们在一个降采样的空间中操作,因而我们可以负担为每个探针分配一个线程组以得到更好的采样效果。乘积重要性采样通常只在离线渲染中可用,不过这里我们可以实时执行。
乘积重要性采样比起只对BRDF或光照(方向)做重要性采样更好,比起多重重要性采样(Multiple Importance Sampling)也更好——它能更快丢弃(其它方式中还需要计算的)低权重的追踪方向。
(图中)左侧我们有墙上一个探针的BRDF,使得我们把方向限定在一个有效半球;在中间位置的图中我们知道了基于上一帧的入射光信息,使我们得知了入射光主要来自两个方向。我们将无用的射线重分配到(BRDF和光照的)乘积得出的更重要的方向上。(*结合图可以看出,重分配射线就以超采样的方式,把更高精度存储到缓存中)
图中的白线展示了我们为更重要的方向生成的超采样射线。我们通过在这些方向追踪4倍的射线以提升画面质量,并且不实际产生更多射线。
之后我们看看世界空间光照缓存的细节。
世界空间光照缓存
世界空间光照缓存是处理远距离光照的,它有着更高的方向精度和更低的空间精度。它能解决例如房间的光照都来源自一个小的远距离窗口,但会被较少的射线数错过的问题。我们集成了两种光照缓存方式——通过减短屏幕探针射线的长度,并在未命中的位置重世界空间光照缓存中插值。
世界空间光照缓存有着稳定的误差——基于探针的位置是稳定的,因而(这种误差)是容易被遮蔽的。
稀疏覆盖
The World Space Radiance Cache has sparse coverage, and we use a clipmap distribution to maintain a bounded screen distance between the probes, to make sure that we don’t over-sample, or under-sample.
世界空间光照缓存有着稀疏的覆盖,因而我们使用一个裁剪纹理(clipmap)分布来维护探针之间的一个有边界的屏幕距离,来确保不过度采样或减少采样。
探针数据使用了持存的(内存)分配,因而它们可以跨帧存在。
缓存
我们将上一帧中仍需要用到的探针传递到新的一帧中。之后我们对摄像机移动或场景移动产生的新探针位置进行追踪。
我们也在整个世界空间重新追踪一个子集以传播光照变化。相比于上一年,我们已经通过一个GPU优先队列来进行了改进,以选择一组需要更新的探针——这带给我们在更新整个缓存时也可控的固定开销——即使缓存在被多个输入来源操作。
The Matrix Awakens夜晚模式
“The Matrix Awakens”中有一个实验性的夜晚模式,它是完全被自发光网格照亮的。场景的大部分灯光来自小而亮的电灯泡,因此处理它们的直接光照对我们的GI方法来说绝对是一次性能测试,因为我们不是明确地(explicitly)从光源进行采样(*而是通过探针,但要算的光源很多)。世界空间光照缓存能更精确地解决直接光照问题——基于它在方向上的高精度,以及分时稳定性。
然后我们推进到全分辨率的分时过滤器。
分时过滤器
分时过滤器是用于覆盖我们探针位置和方向上的抖动。我们需要一个稳定的分时过滤,因此我们不能使用邻接对齐(clamp),作为替代我们基于深度与法线的差异来对历史帧的数据做排除。
这带来了稳定的结果,不过也导致了光照缓存更新缓慢的问题——图中就展示了运动物体的鬼影问题。
改进
我们通过探测出“追踪命中了快速移动的物体”以进行改进,并加快更新快速移动物体入射光源相关像素的分时过滤器。
我们也在分时过滤之后应用了最近范围剔除(shortest range occlusion),因此这不会有任何延迟。
不同距离的缓存时机
在大部分场景的动态变化中,我们可以在远距离的光传播上允许更多的延迟。近距离的间接光照不能承受任何延迟,而天空光照的变化可以有很大的延迟。有趣的是我们已经设置并发挥了不同缓存时机中的优势,在我们的Final Gather中已经将radiance划分到不同距离的不同技术中。
*原文的“时机”更好的理解是“适合的场合与情境”——在上面的图表中就是距离。
不同距离的缓存时机
我们可以利用屏幕空间光照缓存中可允许的延迟,对其进行分时累积;而对于世界空间光照缓存,探针可以从前一帧复用,而天空光照可以在很多帧之后再缓慢更新。
透射和雾的GI挑战
*之前也提到过,Translucency更多指玉石、烟尘之类的物体,是不适合直接翻译成“半透明”的。
下面推进到透射物体和雾的GI。对于透射物体我们需要支持任意数量的层,你可以看到如果不从天空光照投影时图中的烟尘粒子是什么样的。(*亮度明显不对)
对于雾我们需要在可见的深度范围的任何位置都计算GI,我们需要在整个入射球体上计算,而不是基于法线的半球。
同时我们也只有1/8的不透明Final Gather的性能预算,因此我们需要一个非常快的方案。
体积Final Gather
Our Volumetric Final Gather covers the view frustum with a probe volume, which is a froxel grid. We trace octahedral probes and skip invisible probes determined with an HZB test.
我们的体积Final Gather通过一个探针体积覆盖了视锥体,被称为一个
视锥栅格(froxel grid)。我们追踪八面体的探针并通过HZB检测跳过不可见的探针。(*之前也说过froxel,是frustum和voxel的合成生造词) 在追踪并查找Radiance后,我们对空间上的radiance做过滤并进行分时累积,以降低因追踪数较少而带来的噪声。
We then pre-integrate into Spherical Harmonic Irradiance and the forward translucency pass, or the volumetric fog pass, interpolates the irradiance.
然后我们将其预积分至球谐函数(Spherical Harmonic)描述的Irradiance,以及前向渲染的透射pass(或体积雾pass)中,并对irradiance做插值。
即使4X4的追踪对于远距离光照也不够
即使通过很大数量的追踪数,我们也无法解决远距离光照的噪声——例如图中完全被天空光照从一个小洞口照亮的洞穴。
我们启用了另一个世界空间光照缓存以用于远距离光照,这能带来高质量的方向精度以及稳定的远距离光照。
透射的世界空间光照缓存细节
要把数据输入这个世界空间光照缓存,我们需要在可见视锥栅格的各处放置探针。之后我们从探针处追踪并预过滤radiance的值到mipmap中。
我们降低从froxel追踪的距离,并且当其未命中时我们从世界空间探针中插值。追踪世界空间探针的射线是从froxel追踪的16倍,以降低走样情况。
We overlap this new translucency Radiance Cache with the Opaque World Radiance Cache, so that it’s many dispatches fit in the gaps and it’s almost free.
我们将这一新的投射光照缓存与不透明世界空间光照缓存重叠,因此其中的很多派遣填充了线程上的缝隙,使其几乎是无开销的。
*基本上要对透射物体做高精度,都需要定制化的开发,泛用方案只能保证光照不穿帮——为之分配的性能预算太少了。例如要叠加复杂水体,那么本身不透明场景的渲染精度可能就要相应调低,不然就会拖慢整体的性能。
5 反射
屏幕空间降噪的随机积分
对于反射我们采用经过屏幕空间降噪的随机积分,基于Tomasz Stachowiak讲座中的内容。
Looking at our pipeline at a high level, first we generate rays by importance sampling the visible GGX lobe, then we trace the rays using our ray tracing pipeline. Then we run our spatial reuse pass, which looks at screen space neighbors and reweights them based on their BRDF. Then we do a temporal accumulation, and finally a bilateral filter to clean up any remaining noise.
从高层级来看我们的管线,首先我们通过对可见的GGX波瓣做重要性采样来生成射线,之后我们通过光线追踪管线来追踪射线。再之后我们执行空间重用的pass,它能查找屏幕空间位置与相邻位置,并基于它们的BRDF重新分配权重。然后我们执行分时累加,并最终用一个双边过滤器来清理残留的噪声。
*随机积分(stochastic integration)是一个深刻的复杂话题,它的目标不复杂,但是计算理论对我来说太复杂了。
*GGX是一种微表面模型(microfacet models),是以函数模型的方式,通过一组系数描述微表面接收光照后的出射角度范围与能量;波瓣则是以图形来描述这种分布的一种方式。更多信息可以去看看Games101,有讲微表面模型的部分。
原始追踪
从“The Matrix Awakens”中看看这些步骤,我们从原始的射线追踪结果开始——这包含了很多噪声。
空间重用
之后通过空间重用降低了一些噪声,但仍然是可见的。
分时累积
之后我们应用分时累积,图中的亮点被显著地减少了,但在非常亮的区域仍然有一些噪声。
双边过滤
我们通过双边过滤消除这部分噪声。
分时抗锯齿(反走样)
*越高清的情况下,Anti Aliasing越不适合翻译成抗锯齿了,因为处理的就不全是锯齿问题。
通过分时反走样,我们进一步消除了噪声。
双边过滤作为最终的努力
The Bilateral Filter is our last ditch effort for when the physically based reuse isn’t enough. We run it on areas that had high variance after the spatial reuse pass, and we force it on at double strength in areas that were newly revealed by disocclusion, which don’t have any temporal history. We use tonemapped weighting in the Bilateral Filter to remove fireflies, which would crush our highlights if used in the spatial reuse pass, but works perfectly here.
双边过滤是我们在基于物理的重用还不够时的最终努力。我们在空间重用后有较大差异的区域执行它,并强制在刚解除遮挡的区域以加倍的强度来执行——因为这些区域没有历史帧数据。我们在双边过滤中应用色调映射(tonemapped)权重分配以消除噪点,如果在空间重用pass中执行这一步可能会破坏高光的点,但在这一步(双边过滤)就能完美生效。
处理不连续情况
在处理反射时其中的一个问题是不连续的射线追踪较慢。当材质的粗糙度达到1时,GGX波瓣变为了漫反射,我们的射线则变得非常不连续。
The roughest reflections from .4 to 1 often cover half the screen and they are a significant optimization opportunity. Then there are the glossy reflections from .3 to .4 which require more directionality but are also quite slow to trace.
对粗糙度在0.4到1之间的反射通常会覆盖半个屏幕,它们是一个明显的优化机会。对于在0.3到0.4之间的光滑表面,由于需要追踪更多方向因而追踪也很慢。
对于粗糙反射
We can solve the incoherency in the roughest reflections by just reusing the work that we did for Diffuse GI. The Screen Space Radiance Cache has enough directional resolution for a wide specular lobe, and we can just resample it, by importance sampling the GGX lobe to get a direction, and then interpolating radiance from the screen probes.
我们可以通过重用在漫反射GI中的那些工作来处理粗糙反射的不连续问题。屏幕空间光照缓存对于一个宽镜面反射波瓣已经有足够的方向精度了,因此我们可以对其重采样——通过对GGX波瓣做重要性采样的方式来得到一个方向,之后从屏幕探针之中插值radiance。
这降低了反射开销——基于算法能跳过多少(原本需要反射射线的)区域,不过在多数场合开销被降低了50%到70%。
对于光滑反射
For that middle range of glossy reflections, the Screen Space Radiance Cache doesn’t have enough directional resolution, but the World Space Radiance Cache does. We shorten the reflection ray, and interpolate from the World Space Radiance Cache on miss.
对于中等范围的光滑反射,屏幕空间光照缓存不能提供足够的方向精度,但世界空间光照缓存能。我们缩短了反射射线,并将未命中的部分从世界空间光照缓存中插值。
这消除了对射线进行丢弃和排序的需要,因为我们通过缩短射线降低了方向上的差异,并且它们已经是基于各自原点位置排序过的了。.
在 “The Matrix Awakens”中,我们可以看到路面反射的开销额外降低了16%。
通过重用的高效透明漆双反射
我们也有非常高效的(基于重用的)透明漆(clear coat)反射。光滑的底漆部分可以重用屏幕空间光照缓存,因此我们只需要从顶层的清漆层追踪新的射线。
基于Tile的管线
我们的反射管线是基于tile的(*tile不特殊解释的话都是指把屏幕分块进行多线程处理)——这允许我们高效地重用漫反射射线,以跳过天空和部分区域。这点很重要,因为我们在管线中有很多派遣——如图中右侧所示,并在追踪管线中有着更多(派遣数),能这样只操作屏幕上需要的区域会有很大性能提升。
我们实际上会多次执行反射管线——视场景情况而言。其中至少对于不透明部分的反射执行一次,但也可能对于投射材质反射以及水体反射再执行一次,因此只操作屏幕中需要的区域是很重要的。
tile之外的分时过滤器邻接对齐读取
There’s a gotcha with implementing a tile based reflection pipeline - all of the denoising passes read from neighbors which may not have been processed. For the temporal filter’s neighborhood clamp, we could clear the unused regions of the texture, or branch in the temporal filter. It’s slightly faster to have the pass that runs before the temporal filter clear the tile border. We can only clear texels in unused tiles, to avoid a race condition with the other threads of the spatial reuse pass.
这里展示了一个基于tile的反射管线的实现——所有的降噪pass从相邻处的读取可能是未被处理过的。对于相邻对齐的分时过滤器,我们可以清理未使用的纹理区域,或在分时过滤器中做分支。在分时过滤器清理tile边缘之前执行这个pass会略微更快一点。我们只能在未使用的tile上清理纹素(texel 大于一像素的图块),以避免其它线程对于空间重用pass的竞争状态。
*这一段原文稍微有点绕了,其实主要是在说何时以及如何清理tile的边缘。
透射物反射的挑战
对于透射物体的反射,我们需要支持任意的层,并且我们不能直接从像素shader中追踪,因此我们需要在像素shader外层来处理,之后再插值到像素shader中。同时,玻璃也需要镜像反射,因此这部分我们不能做任何插值。
前景层追踪新的反射射线
未提供这种玻璃反射,我们通过深度剥离(depth peeling 之前水体的文章介绍过)的方式把最前景的层拆分至一个尽量小的GBuffer中。之后我们再对有效的像素部分执行一次反射管线,并关闭降噪以减少管线负担。
额外的层使用世界空间光照缓存
对于其余层,我们再次使用世界空间光照缓存。我们使用与不透明Final Gather相同的光照缓存,只需要放置更多的探针。我们通过以低分辨率光栅化透射表面——来标记新的探针位置,之后标记每个像素位置需要的探针。
然后我们将透射部分光栅化至任意层,其中的像素shader都从光滑反射的光照缓存中插值计算。
结语
对其它技术分享的引用
这次引用列表就很好的反映出渲染技术与引擎开发是一项多么需要积累的事情,而新的渲染技术越来越是“改”出来的而不是凭空造出来的了——其中多数人例如Wright都是虚幻引擎的核心开发人员。另外,设计一套中间数据结构缓存技术,并尽量重复用于多项不同的渲染模块,也是现代引擎设计中最突出的一个思想。
原文还有“性能与扩展性”一节列出了一些数据,基于篇幅原因以及实际指导性不大的考虑,这里就不翻译了。我觉得看实际性能表现如何,可以去看看用虚幻5做的游戏(一共没几个)——因为虚幻引擎的管线开放给开发人员干预的部分很少,因此实际的性能表现中引擎技术及其使用占的比重是较高的。
最近的系列都在读UE5技术分享的文章,一定程度上(至少对我个人来说)是一个逐步“打开黑盒”的过程。一方面可以看到Nanite和Lumen两大模块之间的设计与协调开发,以及其中少量磨合得不尽人意的地方;但更多的是看到了其它引擎开发商无法深入去探讨与实践的,属于最高精度渲染才需要考虑的无数细节。
最后是资料链接:
Sorted-Deferred tracing pipeline [Tatu Aalto 2018] [Kelly et al 2021]的一个摘录地址
Lumen: Real-time Global Illumination in Unreal Engine 5 的PTTX