6.3 Volume Lightmap的烘焙实现

有了基于Voxel的快速射线追踪算法,就可以实现Volume Lightmap的烘焙了。Volume Lightmap存储Irradiance数据,本身比较低频,所以Volume Lightmap可以做到分辨率比较小,一般来说1m的精度就够了。但是Volume Lightmap不会存储阴影,所以阴影需要实时计算。和普通的Lightmap相比,Volume Lightmap的优点如下。

● 模型不需要Lightmap UV,节省顶点数据量。

● 数据量和模型展开面积无关,只和模型占用空间大小有关,数据量低于Lightmap。

● 支持法线贴图,可以展现精细的细节效果。Volume Lightmap里面的每一个Cell都可以看作一个单独的Probe,Volume Lightmap的烘焙可以看作针对每一个Probe进行Irradiance烘焙。下面我们先介绍如何计算单个Probe的Irradiance,再阐述如何高效地计算所有Probe的Irradiance。

6.3.1 单个Probe的Irradiance计算

对于某个空间位置来说,Irradiance是一个球面函数,计算Probe的Irradiance就是计算Probe中心点所在的Irradiance球面数据。对于方向ωi来说,该方向的Irradiance计算如下。其中,D是某个需要计算Irradiance的方向,ωi是某个射线发射的方向。

Irradiance (D)=TraceRadiance (ωi) max ( 0,ωi·D) dωi

由蒙特卡罗方法可以写为下式,其中,N是射线的数量。

可以看出,对于单个Probe某方向的Irradiance计算,需要发射大量射线进行路径追踪,在烘焙时当然不可能对每一个位置的每一个方向都进行路径追踪,所以需要利用好每一次射线追踪的结果。

6.3.2 Volume Lightmap的编码

对于Irradiance数据的编码,选择有很多,如Spherical Harmonics、Spherical Gaussain、Ambient Cube、Ambient Dice等。这里选择Ambient Cube,因为它所需要的的数据量是最小的,其他编码方式虽然更逼近原始数据,不过数据量要求更大、带宽要求更高,如3阶的Spherical Harmonics对于RGB的每一个通道都要求9个float值。

Ambient Cube的编码方式决定了在计算时只需要考虑6个方向即可,发射的每一条光线追踪出来的Radiance都可以用来计算这6个方向的值。

6.3.3 Dense Volume Lightmap的所有Probe的Irradiance计算

所有Probe的计算都需要高效的高并发计算。本节考虑均匀分布的Dense Volume Lightmap的Probe的Irradiance计算,也就是所有Probe之间的间隔都一样。

如图6.5所示,每个Probe Dispatch对应一个Thread Group,每个Thread Group中的每个Thread负责一个方向的路径追踪来搜集Radiance。

图6.5 Probe对应的射线分布

因为使用计算机来计算Irradiance,所以高效的算法需要有以下考虑。

● 因为CPU和GPU异构,所以要尽量减少CPU和GPU之间的通信次数和传输数据量,尽量用GPU来调度。

● GPU Global Memory访问太慢,需要减少Global Memory访问的次数,多利用Shared Memory,每次最好访问连续的一段Memory。

● 尽量减少线程的分支数。

● 使用Packet Traversal方式,一个Warp里的射线共享一个Tracing Stack,减少Memory的访问次数。

● 射线按照Morton-Order分发给每个Warp,每个Warp处理的射线尽量在方向上比较接近,每个Warp的射线最好有比较一致的访问Memory的方式。

● 减少Shared Memory和寄存器的使用数量,因为使用得越多,能并发运行的Block和Warp数量越少。

● GPU的硬件Work Scheduler是为负载均衡的线程模型设计的,但是射线追踪的线程负载并不均衡,Work Scheduler的效率较低,因此采用论文[8]中的Persistent Thread并发处理模型,绕过硬件Scheduler,将所有的射线都放到队列里面,由Warp中的线程自己取来用。

● 在收集多个射线追踪出来的Radiance并计算Irradaince时,需要采用高效的Parallel Reduction算法,详见论文[9]

6.3.4 Sparse Volume Lightmp的计算

6.3.3节处理了均匀分布的Volume Lightmap的计算,本节介绍Sparse Volume Lightmap的计算,它的核心其实是Probe的分布,这里要讨论下什么是好的Probe分布。一般来说,如果要最大化一个Probe的数据有效性,那么Probe的位置最好符合下面3个条件。

● Probe中心点和物体的Voxel不能有交叠,否则算出来的Irradiance都是黑的,因为发射的射线都被挡住了。

