这个系列是GAMES104-现代游戏引擎:从入门到实践(GAMES 104: Modern Game Engine-Theory and Practice)的同步课程笔记。本课程会介绍现代游戏引擎所涉及的系统架构、技术点以及引擎系统相关的知识。本节课主要介绍现代游戏引擎中的全局光照技术。
Global Illumination
The Rendering Equation
全局光照(global illumination, GI)是渲染中的重要问题。在介绍GI在游戏引擎的实现方法前我们先回顾一下渲染方程(the rendering equation):

可以说一切渲染问题的本质在于求解渲染方程,而求解渲染方程的难点在于方程自身的递归形式。当场景中的物体被光源照亮后,被照亮的物体又会成为新的光源再次照亮其它物体。以Cornell box为例,来自屋顶的灯光会照亮左右两侧红色和绿色的墙壁,然后墙壁反射的光线又会照亮盒子使得盒子的两侧呈现红色或是绿色,这样的现象称为color bleeding。


在复杂的游戏场景中通过GI会极大地提升画面表现力。

Monte Carlo Integration
作为积分方程,求解渲染方程的经典方法是Monte Carlo积分(Monte Carlo integration)。对于可积函数$f(x)$,我们可以通过采样的方法来逼近$f(x)$的积分。


光线追踪(ray tracing)算法的本质就是通过Monte Carlo积分来求解渲染方程。

Importance Sampling
Monte Carlo积分的效率和精度取决于如何设计采样的样本。当样本的数量比较少或是质量比较低时,通过光线追踪渲染出的图像往往会具有非常多的噪声。

因此如何设计采样的分布对于提升渲染质量有着非常重要的意义,其中最简单的采样方法是均匀采样。

现代高质量渲染的核心技术在于重要性采样(importance sampling)的大规模应用。重要性采样理论指出当我们的采样函数接近于被积函数时只需要相对少的样本就可以很好地近似被积函数的积分,而在计算Monte Carlo积分时只需要对样本按照pdf进行加权求和即可。


回到渲染方程中,被积函数包含余弦项$\cos \theta_i$,因此一个常见技巧是根据余弦项进行采样;类似地也可以按照BRDF来进行采样,这种采样方式对于光泽表面的物体往往有着更高的积分效率。



Reflective Shadow Maps
尽管通过光线追踪可以解决GI的问题,但它的主要缺陷在于光线追踪基本无法应用在游戏这样有实时性要求的场景中。为了实现实时的GI人们设计了各种各样的算法来进行近似,其中最早的工作可以追溯到2005年的reflective shadow maps(RSM)算法。
从实现的层面上讲,RSM更接近于光子映射(photon mapping)。光子映射理论认为相机接收到的radiance本质是由光源发射的光子经过场景不断的吸收和反射最终的被相机捕获的结果,因此我们可以从光源出发发射大量的光子然后计算光子在场景上的分布,然后通过相机来进行收集即可。
在RSM中我们需要从光源的位置首先渲染一张shadow map,它表示场景中所有被光源直接照射的部分。

shadow map记录了这些区域在直接光照下反射的radiance,这样当我们需要考虑GI时只需要把直接光照和shadow map上记录的来自其它物体的反射光线进行相加即可。

直接计算来自场景中其它物体的反射光线仍然需要非常多的计算量,在RSM中使用了cone tracing的技术来进行简化。

由于间接光照一般来说是相对低频的,在渲染时还可以降低输出的分辨率进一步提升效率。然后在与直接光照相加时通过插值的方式来获得完整分辨率下的间接光照。

通过RSM实现的GI可以明显提升游戏画面中阴影部分的细节。

总结一下,RSM作为实时GI的早期工作非常容易进行实现而且很高的计算效率;而它的缺陷在于RSM只能考虑光线的一次反射,而且在计算间接光照时没有进行可见性检测。

Light Propagation Volumes
light propagation volumes(LPV)是考虑光线在场景中不断传播的一种GI算法,最早在2009年提出。

LPV的核心在于把场景使用三维的网格进行表示,并以此来计算光线在场景中的分布。


LPV在计算时会记录每个格子上当光线传播到物体表面后散射的radiance,然后以此为中心向其它格子进行扩散。


某种意义上讲LPV把光线的传播视为扩散过程,严格来说这样的处理是不完全遵循物理法则的。

Sparse Voxel Octree for Real-time Global Illumination
SVOGI的思路与LPV非常接近,都使用了网格的方式来对场景空间进行划分。在SVOGI中使用了保守光栅化(conservative rasterization)的方法来获取场景的体素表达,从而得到场景中所有表面的体素。


为了更高效地管理场景中的体素,SVOGI使用了八叉树这样的数据结构来把体素组织起来。

在进行shading时则使用了cone tracing的方式来对八叉树进行查询。

Voxelization Based Global Illumination
VXGI可以看做是对SVOGI的简化。在VXGI中使用了clip map这样的数据结构来描述场景,离相机越近就具有越高的分辨率。

当相机发生运动时无需更新clip map,只需要更新相机采样的范围即可。

这样整个场景就得到了一个体素化表达,离相机近的地方体素越稠密,越远的地方体素越稀疏。

