【RTR4笔记】 第十一章 全局光照(下)

漫反射全局光照、高光全局光照

Posted Kongouuu's Blog on March 29, 2022

11.5 Diffuse Global Illumination

上一部分讲的是进行全局光照的一部分(遮蔽)的模拟,这一章则会开始讲如何去模拟一个全局的光线弹射的效果。

11.5.1 Surface Prelighting

我们在开篇的时候有提到一种叫 Radiosity 的手法,就是把所有的物体切割成不同的面当作间接光源。不过这个办法一般来说我们只能拿来做离线渲染,因为实时的去解算整个画面的辐射是个非常耗时的事情,基本上不能拿来应用。

Surface Prelighting 的核心思路就是把场景分为静态物体,和动态物体, 然后离线的为静态物体计算光照信息。

这种预计算的行为一般被称作烘焙,可以让我们的静态场景,例如建筑物本身拥有不错的全局光照效果,那么至于动态物体呢?一般来说动态物体对场景的占比比较小,不会有太多间接光来对环境干涉,所以一般来说使用基于屏幕的手法(例如SSDO) 去处理动态物体与自己或者环境之间的全局光照细节就可以了。

这里主要思路就是计算所有静态面的辐照度。不过问题出来了,预计算的辐照度按常规方式去进行只能为平滑的表面写入辐照。如果我们的表面会通过法线贴图细化,提前计算的辐照度会顾虑不了这样的变化。不过这单纯只是最基础的辐照度预计算的存储和计算方式的问题。单纯的因为这个方法开发的时候没有去过多的考虑法线贴图的使用吧。

11.5.2 Directional Surface Prelighting

相较于单纯的在每一个表面的面片上直接的储存对着法线的光照信息,我们可以进一步优化这个光的储存。被提出的方法就是使用 SH, 把 SH 的系数写入贴图里面放到这些表面上面;相较于一个单纯储存辐照度的纹理来说,储存球谐信息可以更准确的定位这个表面的兰伯特漫反射往不同方向的贡献。

当然我们其实是不一定需要用到什么球谐函数半球函数。我们在前面的部分是为每个物体放一个贴图表达自己的辐照度,不过这个辐照度的信息是缺乏方向性的,也就是我们没办法说根据他的法线贴图等额外信息去做额外储存 (可能读取不便), 所以我们需要一个可以把方向性也带进来的储存方式。比如说 Crytek 所使用的就不是球谐函数,而是带方向和颜色的纹理图。

当然,一直到这里,不管我们换什么方式的储存,始终只是对静态物体渲染的表达。虽然说好听点叫烘焙,不过本质上其实跟离线渲染差得不太多。这里因为考虑的是一个漫反射的全局光照,所以摄像机是可以自由移动的,这大概是唯一动态/实时的地方了。

11.5.3 Precomputed Transfer

前面的两个方式都是单纯考虑静态物体,唯一动的只有摄像机,这样的效果对实时渲染来说是比较不理想的,因为就连光源也必须是静态的。

PRT

本小节讲的办法就是在静态的场景下允许光源去变换的一个方式,Precomputed Radiance Transfer。这个手法的核心思路就是提前去计算光照对每个物体之间的传输影响(遮蔽信息) , 也就是 Transfer 。 相当于提前计算好每个物体往所有方向的遮蔽信息,以及应该接收到多少光源。

对于普通的光源来说,我们会提前为每个表面计算好每个光照对于这个表面的影响;这样在运行时我们可以动态的去开、关、调整光源的颜色。对于环境光,比如我们使用一个 SH 来代表我们场景的环境光,我们同时也可以用 SH 来储存表面对所有方向的光源接受度,然后只需要计算两者的内积就可以得到环境光对表面的影响;并且光源也是可以旋转的。

Enlighten by Geomerics

上面提到的 PRT 相关的内容纯属是只考虑的直接光照,换个方式说目前提到的多半只能算作局部光照。如果要在 Precomputed Transfer 中达到一个全局光照(考虑间接光)的效果,整体的计算会复杂的许多;一个比较流行的系统是 Enlighten by Geomerics。在这个系统中,我们会把很多的静态表面定义成 次要光源,并且提前计算这些定义好的次要光源与普通表面之间的 Transfer 值。 这样在运行的时候,我们只要先点亮这些间接光源,就可以用过计算来得到一个全局光照的效果。

