之前写过一篇Global Illumination_Reflective Shadow Maps(RSM)、Virtual Point Light(VPL),近期重拾传统GI技术的实际工程场景中的应用,于是从效率方面对RSM、LPV、VCT技术进行效率优化,后续逐个对三个主流技术进行优化分析,本部分主要对RSM技术进行优化介绍,优化主要参照国外大佬Evgenii Afanasev的论文Dynamic real-time global illumination algorithms on modern GPU hardware and software。
一、RSM简述
知其然,知其所以然,我们首先需要知道RSM技术的主要卡点在哪,那么我们先来看一下RSM技术的原理以分析其性能卡点。
首先看一下RSM的管线流程,如下图:
可以看出原始的RSM算法包括两个通道:RSM纹理的生成和最终的间接漫射照明计算。第一个非常简单,如果在应用程序中使用,它可以与阴影映射通道结合起来(通量纹理只不过是光的颜色和对象的表面颜色的乘法)。另一方面,第二阶段的光照辐射就比较反锁了。因此本节重点介绍两个主要的着色器优化技术和一个图形管线优化技术,以提升性能。
二、RSM优化技术
2.1 复杂着色器代码优化
我们首先来看一下RSM的计算公式:
可以看到用了pow函数,那就牵扯到SFU的使用了,可以看下图了解下Shader中应尽量避免的SPU函数操作,详细原理可以参照之前文章GPU架构与管线总结。
因此我们对除数的计算进行优化,计算长度(平方根运算),即将pow函数改成dot函数,由于现代显卡的向量化结构,减少了GPU上着色器组装指令的数量(图8,9)。
可以对比看一下汇编的不同:
优化后:
优化前:
这个优化是相当基础的,因为它可以用于任何其他的着色器与类似的数学操作。而且我们会发现它对于着色器利用率低的场景很有用。
2.2 降采样优化
上述优化后,我们会发现其实无论我们怎么优化Shader代码带来的收益都是很少的,你会发现其实RSM的性能都高度依赖于最终纹理的分辨率。因此我们必须优化分辨率以减少计算,所以就有了降采样方式,本方式也是本算法性能提升最大的技术。
我们具体看一下最初的技术是对屏幕上的每个点进行𝑁样本,以计算场景的间接全局照明,这在全分辨率下是非常费的,而且这种方法的性能是:
其中,𝑠𝑐𝑟𝑒𝑒𝑛𝑊𝑖𝑑𝑡ℎ和𝑠𝑐𝑟𝑒𝑒𝑛𝐻𝑒𝑖𝑔ℎ𝑡是最终渲染目标(即1920x1080)的维度,𝑁是每个点的样本数。
上述的计算很费,所以我们假设最终渲染目标的降采样版本可以在主通道中计算出来,然后再进行上采样和过滤,而不会失去太多的渲染质量。实践表明这种方式可以并显著提升了效率,因为复杂性变成了:
其中,𝑘是降采样因子。RSM的渲染质量也会随着降采样因子的增大而降低。简单的9x9高斯模糊可以被用作上采样的滤波,使用降采样后的渲染渲染管线边成了这样:
使用降采样之后(降采样因子用3)性能可以急剧提高,下图显示了有和没有降采样间接照明纹理的整个帧时间的性能差异:
查看GPU执行情况如下图:
未优化时GPU使用情况:
降采样后GPU使用情况:
值得注意的是,着色像素数量的大幅减少,导致了l2-缓存和VRAM的最大吞吐量的提高。
至于上采样和滤波pass,单帧仅增加了0.46 ms左右时间,并且上采样和滤波算法是通用的,可以用于其他类似的通道。
2.3 计算着色器优化
为了充分利用并行算法,计算着色器也是一个重要的优化点,因此RSM的另一个优化是将第二个/主通道的逻辑从原始像素着色器移动到计算着色器。在最初的算法被发明出来的时候,计算着色器并不存在,因此最初算法是基于PS的。计算着色器实现RSM在功能上是相同的,除了在计算着色器中,我们执行用于降采样纹理的每个像素的分派线程可能需要针对图形API的Dispatch和Tex进行对应。
具体算法是将RSM计算、上采样和滤波两个阶段基于CS实现,流程如下所示:
由于算法对大量相邻点的随机采样等重算法计算型的逻辑依旧和之前一样,因此计算着色器和像素着色器的执行效率差异并不突出,但平均而言,总时间最终得到一点点的改善。
经过计算作色器的进一步优化,算法仍然非常受带宽限制(仍存在许多纹理样本),降采样并且使用计算着色器后GPU使用情况:
可以看出SM(流多处理器)的最大吞吐量仍然是很差的仅为19%左右。
2.4 并行渲染管线升级
正如我们从上面的图表中所看到的,RSM算法可能会严重影响GPU的计算单元。由于算法的逻辑,在着色器中额外的优化将不再增益。因此,我们看一下现代图形API的异步操作优化,在DirectX 12中允许通过多个队列(图形、异步、复制)并行化GPU上的不同作业。这个想法在理论上是很有前途的,但是在引擎中实现了异步计算的同时,至于实践项目能到什么程度就看具体实现了。
本次实践的目标是运行大量的RSM计算通道,如主RSM通道和它的“上采样和滤波”,并行于其他渲染通道,如阴影映射和RSM缓冲区生成。因此优化后的并行RSM渲染管线框架如下图所示:
第二个队列(计算)执行了仅计算相关的任务。从图中可以看出,RSM缓冲区的生成也与RSM的主通道并行运行,该主通道使用了该生成通道的结果。在这个场景中,在这些资源上会发生竞争条件,即上述纹理同时被读取和写入。这可能会导致在任何时刻产生渲染错误,而且在任何多线程场景中都是不希望出现的。这个问题可以通过处理前一帧的图形队列的结果来解决,也就是说可以使用处理前一帧的RSM缓冲区的副本来解决。这一操作对性能的开销并不显著,主要是增加了内存消耗。
理想很丰满,显示却很骨感。在NVIDIA GPU上,异步并行潜在的性能提升并不乐观。异步版本和非异步版本的帧率差异不大(非异步单帧16.61ms,异步单帧16.05ms)。分析结果如下:
非异步处理情况:
异步处理情况:
从上面的屏幕截图中可以看出,这两种情况下的帧时间持续时间都在16 ms左右。在原始的非异步管道中,RSM主通道和上采样大约需要8-9个ms,而异步版本在10ms中执行相同的计算任务。在某些帧中,它的表现比这更好,但平均而言,提升并不明显。很明显管线架构没问题,计算任务是与图形任务一起运行的,这从吞吐量和占用率统计数据中可以看出,尽管异步计算任务的单位的百分比使用低于非异步版本。例如,SM、L1和L2的吞吐量在整个帧中均匀分布,但是在异步模式下的计算任务不能充分利用这些计算单元的最大算力,因为它们与图形任务共享缓存可能会存在数据抢占。然而,在非异步的图中,可以看到计算任务(大致从帧的后半部分开始)却充分利用了GPU可用的吞吐量,没有任何严重的下降和峰值。在“SM占用”的统计曲线中利用率也是如此。
可以看出RSM主流程的计算仍然是很耗的大头。顶点/像素着色器和计算着色器工作量之间的差异以及图形队列中没有任何其繁重的并行任务,可能是为什么即使使用了异步队列的性能提高还是不明显的原因。
三、小结
本项目的RSM实现了几个优化版本用于间接全局照明的近似效果,但碍于算法效果实在是不佳,我们也不准备在继续下去。首先,它只允许一次反弹的间接扩散,没有任何其他影响,如反射或环境遮挡等间接光照且十分低频。更致命的是RSM不完全准确存在着严重的漏光现象。此外,RSM需要几个高分辨率的RSM纹理,这些纹理应该加载在GPU内存中,这可能导致一些低端设备的内存和带宽瓶颈。除此之外,主要的缺点是对这些纹理的采样,即使在现代GPU上,该算法的主着色器通道仍然非常重,而异步方案的额外优化并没有显著改善这种情况。虽然采样的数量可以减少,但随之而来的是渲染质量的损失。但实现起来简单,一些简单场景或手机项目上可能会在某些情况下使用它。
总之,学习算法与优化思路才是根本,后续更新LPV与VCT深度优化技术。