【GPU Technology Conference】Pract
今天看到了一篇文章描述了一个针对此前介绍的Voxel Cone Tracing方案的改进方案,因此抽时间对其进行学习整理。
这个方案被称之为VXGI(Voxel-based GI),原文参考链接照例放在文末。


上面给出了GI的定义与业界现有算法的不足,下面给出VXGI的解法:

基于Voxel Cone Tracing方案(跟SVOGI方案的关系是?)做了改进,不需要离线过程,通过从不同分辨率的GPU Voxel Scene中获取数据,可以做到对动态场景的友好。

先来回顾一下VCT算法的实现逻辑:
- 将场景光栅化为一个voxel opacity map,且这个map是按照8叉树层级组织的
- 绘制RSM,并将RSM转换成另一张同样带有8叉树层级的voxel map
- 为每个像素(或者降分辨率后的像素)沿着上述两个map进行cone tracing,计算光照结果

相对于VCT中使用稀疏八叉树来存储体素数据,这里采用了不同的3D Clipmap的存储方法,2D Clipmap我们知道,就是中间精细,四周粗糙的网格,将这个概念扩展到3D,就得到了3D版本的Clipmap。
使用Clipmap的好处是:
- 对于硬件来说,使用Clipmap更容易构建跟读取
- 可以根据需要选择不同粒度的实现逻辑,显存需要可以在12MB到2.5GB之间进行伸缩

为了方便大家理解,这里将Clipmap跟Mipmap之间的概念做了一下对比,可以看到,不同的Mipmap覆盖的范围是相同的,且像素(体素)数目逐级递减;而Clipmap每一级的像素(体素)数目是不变的,与之相对的是,覆盖范围逐级扩大(知道我们认为不再需要扩大为止,比如上图中达到了最大范围,此时再往上,其逻辑就跟mipmap一致了)。

对Clipmap的更新采用了类似Shadow Scroll一样的缓存策略,静态物件的数据可以被重用

基于地图尺寸,计算世界空间中的点在clipmap中的坐标

在相机移动后,clipmap中的大部分数据还依然是有效的

对于动态物体,当物体发生了变化后,其对应的区域需要经历重新体素化。

这里需要两套Clipmap,分别是Opacity跟Emittance
- Opacity存储Opacity方向,每个voxel需要3个(HQ tracing)或6个(LQ tracing,更少的自阴影效果),代表的是voxel被沿着某个方向观察时的不透明程度
- Emittance存储自发光方向,同样每个voxel需要3个(LQ tracing)或6个(HQ tracing,具备second bounce效果)

下面来看下Opacity的数据的计算逻辑(基于Opacity可以实现透明投影效果,即将半透物件的影响加入到GI中来),给定一个三角形跟一个voxel

选定一个投影方向(XYZ三个方向中),在这个方向下,三角形投影面积是最大的

将面片光栅化,并且基于MSAA算法,为每个像素计算一个coverage mask。
通常情况下,每个像素的MSAA pattern是不同的,不过这里将像素中的各个采样点转换到一个regular grid上(没明白具体是啥意思)

接下来基于选定的投影平面上的采样点,找到其在原始三角形上的位置,并将之投影到另外两个投影平面上。

所有的sample都做同样处理。

对samples做模糊处理,扩充投影区域,这个操作可能会将当前voxel数据扩充到相邻的其他voxel上。



这是opacity体素化后的结果。


通过下采样,获得其他层级的clipmap

再来看下Emittance的体素化逻辑。
- 先使用前面MSAA的coverage mask计算出当前像素的自发光颜色,据此估算出近似的自发光brightness
- 计算自发光贴图的brightness跟法线数据,每一套数据对应六张贴图(分别对应一个体素的六个方向?)
- 将brightness转化为带方向的自发光

自发光体素化的一个问题是小物件的变化可能会引起brightness的剧烈波动,上图给了个例子,比如绿色长方形是一个小物件,移动位置前后对应的samples数目从4变成了1,这个问题在clipmap的remote region中出现比较频繁,可以考虑通过supersampling跟analytical coverage计算方法来解决。

每个三角面片会被光栅化到多个不同的分辨率的clipmap上:
- 中心区域的低分辨率clipmap的像素可以通过高分辨率clipmap下采样得到
- 可以通过GS的实例化功能来同时对一个三角形做多次光栅化

之所以要从高分辨率下采样获得低分辨率数据,是因为这样得到的结果会比直接低分辨率光栅化要精确

Light injection是基于被光源照亮的体素来计算emittance的过程,这个过程需要从RSM中获取数据。

这里给出了三种算法,成本逐级递增,效果自然也是逐级递增:
- 直接比对
- RSM Gather
- RSM Scatter


注入过程会遇到的一个问题就是锯齿(闪烁),物体或者光源的轻微变化,就有可能导致结果的剧烈变化

一个想法是在注入之前先做模糊处理,但是有这么几个问题:
- 对于Scatter算法来说,可能会加剧计算成本
- 对于不同level的clipmap,会需要使用不同的kernel,但不同的kernel可能导致结果的不匹配

先来看下cone tracing的基本概念

不同需要,cone tracing的参数也不同


diffuse cone tracing的一些细节:
- 沿着某个方向往前marching,计算当前voxel到相机的透光率,当大于某个阈值时,marching结束
- 随着marching方向往前走,采样面积会逐渐增加,此时需要取 Clip map 里面更高的 Mip 的Voxel,从而只需要一次采样就能覆盖整个区域

这里给出VXGI的问题[2]:由于是Cone tracing的,且为了保证计算的效率,每次会选择跟cone射线点的半径相一致的voxel,这就导致了,其计算得到的opacity会存在问题,如上图的上半部分所示
而这个问题导致的现象就是漏光,如上图下半部分所示,对于一些存在上述计算问题的区域,尤其是一些比较薄的物体,经过上述粗糙估算就会导致漏光现象。

这里给出了算法在不同GPU上,使用不同配置时的计算消耗(单位ms)

这里给出了各个部分的具体耗时占比:diffuse cone tracing > specular cone tracing > emittance update > opacity update > allocation update

这个算法已经集成到NVIDIA的GI Works库中,下面给一些实验数据展示。

diffuse cone tracing在降低采样密度后的表现,可以看到,差别不算太大,但性能却有很大提升

不过过于稀疏可能导致的问题就是会在相机移动的时候,出现明显的闪烁

specular cone tracing的step过长会导致banding效果,过短的话消耗又高,尤其是在一些非平面的表面上,这里推荐了一种noisy的dense tracing方案,可以在性能跟效果之间做一个平衡。

这里给出了使用这种算法的效果展示,此外值得一提的是,UE某个版本的GI插件使用的就是VXGI算法。
参考
[1]. Practical Real-time Voxel-based Global Illumination for Current GPUs - NVIDIA
[2]. Dynamic Global Illumination and Lumen