不过这个办法的缺陷就是开销问题,我们设置的间接光源越多我们越难以储存大量的信息,并且计算时也会有过大的开销。不过这个方法他只是一个思路,实际的实现方式是可以有很大的改变的。例如我们的光源和接收者都不一定需要是在物体表面上,可以是单纯的均匀的遍布在场景上面。例如我们把场景分成不同小块,然后先试着点亮所有小块来表达光照信息,然后再通过小块内部的接收者的 Transport 来得到间接光照的计算。

11.5.5 Dynamic Diffuse Global Illumination

这一节会开始介绍不需要预计算的一些方式。

我们之前聊过把直接接受光源的点当作间接光源的 Radiosity, 这样的做法是没办法在实时渲染中使用,不过启发了另一种相似的手法 Reflective Shadow Maps 。这个做法就是在记录阴影贴图的时候顺便同时记录光源摄像机看到的面的法线、辐射通量、颜色等信息。这些信息会被当作间接光源,在常规渲染的时候我们会把一定范围内的 RSM 间接光源纳入光照考量来做全局光照。不过这样的做法很难去考虑遮挡就是了。

11.5.6 Light Propogation Volumes

LPV 的做法是把场景分成大量的 3d网格 ,然后为每个网格计算内部的光照情况。

实际的做法是首先通过类似 RSM 的办法为直接光照的网格生成信息,然后再一步一步的把邻居的网格写入光照信息,直到收敛。 这样的做法是不考虑遮挡问题的,因为我们是对邻居网格一一操作,不过后面还是有人研发出稍微处理了一些些遮挡。

11.5.7 Voxel-Based Methods

体素,也就相当于类似 3D 的纹理元素,是现在比较流行的可以用来做全局光照的东西。 一般提到全局光照就会想到像 VSGI, SVOGI 这样的算法。

体素的全局光照主要是基于 简化 用光线追踪 提供间接光照 的做法来进行的。基本上思路是下面这样:

  1. 先生成 阴影贴图做遮挡计算
  2. 把场景体素化成方块。每个面会储存每个投影到面上的三角形的颜色,法线等信息。并且每个方块都会储存一个光照的信息
  3. 把已经生成的体素化场景进行 Mipmap 操作
  4. 为要着色的顶点进行 Voxel Cone Tracing (体素锥形查找) , 相当于是使用一个锥形的形状去进行光线追踪,并且每次的查询都会根据锥形在当前长度的大小选取正确的 Miplevel

本质上就是把场景转换成适合锥形查找的格式,来简单的模拟光线追踪的过程。这里的讲法忽略了很多细节,不过大致上就是这么一回事。

11.5.8 Screen Space Methods

简单来说屏幕空间能提供的信息太少了,拿来做环境光遮蔽还可以,如果要考虑漫反射的间接光照的话效果过于难看。因此屏幕空间的算法大部分情况下都是用来对比其他的 GI 的结果,来称赞说其他的算法的效果为什么比较好。

11.6 Specular Global Illumination

11.6.1 Localized Environment Maps

当我们要渲染高光的时候,我们会考虑到物体的不同的反射 lobe, 来考虑光的采样。就像使用环境贴图时一样,这方面比较早被提出来的手法也是通过使用环境贴图来适配对不同 lobe 的采样。

这个手法的核心就是把场景分成大量的小块,并且每个小块都有自己的一个环境贴图,局部环境贴图是在我们计算完直接光照后,在每个放探针的地方往六个方向去渲染场景得到的结果。这里我们不讨论实际储存方式,大体上就是这么一个概念。

不过很明显的问题就是如果我们物体并不在一个区域的中间,那么效果会不太理想;当然也有使用什么反射代理的方式让我们原理探针的表面可以更精确的拿到更合适的环境光照数据。

另一个问题就是这样的手法目前来看只适用于离线,因为我们很难事实的去进行大量的环境图捕捉,加上我们还需要对所有的面去进行一个滤波操作来得到不同 lobe 要采样的范围。

11.6.2 Dynamic Update of Environment Maps

一般来说这样的手法是过于昂贵的,很难拿来做一个动态的场景。常规的情况下我们更情愿在需要的地方使用屏幕空间的效果来解决一些动态方面的问题。全局方面肯定是没办法全部动态的,不过我们可以做到部分动态,比如说就几个面是需要动态的去更新的。

当然我们也可以在场景变化,例如有动态物体或者灯光变化的时候,去慢慢的更新我们的局部环境贴图。因为间接光照这部分他的更新并不会特别特别直观的表现到我们的屏幕上面。所以一个普遍的做法是我们把对这些局部环境贴图的更新拆分到不同的帧去进行,比如说一帧就去做一个面的渲染这样。当然在这之上还有一些例如存储或者滤波的问题需要去进行压缩和加速倒是,不然还是会对我们的程序拥有比较大的影响。