对于每个体素我们还需要计算该体素遮挡了多少的光线,这里会记录体素在三个方向上的可见性。


当来自光源的光线注入到场景中时需要记录每个体素的表面上接收到的直接光照。


而对于屏幕上的像素则通过cone tracing的方式来计算间接光照,通过叠加整条光路上的radiance来获得间接光照。



VXGI的主要缺陷在于cone tracing的结构仍然是对间接光照的一种近似,而且它非常容易出现漏光的问题。

Screen Space Global Illumination
SSGI和前面介绍过的方法相比是基于屏幕空间的GI技术。在现代GPU渲染管线中我们可以快速地渲染出屏幕空间上的各种物理量,通过重用屏幕空间的数据就可以实现GI。

当我们渲染得到屏幕空间上的直接光照时,可以利用屏幕空间上像素的法向信息来继续计算间接光照。

计算间接光照时可以利用ray marching的方式来计算光线和平面空间中物体的交点。

为了进一步提升ray marching的效率,SSGI还使用了z-buffer的mipmap来进行加速。







同时SSGI还会对每一个像素来重复使用其相邻像素采样的间接光照,这样可以减少采样的光线数量从而进一步提升渲染效率。


和前面介绍过的基于体素的GI技术相比,SSGI对于光泽表面有非常好的渲染效果;但需要注意的是SSGI无法处理屏幕空间之外的物体,这容易导致各种错误的渲染结果。


Lumen
Luman是虚幻5引擎提出的最新实时GI技术。尽管实时光线追踪也可以实现实时GI,但它依赖于硬件层面的实现而且需要大量的采样才能实现比较好的渲染效果。而Luman不依赖于硬件实现,可以应用到大量对实时性有需求的环境中。



Fast Ray Trace in Any Hardware
Luman的渲染过程可以大致划分为4个部分,首先是进行高效的路径追踪。考虑到不是所有的硬件都支持光线追踪的加速,Luman使用了SDF的方式来实现这一过程。

由于直接把场景转换成SDF往往是比较复杂的,我们可以先对每个物体转换为SDF。对于平移变换和等比例的放缩,物体坐标系下的SDF都可以很容易地转换为场景坐标系下的SDF。

需要注意的是当物体比较薄时要进行一些额外的处理。

SDF的一大优势在于它可以非常容易地得到光线步进的长度:在任意p位置前进SDF(p)的距离总是可以保证不会和场景出现相交。

同时,SDF在进行cone tracing时也可以很容易地计算出遮挡面积比例的估计。

生成SDF时可以考虑使用一些稀疏的数据结构进行表示。

SDF甚至可以表达物体的不同LOD。

在低LOD下结合sparse mesh可以极大地减少模型的存储空间。

当然,直接将场景中物体的SDF组合到一起在进行计算时仍然是过于复杂的。


这里可以结合屏幕空间的概念只考虑相机视野范围内的物体,将它们的SDF融合为一个低精度的全局SDF。

在这个低精度的全局SDF上进行ray tracing可以极大地降低计算压力。

除此之外SDF也可以结合mip的思想,近处的精度高而远处精度低。

Radiance Injection and Caching
接下来我们需要把光照信息注入到场景中。在Lumen中使用了mesh card来保存物体在6个正方形上被光照亮后的结果。


对于每个card我们还需要记录物体表面的albedo、法向以及深度等信息。

所有物体的表面信息会统一记录到一张标准大小的纹理图像上,称为surface cache。


根据物体距离相机的远近还可以设置不同的card分辨率以降低存储和计算需求。

我们的目标是把物体所有光照的radiance记录到surface cache上。

在Lumen中整个光照可分解为直接光照、体素化光照以及间接光照三部分。

直接光照相对比较简单,我们只需要考虑光源直接照射到物体上反射的radiance即可。对于阴影中的物体则可以结合shadow map来进行处理。


得到直接光照后Lumen会对场景进行体素化来存储物体在6个方向上直接光照的亮度。而且体素化后的光照信息会在相邻帧上进行传递,随着时间的积累会得到光线多次弹射的效果。





最后计算间接光照时只需要从surface cache上进行采样,利用上一步体素化光照作为照明即可。



把直接光照和间接光照相加就得到了环境中光照的信息。


Build a lot of Probes with Different Kinds
Screen Space Probe
有了光照后我们需要在物体表面通过采样来计算着色,此时我们需要在场景中放置探针(probe)来采样光照。Lumen中直接在屏幕空间里放置probe,每个probe会同时记录光线前进的距离以及收集到的radiance。








Importance Sampling
为了提升渲染质量我们需要使用重要性采样的技术尽可能在比较重要的方向上进行采样。


回到Monte Carlo积分的公式中,我们需要把光线更多地分布在光照比较强或是接近物体法线的方向上。

Lumen中会对上一帧以及四周相邻的probe进行平均来估计光照的分布。

而对于物体法向附近的方向,Lumen会估计probe附近的法向分布。







Denoising and Spatial Probe Filtering
为了进一步提升渲染效果我们还需要对图像进行滤波降噪。





World Space Probes and Ray Connecting










Shading Full Pixels with Screen Space Probes


Overall, Performance and Result











Conclusion