● Probe中心点不能距离物体的Voxel太近,否则算出来的Irradiance有效信息太少,因为它只能“看到”很少的区域。

● Probe中心点不能距离物体的Voxel太远,否则储存的Irradiance信息用处不大,因为它能“看到”的区域虽然很大,但是分配给这些信息的存储太少。

从上述条件可知,每个Probe与物体表面或Voxel的距离信息很关键,这些距离信息可以用Signed Disntance Field(SDF)方法来计算。

首先根据场景大小如Probe之间的间隔大小计算出Dense Volume Lightmap的分辨率大小,申请一张同样分辨率大小的3D纹理,在Voxelization阶段,该纹理记录的数据是计数值。对于每一个Voxel,如果它落在某个Dense Volume Lightmap的某个Cell里面,就将该Cell对应的计数值加1,当Voxelizaition结束之后,对于每一个Cell,如果它对应的计数值超过了一个阈值,则把该Cell标记为被物体占满。然后使用论文[10]中的Jump Flooding算法算出每个Cell(也就是Probe)距离最近的物体之间的距离。

2D Jump Flooding算法例子如图6.6所示。这里简单描述下Jump Flooding算法如何快速计算SDF,以2D贴图为例,假设贴图大小是n×n,运行算法之前,对于标记为被物体占满的Cell c,初始化c对应的贴图数据为<cxcy>,也就是说离c最近的Cell就是它自己。接着运行lgn次Flooding过程,在每次的Flooding过程中,每个Cell都将自己记录的最近距离节点信息传递给其他最多的8个节点,这8个节点的坐标是<cx+icy+j>,其中ij∈{-l,0,l},l是lgn次迭代的步长,取值n/2,n/4,…,1。在运行了lgn次Flooding之后,每个Cell就记录了离它最近的被物体占满的Cell的坐标。更多的计算细节可以参考原始论文,整个算法可以用计算机计算,效率很高。

图6.6 2D Jump Flooding算法例子

每个Cell知道离自己最近的被物体占满的Cell坐标之后,就能算出最近距离。由前面的分析知道,只需要对距离包含在某个范围内的Cell计算Irradiance,这个范围可以由用户设定。这些有效的Cell计算完后可以用Octree或KD-Tree记录下来,在Runtime时,Shading Point法线方向的Irradiance就可以先自顶向下遍历找到周围Cell对应的Probe,再根据这些Probe的Irradiance插值计算得到。

需要注意的是,Sparse Volume Lightmap虽然大大减少了存储空间,但是计算量大大提高了。

在图6.7中,小球体就是稀疏分布的Probe采样点。

图6.7 Sparse Probe分布例子

6.3.5 Volume Lightmap的存储

对于《王者荣耀》5V5对战场景来说,使用3D纹理压缩Dense Volume Lightmap是最好的方案,原因如下。

● 5V5对战场景是个规规矩矩的方形,并且高度有限,能和3D贴图较好地对应。

● 5V5对战场景需要很高的渲染效率,稀疏的Volume Lightmap虽然能大大减少存储空间,但是需要消耗较多的Alu指令开销计算和Shader分支,并且跳转较多,Cache利用率较低。

● 用3D贴图存储Irradiance可以使用ASTC压缩格式大大降低存储代价。

● 5V5对战场景的相机处于俯视角度,能看到的纵深较小、范围较小,采用3D贴图存储方式,Cache利用率会比较高。

对于《王者荣耀》用的GLES和Metal,作者分别采用了不同的压缩方案。对于GLES,如果支持GL_KHR_texture_compression_astc_hdr扩展,则使用3D ASTC HDR压缩方案,根据品质要求可以选择3pixel×3pixel×3pixel或4pixel×4pixel×4pixel的Block Size的ASTC压缩。对于Metal,由于不支持3D ASTC HDR,所以只能使用LDR的ASTC格式,并且使用Slice-Based 3D贴图,HDR的Irradiance采用类似Unreal Lightmap的编码方式编码得到LDR数据,一个Slice一个Slice地压缩后,组装成3D贴图。对于既不支持3D贴图又不支持Slice-Based 3D贴图的平台,可以把3D贴图展开成2D贴图,在Shader中模拟3D贴图的采样,不过需要付出一定的额外性能开销。

6.3.6 Volume Lightmap的使用

因为Volume Lightmap的数据采用了Ambient Cube编码,所以其解码是比较简单的,首先根据normal选择3个方向,然后采样对应的数据即可。