本期为GAMES104《现代游戏引擎:从入门到实践》课程视频公开课文字实录第23期。GAMES104课程由GAMES(图形学与混合现实研讨会)发起,游戏引擎技术专家王希携手游戏引擎一线开发者共同研发。课程共计22个课时,介绍了现代游戏引擎所涉及的系统架构,技术点,引擎系统相关的知识。
关注公众号GAMES104,可查看往期文字实录内容。
本期编辑:Piccolo 社区编委会 彭渊
今天我们继续讲解渲染部分的最后一节课,主要会介绍渲染管线、后处理和其他相关知识。
GAMES104的定位是一门通识课。游戏引擎是一种特殊的计算机软件,几乎涵盖了计算机科学的所有知识点。在全球范围内,我觉得能够完整了解现代游戏引擎的所有知识点的人屈指可数。我们无法通过短短的二十多节课就能够介绍完游戏引擎开发过程中的所有知识点,即使只介绍渲染相关的知识也不可能。
在课程设计之初,我们就认为,这门课的核心目标是帮助大家建立概念,建立知识体系。所以课程中会经常强调,不用特别关注一些公式。很多同学都应该具备大学高等数学的基础,有些公式可能需要花费很长的时间进行推导和理解。但公式并不重要,重要的是思想。大家如果理解了这些方法的思想的话,在未来的工作中就可以举一反三。
很多同学将来未必会真正从事游戏引擎开发的相关工作。但如果真正理解了游戏引擎设计过程中遇到各种各样问题时,是如何选择解决方案的思路的话,在自己未来的工作开发中就可以触类旁通。这些方法论对于大家以后从事游戏、技术美术(Technical Artist)、WebGL或者数字孪生等工作的开发,都是很有帮助的。
在本门课程中,我们会尽我们所能将目前行业内的比较主流的解决方案以及知识体系介绍给大家,为大家未来的学习打好基础。
有过科研经历的同学们可能知道,老师会让大家去看前沿的科研论文,有时候花费了一天时间去理解一个算法,仍然是不得要领。然而如果有人告诉你,这篇论文的核心点就是哪些的话,再去读论文,就会豁然开朗。因为大家这时候知道需要重点关注哪些内容,而可以忽略哪些细节。这也是本门课程的初衷。
有些知识点大家一时用不上也没关系,也许几年后在解决另外一个问题时,就会想起来在GAMES104课程上建立了这样一个印象,正好可以解决手头的问题。因此,有些知识点暂时听不懂也没关系,但一定要听懂这些方法,听懂它的整个知识结构,以及人们是怎样将这些知识串在一起的。这样,同学们就建立了一个现代引擎的知识体系。
整个课程的渲染部分着力于帮助大家建立游戏渲染的基础概念。希望大家不要轻视课程中提到的每一项技术,因为这些技术都是经过成百上千名优秀的工程师在实践中总结出来的,这就是这些技术的真正的价值。
01「环境光屏蔽」
我们先从一个相对简单的效果开始——环境光遮蔽(Ambient Occlusion,以下简称AO)。大家可以看到,下图中的场景没有任何渲染效果,也没有任何着色效果。但场景呈现出了非常清晰的结构空间感,其中有很多的几何细节,而在光场中表现出了一定的明暗关系。这个明暗关系和传统的明暗关系不一样。比如图中的凹陷部分会显得比较黑,而突出部分就显得比较亮,这很符合人类生活中的实践。这种细节的光影变化也满足Kajiya的光照方程,但在渲染时很难实现这种效果。因为这种结构非常小,我们一般称之为中尺度结构(Meso Structure)。当天空光对其进行照明时,这种结构表面的几何细节会有相应的响应。
大家平时可能注意不到AO的效果,但在现代游戏渲染中,AO的效果是非常明显的。下图的场景中应用了PBR材质,呈现出了强烈的光照,但这张图中缺少了AO效果。如果我们打开AO效果,场景就会呈现出更强烈的立体感。因为人类大脑对这种光影的明暗变化非常敏感,并且可以根据光影的明暗变化构建3D视觉感知。
大家一直以为3D视觉是基于双目视觉,然而,双目视觉只是空间立体感知(Perception)很重要的一个基础。因为即使只有一张平面图片,人类也可以在大脑中构建出一幅具有立体感的画面,这是通过光影变化来实现的。而在光影变化中,AO是一个非常重要的视觉元素。很早以前,大家就意识到了AO的重要性。比如前面课程中介绍的渲染方程,对于表面上的每个点,在它的可见正半球面上,只有部分能够看见天光,而有些部分会被其周围的几何结构遮挡住,因此就产生了AO效果。
回忆一下之前介绍过的Cook-Terrence材质模型,其中有一项是G项(即Geometry,几何项),G项处理的就是几何结构的自遮挡。AO和也类似于几何自遮挡。如果将相机拉得足够远,AO所表现出的数学方程和BRDF十分接近。因此AO的尺度是相对的,取决于相机的远近。
最初,对于AO效果的实现十分简单粗暴。比如在Zbrush中雕刻高精度模型时,会雕刻出很多细节,如下图所示。图中呈现出了人脸和眼角处的皱纹。当将高精度模型转换为游戏中使用的低精度模型时,就会将这些细节烘培到法线上。但法线体现出的只是一种明暗变化,而无法体现出几何的变化。在渲染时,也无法根据法线来实现几何上的变化。因此,在现代的很多建模软件中,都提供了AO烘培功能。如下图中的右图所示。
如果在渲染过程中使用了AO贴图,就会呈现出上图中间的效果,和左图中的效果完全不同,明暗对比更加明显。这种预计算的AO在十几年前就应经在游戏行业中应用,而且效果非常好。
即使后面又出现了实时计算AO的方法,但这个方法还是不能被取代掉。因为AO计算都是基于几何结构的,没有几何结构,实时方法也无法计算出来。因此上述方法多用于角色的表达,在制作角色时,通常会生成一张AO贴图。
这种方法也是使用了之前提到过的空间换时间的思想。预先计算好AO的信息,在程序运行时,只需要直接进行采样。但这种方法只能表达单个物体。当对整个场景进行AO处理时,上述方法就无法胜任了。比如地板上有一张桌子和一把椅子,桌子和椅子的位置一直在变化,这时我们就无法预先烘焙好它们的AO效果。
这里我们介绍一个最基础的方法,叫屏幕空间环境光遮蔽(SSAO,Screen-Space Ambient Occlusion)。该方法的思路十分简单,当我们使用相机渲染场景时,除了可以生成场景的颜色信息,还可以生成场景中物体的深度信息。如果将每个像素点的深度信息连在一起,就可以得到一个高度场(Heightfield)。有了高度场信息之后,我们就可以估算每个区域的自遮挡关系。
算法的实现并不难,首先从人眼(相机)射出去一根光线,就是说我从这个眼睛射出去一根光线,这条光线会和物体相交,我们可以得到交点的坐标(x,y,z)。给定一个半径,在这个半径范围内的球形区域中随机撒若干个采样点。然后再利用相机投射计算出这些采样点的深度,如果一个采样点的深度比Z-buffer中的深度更近,说明该采样点位于可以看得见光的地方。如果采样点的深度比Z-buffer中的深度更远,则说明该采样点被当前绘制中的某个几何体遮挡了。
这里一个很重要的思想就是屏幕空间(Screen-Space)在游戏中,我们对整个世界有一个完整的几何表达,但在实际计算过程中,如果我们真的对上万个物体和上百万的面片进行各种各样的几何运算,效率是非常低的。后来人们发现,可以利用屏幕空间中的深度信息来简化运算,因为屏幕空间深度信息是对世界的一个局部采样的几何信息,利用这个局部采样的几何信息,我们就可以实现很多算法。今天我们介绍的屏幕空间环境光遮蔽就是利用了这个原理。
后面我们会提到的屏幕空间阴影贴图、屏幕空间反射、屏幕空间全局光照,都来源于这个思想。对于SSAO来说,比如我们随机撒了60个采样点,如果有32个点可见,我们就可以知道,着色点周围大约有一半的空间被挡住了,所以光强只能有原来的一半。这样我们就可以给出一个简单的方程,对于N个采样点来说,如果有O个被遮挡了,那么该点的光强就是。
这就是Screen-Space最古老的一个方程,思想也很简单,但我认为这个方程是错误的。首先,对于一个着色点来说,着色点的受光面并不是一个完整的球,而是一个半球面。当对着色点周围的整个球面进行采样时,按照概率分布,将会有一半的采样点分布在下半球面,而下半球面本来就没有AO效果。如果按照这个方程进行计算,算出来的光强就是正常的1/2,因此这个方程用的非常少。大家都意识到这个方程有问题,然而遗憾的是,提出这个方程的原始论文就是这么写的。当然在实际应用中,我们可以将被遮挡的点的数量乘以1/2。重要的是,大家需要理解的是屏幕空间这个想法,即从人眼射向平面中像素点的光线,可以对一个立体空间进行采样,这个思想是非常重要的。
现在大家已经意识到,这样进行采样是多余的,因为我们只需要对半球面进行采样。基于这个思想,我们引入另外一个方法。如果我们知道着色点的法线朝向,沿着法线方向,我们只需要对半球面进行采样。因此就产生了一个改进算法,一般称为SSAO+,这样就可以减少一半的采样点,并且能够解决前面我们提到的问题。所以最早期的SSAO算法运行时会让整个画面变暗,现在我们知道这是因为整个算法存在着一定的问题。
下图中就是有SSAO和没有SSAO效果的对比,有SSAO效果的图像的立体感会好过没有SSAO效果的图像。这个方法也存在一定的问题,比如图中路灯背面的地面上也出现了很强的AO效果,这个AO效果是错误的。因为地面距离路灯柱已经很远了,路灯柱不应该对地面产生强烈的AO效果。但在屏幕空间中没有办法区分这一点,所以会产生失真(Artifact)。当然大家也在不断对这个算法进行改进。
下面我们介绍另外一个算法——HBAO,HBAO已经部分解决了前面的问题。我们需要得到一个着色点在球面空间上的可见性,假设我们从这个点出发,沿各个方向转动,寻找到一个光线能够越过该着色点的最高的相邻几何点,我们称之为仰角(Pitch Angle)。想象一下,我们位于一个山谷中,向外射出一束光线,那么这束光的仰角要多大才能够越过最高的山脊,这就是我们需要寻找的点。如果在山谷的周围一圈都找到了这个点,就可以得到下图中右上图所示的高低起伏的覆盖图。如果能够得知着色点周围的半球形空间的仰角的采样信息,我们就能够估算出有多大面积的球面被遮挡了。我们可以理解为一种离散积分。
HBAO方法中还引入了一个Hack,即一个衰减函数。当山距离着色点过远时,则认为山体对于该着色点的AO没有什么影响,这样就可以处理SSAO算法中出现的失真现象。HBAO算法的实现涉及到很多细节,我们不进行详细展开。具体来说,算法还是使用了Ray Marching方法,以一定的步长向前测试。同时将深度缓冲作为高度场,并直接在2D空间中追踪光线,以水平角(Horizon Angle)来估算AO。
在进行光线步进时,每向前步进一步,都会稍微抖动(jitter)一下。这是因为如果光线每次行进的方向都相同,采样率就会很低,会出现一些很明显的走样。大家如果学过信号和系统就会知道,当一个信号采样率特别低,而且采样的滤波器非常规整的话,所采样出来的信号本身就带有很明显的Pattern(编者注:这里的Pattern可以理解为模式、样式或者图案,意为一种规整的表现)。因此算法中加入了一个Trick。最终,在像素着色器中,会一步步沿着深度缓冲区步进,寻找最大的仰角。这样寻找一圈,就可以得到遮挡信息。这就是HBAO算法的核心思想。因此,HBAO算法本质上是基于半球面积分的一个想法。
HBAO算法的结果比SSAO算法的结果要好很多,而且能够解决SSAO算法中出现的失真问题,但也存在一个问题。前面的课程中曾经提到过,我们可以认为天空是一个球体。而对于球体来说,从天顶射下来的光线和从水平方向射来的光线,贡献值是不一样的。我们在介绍漫反射模型时曾经提到过Lambertian模型,Lambertian模型中有一个很重要的因子cosine,当光线越靠近天顶时,如果光线和天顶之间的夹角是θ,那么就接近于零。光线的Irradiance接近于100%被表面吸收。而散射是向四面八方的均匀散射,因此无论从哪个角度观察,所看到的的都是100%的散射强度。但同样亮度的光如果以斜45度角入射,那么散射出来的光就是度,大约是0.7左右。
而HBAO和SSAO算法在计算过程中都没有考虑这个因素,所以计算出来的值都是不正确的。现在大家使用的最多的方式是GTAO(Ground Truth-based Ambient Occlusion),该算法号称计算结果接近离线算法计算出来的结果。该算法的论文中所给出的Demo也和使用蒙特卡洛积分所计算出来的结果几乎一样,因为算法也考虑了表面法线的影响。这样一来,当光从四面八方入射时,倾斜的光线的权重会被降低,而靠近天顶的光线的贡献会很大。
GTAO还有一个值得一提的地方,如果能够计算出来AO值,那么就能够猜测光线射入之后,在黑暗处来回反弹的亮度,我们称为Multi Scattering。因为AO只是计算了遮挡,和真实的物理效果不一样。假设人位于山谷的谷底最黑的地方,而由于山谷两侧的反光,谷底并没有那么黑,这才是真实的物理效果。而且谷底的颜色和山谷周边的颜色相关。比如山谷是绿色或者红色,那么在谷底看到的AO也是有颜色的。这需要进行很多积分才能得到这样的结果,但这是GTAO中一个非常好的想法。
GTAO方法根据不同的AO值,对数值进行了大量的分析,并得出了一个结论——AO值实际上和Multi Scattering有一定的关联度。这个关联度不是线性的,但符合某条曲线。于是作者给出了一个三阶多项式的方程,使用这个方程,就能够根据AO值估算出光线来回反弹的最终结果。
该方法和我们上节课中介绍大气散射的方法有异曲同工之妙。上节课中,我们也是计算出了单次散射,然后就能够估算出多次散射的结果。虽然原论文没有给出理论证明,但我个人觉得这种方法在数学上是有基础的。
回忆一下,我们在计算BRDF模型时,也并不知道微表面上的细节结构,但我们使用了一个统一的量,叫做粗糙度(Roughness),就表达了微表面的几何不平整度。而这里的AO值,就有点像小区域内的粗糙度,可以表示出某点周围对该点的遮挡性。类似于BRDF中的G项,假设表面的分布符合一个统计学分布,如果使用统计学的方法进行计算,这其中肯定是有一个关联度的。但这个关联度可能是个积分值,也可能是个更复杂的方程。而再复杂的方程,在某些情况下,我们都可以用一个多项式来进行拟合,这在数学上也是成立的。因此,GTAO方法的这个发现,是有数学基础的。同学们如果有兴趣,可以进行深入的研究。
对于GTAO方法自身来说,并不需要花费这么长时间进行介绍。但这个思想特别值得借鉴。从最开始的SSAO,大家想到了利用屏幕空间的方法,不用对整个世界进行几何上的运算。而对于HBAO,我们可以认为这是一个过渡性的方法。而到了GTAO方法,则是从数学上彻底将这个问题解决了。作者同时还观察到了一个很重要的性质,并给出了一个拟合方程。这样一来,AO的效果就从一个简单的黑白分明的效果变成了彩色的有色相的效果。
最近几年,还有一个越来越火的方向,这就是实时光线追踪的Ambient Occlusion。实时光线追踪的数学基础核心就是现代GPU能够快速进行光线投射(Ray-Casting)的计算,并给出是否命中物体的结果。对于屏幕上的每个像素,我们可以发射出射线,然后就可以得到周围有没有遮挡的结果。
当然,如果希望结果正确,我们需要为屏幕上的每个像素点在它的半球面发射出很多条光线,但即使现代的GPU也没有这样的算力。真实的做法是在每一帧对于每个像素,只向外发射1-2条射线,但会在时序上对数据进行收集,这样就够完成球面上的采样。我们不展开介绍这个方法,希望同学们关注这个方向的发展。对于AO来说,GTAO和基于光线追踪的AO,是比较前沿的方向。