现代游戏引擎 - 动态全局光照和Lumen(二十一)
GI基础(Global Illumination)
渲染方程式(The Rendering Equation)
全局光照(global illumination, GI)是渲染中的重要问题。在介绍GI在游戏引擎的实现方法前我们先回顾一下渲染方程(the rendering equation):渲染方程
可以说一切渲染问题的本质在于求解渲染方程,而求解渲染方程的难点在于方程自身的递归形式。当场景中的物体被光源照亮后,被照亮的物体又会成为新的光源再次照亮其它物体。以Cornell box为例,来自屋顶的灯光会照亮左右两侧红色和绿色的墙壁,然后墙壁反射的光线又会照亮盒子使得盒子的两侧呈现红色或是绿色,这样的现象称为color bleeding。渲染方程2
全局光照:数十亿个光源
在复杂的游戏场景中通过GI会极大地提升画面表现力。全局光照对游戏很重要
蒙特卡罗积分(Monte Carlo Integration)
作为积分方程,求解渲染方程的经典方法是Monte Carlo积分(Monte Carlo integration)。对于可积函数蒙特卡罗积分
蒙特卡罗积分2
光线追踪(ray tracing)算法的本质就是通过Monte Carlo积分来求解渲染方程。蒙特卡罗光线追踪(离线)
重要性采样(Importance Sampling)
Monte Carlo积分的效率和精度取决于如何设计采样的样本。当样本的数量比较少或是质量比较低时,通过光线追踪渲染出的图像往往会具有非常多的噪声。采样是关键
因此如何设计采样的分布对于提升渲染质量有着非常重要的意义,其中最简单的采样方法是均匀采样。采样:统一采样
现代高质量渲染的核心技术在于重要性采样(importance sampling)的大规模应用。重要性采样理论指出当我们的采样函数接近于被积函数时只需要相对少的样本就可以很好地近似被积函数的积分,而在计算Monte Carlo积分时只需要对样本按照pdf进行加权求和即可。概率分布函数
重要性采样
回到渲染方程中,被积函数包含余弦项重要性采样:最好的PDF渲染?
重要性采样:PDF是重要的
重要性采样:Cosine和GGX PDF
反射阴影贴图(Reflective Shadow Maps - RSM)
尽管通过光线追踪可以解决GI的问题,但它的主要缺陷在于光线追踪基本无法应用在游戏这样有实时性要求的场景中。为了实现实时的GI人们设计了各种各样的算法来进行近似,其中最早的工作可以追溯到2005年的reflective shadow maps(RSM)算法。
从实现的层面上讲,RSM更接近于光子映射(photon mapping)。光子映射理论认为相机接收到的radiance本质是由光源发射的光子经过场景不断的吸收和反射最终的被相机捕获的结果,因此我们可以从光源出发发射大量的光子然后计算光子在场景上的分布,然后通过相机来进行收集即可。
在RSM中我们需要从光源的位置首先渲染一张shadow map,它表示场景中所有被光源直接照射的部分。反射阴影贴图
shadow map记录了这些区域在直接光照下反射的radiance,这样当我们需要考虑GI时只需要把直接光照和shadow map上记录的来自其它物体的反射光线进行相加即可。反射阴影贴图2
直接计算来自场景中其它物体的反射光线仍然需要非常多的计算量,在RSM中使用了cone tracing的技术来进行简化。用RSM跟踪锥体
由于间接光照一般来说是相对低频的,在渲染时还可以降低输出的分辨率进一步提升效率。然后在与直接光照相加时通过插值的方式来获得完整分辨率下的间接光照。具有低分辨率间接光照的加速
通过RSM实现的GI可以明显提升游戏画面中阴影部分的细节。提升游戏画面中阴影部分的细节
总结一下,RSM作为实时GI的早期工作非常容易进行实现而且很高的计算效率;而它的缺陷在于RSM只能考虑光线的一次反射,而且在计算间接光照时没有进行可见性检测。RSM
光传播体积(Light Propagation Volumes - LPV)
light propagation volumes(LPV)是考虑光线在场景中不断传播的一种GI算法,最早在2009年提出。光传播体积
LPV的核心在于把场景使用三维的网格进行表示,并以此来计算光线在场景中的分布。光传播体积2
光传播体积3
LPV在计算时会记录每个格子上当光线传播到物体表面后散射的radiance,然后以此为中心向其它格子进行扩散。“冻结”体素中的亮度
光线传播
某种意义上讲LPV把光线的传播视为扩散过程,严格来说这样的处理是不完全遵循物理法则的。带有“限制速度”的光
用于实时全局的稀疏体素八叉树(Sparse Voxel Octree for Real-time Global)
SVOGI的思路与LPV非常接近,都使用了网格的方式来对场景空间进行划分。在SVOGI中使用了保守光栅化(conservative rasterization)的方法来获取场景的体素表达,从而得到场景中所有表面的体素。体素化过程
收集表面体素
为了更高效地管理场景中的体素,SVOGI使用了八叉树这样的数据结构来把体素组织起来。SVOGI
在进行shading时则使用了cone tracing的方式来对八叉树进行查询。体素树中的锥形追踪阴影
体素基GI(Voxelization Based Global Illumination)
VXGI可以看做是对SVOGI的简化。在VXGI中使用了clip map这样的数据结构来描述场景,离相机越近就具有越高的分辨率。体素基GI
当相机发生运动时无需更新clip map,只需要更新相机采样的范围即可。体素更新和环形寻址
这样整个场景就得到了一个体素化表达,离相机近的地方体素越稠密,越远的地方体素越稀疏。体素化表达
‘
对于每个体素我们还需要计算该体素遮挡了多少的光线,这里会记录体素在三个方向上的可见性。用于不透明的体素化
体素化:定向覆盖范围
当来自光源的光线注入到场景中时需要记录每个体素的表面上接收到的直接光照。光注入
光注入2
而对于屏幕上的像素则通过cone tracing的方式来计算间接光照,通过叠加整条光路上的radiance来获得间接光照。带圆锥跟踪的着色
沿着路径积累体素的光线和不透明度
沿着路径积累体素的光线和不透明度2
VXGI的主要缺陷在于cone tracing的结构仍然是对间接光照的一种近似,而且它非常容易出现漏光的问题。VXGI中的问题
屏幕空间全局照明(Screen Space Global Illumination - SSGI)
SSGI和前面介绍过的方法相比是基于屏幕空间的GI技术。在现代GPU渲染管线中我们可以快速地渲染出屏幕空间上的各种物理量,通过重用屏幕空间的数据就可以实现GI。总体思路
当我们渲染得到屏幕空间上的直接光照时,可以利用屏幕空间上像素的法向信息来继续计算间接光照。屏幕空间中的光线采样
计算间接光照时可以利用ray marching的方式来计算光线和平面空间中物体的交点。射线性行进
为了进一步提升ray marching的效率,SSGI还使用了z-buffer的mipmap来进行加速。层次追踪
层次追踪2
层次追踪3
层次追踪4
层次追踪5
层次追踪6
层次追踪7
同时SSGI还会对每一个像素来重复使用其相邻像素采样的间接光照,这样可以减少采样的光线数量从而进一步提升渲染效率。邻居像素之间的光线重用
使用Mipmap筛选的锥体跟踪
和前面介绍过的基于体素的GI技术相比,SSGI对于光泽表面有非常好的渲染效果;但需要注意的是SSGI无法处理屏幕空间之外的物体,这容易导致各种错误的渲染结果。SSGI摘要
SSGI的独特优势
流明(Lumen)
Luman是虚幻5引擎提出的最新实时GI技术。尽管实时光线追踪也可以实现实时GI,但它依赖于硬件层面的实现而且需要大量的采样才能实现比较好的渲染效果。而Luman不依赖于硬件实现,可以应用到大量对实时性有需求的环境中。光线跟踪缓慢
取样困难
低分辨率滤波场景空间探测器照亮全像素
流明:SDF光线追踪(Fast Ray Trace in Any Hardware)
Luman的渲染过程可以大致划分为4个部分,首先是进行高效的路径追踪。考虑到不是所有的硬件都支持光线追踪的加速,Luman使用了SDF的方式来实现这一过程。什么是SDF
由于直接把场景转换成SDF往往是比较复杂的,我们可以先对每个物体转换为SDF。对于平移变换和等比例的放缩,物体坐标系下的SDF都可以很容易地转换为场景坐标系下的SDF。每个网格SDF
需要注意的是当物体比较薄时要进行一些额外的处理。薄网格的SDF
SDF的一大优势在于它可以非常容易地得到光线步进的长度:在任意p位置前进SDF(p)的距离总是可以保证不会和场景出现相交。带SDF的射线跟踪
同时,SDF在进行cone tracing时也可以很容易地计算出遮挡面积比例的估计。使用SDF的圆锥体跟踪(即软阴影)
生成SDF时可以考虑使用一些稀疏的数据结构进行表示。稀疏网格SDF
SDF甚至可以表达物体的不同LOD。网格SDF LoD
在低LOD下结合sparse mesh可以极大地减少模型的存储空间。稀疏网格SDF2
当然,直接将场景中物体的SDF组合到一起在进行计算时仍然是过于复杂的。真实场景中的光线跟踪成本
每条光线上都有许多物体
这里可以结合屏幕空间的概念只考虑相机视野范围内的物体,将它们的SDF融合为一个低精度的全局SDF。全局SDF
在这个低精度的全局SDF上进行ray tracing可以极大地降低计算压力。使用全局SDF的光线跟踪
除此之外SDF也可以结合mip的思想,近处的精度高而远处精度低。缓存摄影机周围的全局SDF
流明:光辉缓存(Radiance Injection and Caching)
接下来我们需要把光照信息注入到场景中。在Lumen中使用了mesh card来保存物体在6个正方形上被光照亮后的结果。网格卡-6轴对齐方向上的正交相机
网格卡-6轴对齐方向上的正交相机2
对于每个card我们还需要记录物体表面的albedo、法向以及深度等信息。生成表面缓存
所有物体的表面信息会统一记录到一张标准大小的纹理图像上,称为surface cache。生成表面缓存2
生成表面缓存3
根据物体距离相机的远近还可以设置不同的card分辨率以降低存储和计算需求。视图相关的每对象卡片分辨率
我们的目标是把物体所有光照的radiance记录到surface cache上。我们如何才能“冻结”表面缓存上的照明
在Lumen中整个光照可分解为直接光照、体素化光照以及间接光照三部分。光照缓存管道
直接光照相对比较简单,我们只需要考虑光源直接照射到物体上反射的radiance即可。对于阴影中的物体则可以结合shadow map来进行处理。直接光照
直接光照2
得到直接光照后Lumen会对场景进行体素化来存储物体在6个方向上直接光照的亮度。而且体素化后的光照信息会在相邻帧上进行传递,随着时间的积累会得到光线多次弹射的效果。全局SDF无法采样表面高速缓存
对整个场景的光线缓存的体素剪贴图
通过短射线投射来构建体素面
通过4x4x4格子过滤大多数对象
将灯光注入到剪贴图中
最后计算间接光照时只需要从surface cache上进行采样,利用上一步体素化光照作为照明即可。间接光照
间接光照2
具有4个探针插值的每像素间接光照
把直接光照和间接光照相加就得到了环境中光照的信息。联合光照
光照更新策略
流明:内存探头(Build a lot of Probes with Different Kinds)
屏幕空间探测器(Screen Space Probe)
有了光照后我们需要在物体表面通过采样来计算着色,此时我们需要在场景中放置探针(probe)来采样光照。Lumen中直接在屏幕空间里放置probe,每个probe会同时记录光线前进的距离以及收集到的radiance。屏幕探针结构
屏幕探针结构2
屏幕探针放置
探针插值的平面距离加权
检测不可插值的情况
屏幕探测图集
屏幕探测图集2
屏幕探针抖动
重要性抽样(Importance Sampling)
为了提升渲染质量我们需要使用重要性采样的技术尽可能在比较重要的方向上进行采样。重要性抽样
重要性抽样2
回到Monte Carlo积分的公式中,我们需要把光线更多地分布在光照比较强或是接近物体法线的方向上。重要性抽样3
Lumen中会对上一帧以及四周相邻的probe进行平均来估计光照的分布。来自最后一帧探测器的近似亮度重要性
而对于物体法向附近的方向,Lumen会估计probe附近的法向分布。在附近的正态分布
附近的正常积累
结构化重要性抽样
基于光照和BRDF的固定预算重要性采样
基于光照和BRDF的固定预算重要性采样2
基于光照和BRDF的固定预算重要性采样3
基于光照和BRDF的固定预算重要性采样4
去噪和空间探测滤波(Denoising and Spatial Probe Filtering)
为了进一步提升渲染效果我们还需要对图像进行滤波降噪。去噪:探测器的空间滤波
去噪:从邻居那里收集光线
去噪:从邻居那里收集光线2
夹角距离不匹配
夹角距离不匹配2
夹角距离不匹配3
世界空间探测器和射线连接(World Space Probes and Ray Connecting)
世界空间光线缓存
世界空间光线缓存2
连接射线
连接射线2
连接射线3
连接射线4
连接射线5
放置和缓存
没有世界空间探测器
有世界空间探测器
流明:阴影(Shading Full Pixels with Screen Space Probes)
使用屏幕空间探测器着色全像素
与SH的最终集成
流明:性能与结果(Overall, Performance and Result)
不同跟踪方法的速度
SDF生成
过程
SSGI关闭
SSGI开启
表现
表现2
表现3
表现4
表现5
表现6
结论(Conclusion)
真实渲染的复杂性
引用
- 本文作者:樱白 - Cherry White
- 本文链接:https://cherry-white.github.io/posts/fd7746b4.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!