如果我们不考虑物体动态,只说光源动态的话,我们完全可以让每个环境贴图离线计算好几何信息,并且运行时只更新光照内容,这也是可以的。

11.6.3 Voxel Based Methods

上面提到的局部环境贴图办法是可以顺利的在实时渲染力面运行,不过效果一般来说比较差强人意。因为如果我们想要提高整体的精度需要花费大量的储存和计算的开销,但是在低精度的情况下这样呈现的结果,起码对于光滑表面来说是比较差的。

使用体素的办法上面已经提到过了,大致的流程和漫反射是一样的;不过漫反射我们会往整个半球范围进行 Cone Tracing, 而渲染高光的话则会根据自己的 lobe 去判断。比如说如果我们需要一个较为镜面的材质,我们可以让我们的锥形变得非常的细,这样会变得很有镜面反射的效果。

11.6.4 Planar Reflection

对于反射来说,例如镜子、玻璃、水这样的材质。我们在特殊的情况下可以把已经渲染的场景直接套用到这些材质上面。比如说我们场景的地板是个水面,那么我们在渲染完场景之后可以在水面上把已经渲染好的场景重现一遍作为倒影。

11.6.5 Screen-Space Methods

基于屏幕空间的做法, SSR (Screen-Space Reflection) 的基本思路就是使用我们的深度缓冲去做路径追踪。例如我们要给一个点计算反射,我们就在深度缓冲里找到这个点,根据反射方向在里面进行路径追踪直到碰撞,然后通过碰撞的点的颜色来计算反射的值。

上面提到的是最基本的方式,不过这个做法有一个问题,那就是深度缓冲因为我们使用透视投影的问题,他的深度是非线性的,这就代表我们沿着路径上的采样点会是非均匀分布的,不过这里有一个方法是使用 digital tifferential analyzer (DDA) 来确保每一次的步进在世界空间中是相等的。

很显然这样的做法看似只能用来做镜面反射。不过后来有很多不同的做法可以让其他材质的反射成功的通过同样的手法制作出来。第一种是我们把渲染好的场景进行不同程度的滤波,然后我们会根据材质的 lobe 去判断最后取的颜色来自哪个滤波。 第二种方法是,既然我们在做路径追踪,那我们可以完全的直接使用蒙特卡洛积分的方式对我们的 lobe 进行多次的光线追踪采样。

为了提升路径追踪的效率,被推出了一个基于层次的查找的追踪手法。他的基础概念是当我们目前的步进没有碰撞,那么下一次步进的距离会更长 (或者说下一次步进的分辨率更低), 然后如果检测到碰撞,则退回上一次步进,提高精度重新测试一遍。这样理想的情况下在没有碰撞的行径上可以省下不少的时间:

屏幕空间的反射在局部的范围内可以达到不错的反射效果。不过作为一个全局光照的解决办法来说他局限性太强了,毕竟屏幕空间的信息是十分有限的。如果我们要考虑到其它间接光的影响的话可能并不是很方便进行搭配。

11.7 Unified Approaches

这个章节提到过的所有的办法都只能处理部分的全局光照情况。例如说我们用环境光遮蔽处理遮挡问题,使用VXGI进行间接光照的模拟等;但这些方法全都是在处理全局光照的一部分。我们很显然是可以通过把很多种不同的全局光照的技术放在一起,并且来达到一个非常真实的画面,不过在特定的场合这样的不连贯性可能会让结果略微失真。毕竟所有的不同方式虽然在处理不同的部分,但是物理上来说他们并不会保持一个像能量守恒这样的情况,可能会互相有一些不好的影响。

为了得到一个连贯、完全、且真实的实时全局光照,最好还是使用光线追踪。现在光线追踪还不能比较完美的拿来直接使用,因为使用它的限制过多了,例如硬件设备的适配等。加上如果我们想要真实的效果,例如多次的弹射,这样的开销是难以想象的,电脑一般来说很难去处理这么多的光线以及弹射问题。假如我们要给一个光滑的表面进行多次的弹射,同时场景对每个光进行两次弹射的追踪,那一个普通的 1920x1080 分辨率的画面就要进行 1920x1080x采样数^2 这样数量的光线追踪,我想这是难以在实时的情况下进行的。