本期为GAMES104《现代游戏引擎:从入门到实践》视频公开课文字实录第14期。本课程由GAMES(图形学与混合现实研讨会)发起,游戏引擎技术专家王希携手游戏引擎一线开发者共同研发。
课程共计22个课时,将介绍现代游戏引擎所涉及的系统架构,技术点,引擎系统相关的知识。为配合学习实践,课程组在 GitHub 上开源了小引擎Piccolo,上线1个月即获得了2900+star, 累计下载量已超过20000+。
以下内容为公开课视频转文字版本,为阅读通顺,有删减
01「前言」
今天我们继续介绍渲染。我个人认为,第5讲是难度最大的一讲,我们花了很多时间来准备课件,但是我并不是特别有信心我们能够讲好。
因为今天要介绍的主题所包含的内容特别丰富,如果将今天讲课的内容分成十节课来讲,也没有太大问题。今天讲座的主题就是绘制。在上一节课中,我们介绍了网格、材质、纹理等知识点。这些数据放在一起,GPU如何组织这些数据,如何处理这些数据?这时就需要GPU进行绘制。绘制就是渲染的核心。
02「渲染方程及其挑战」
绘制需要考虑的第一个问题就是光。光子从光源发射出来,经过反射被吸收或反弹,直至最后进入我们的眼睛,这一过程的原理是什么?第二个问题就是光和物质之间如何相互作用?物质对光是进行反弹,亦或是吸收,还是让光线在物质内部不断折射?物质和光之间如何相互作用决定了这个物体外表的观感。最后一个问题就是Shader的具体实现问题。Shader中有很多复杂的运算。所以今天我们介绍的光照、材质和着色器,实际上是一个非常庞杂的系统,我们竭尽所能将这些内容放在一讲中。我们将过去几十年全世界最聪明的理论科学家,以及图形学的科研工作者和工程师们的各种聪明想法集结在一起。在这些想法中,有很多大胆的假设,以及一些“Hack”技术,并且在实践中行之有效,于是最终形成了非常棒的游戏画面体验。
在本节课中,会出现很多晦涩难懂的数学公式。如果大家觉得难以理解,不必着急,可以逐步建立对于这些概念的理解和认知。
上图中就是James Kajiya在1986年于SIGGRAPH会议上提出的渲染方程(论文名:The Rendering Equation)。概括来说,全世界所有的渲染用上述的六维方程就可以表示。这个方程看上去很复杂,但并不难理解。
假设观察者位于一个点,从该点观察一个物体表面上的点X,X可以是二维空间的点,也可以是三维空间中的点。首先,我们需要有一个观察角,即上式中的, 就是观察角。其次,我们需要考虑点X会不会自发光,如果X会自发光,那么X在角度上射出来的光也会被看到,这就是上式中的项。然后,如果点X被很多其他的周边光源照亮,X所反射出来的光照也会被看到,这些光照构成了一个积分项,即从球面上的所有可能的入射角发射出来,并投影到我们所观察的表面上的光。需要注意的是,这里要乘以一个项,项可以视为斜着照射到表面的光线在垂直方向上的分量。项理解起来也很简单,大家可以从地球不同纬度的气温变化来理解,太阳光照射到地球的不同纬度上,纬度高的地方就会冷,纬度低的地方就会热。因为在同样的单位面积上,对于纬度高的地方来说,所接受的光照需要乘以高纬度处的球面上的垂直分量,其实就是法线和太阳光线之间的夹角。如果在极地位置,项几乎为零。在赤道位置,则接近90度,所以接收到的能量会更高。
现在,我们介绍另外一项,即散射方程(scattering function)。散射方程的作用是,在给定的入射角和出射角的情况下,计算大概有多少能量会被反射到方向。这其中具体所发生的情况,比如光电子反应等,Kajiya的论文中没有进行进一步解释,但引入了一个四维方程来计算这一项,这就是著名的BRDF方程(Bidirectional Reflectance Distribution Function)。
对于从事图形学研发工作的同学来说,BRDF方程是大家无法绕开的一个概念。Kajiya用这个六维方程就将渲染的内容全部讲完了。但自从Kajiya在论文中提出BRDF开始,已经过去了三十多年,整个渲染行业一直在致力于精确求解BRDF方程。
上图是一个很经典的示例,展示的是书桌的一角。
在上图中,光线直接照射到书桌表面,将表面照亮,而有些地方会被其他物体遮挡,从而会产生阴影(Shadow)。大家在玩游戏的时候,最在乎的就是阴影效果的质量。然而,阴影的计算比大家想象的要难得多。而且还有一个更难处理的问题,当光线照射到表面之后,又发生了反射,反射表面自身又变成了一个光源,又照亮了别的物体。这就是著名的“Color Bleeding”。
换言之,如果一个反射面带有颜色的话,这个颜色还可以反射出去。还有一些非常光泽(Glossy)的物体,它本身像个镜子一样,会直接将光线反射回来。还有一些半透明物体,光线会在其中发生折射(refraction),甚至是多次折射,然后再穿出物体。
上述的这些效果,都可以用渲染方程来概括。现实问题是,如何求解或者实时求解渲染方程?这就是我们所面临的挑战。我个人总结了在求解渲染方程时需要处理的几个难题。
第一个挑战是和光源相关的问题,这其中分为两个问题:
第一个问题对光源的可见性问题,即“Visibility to Lights”。即对于某个点,我们能够看到,而这个点是否能被光线照射到。更熟悉的解释就是阴影。游戏行业中一位非常资深的图形学大佬曾经说过,阴影是计算机图形学中最常用的一个效果,但想要做的好,是非常难的。
人们发明了大量的“奇技淫巧”(Hack and Trick)方法来实现阴影效果。有些方法只适用于某种特定场景,在其他场景下则会引发新问题。所以这么简单的“Visibility to Lights”问题就很难完美解决。但这个效果又不能没有,因为一旦没有了阴影之后,大家看到的就是下图中的最右图效果。图中的空间关系会让大家产生很多视觉上的错乱,因为大脑很多时候是利用这种明暗遮挡关系来判断物体之间的层次结构的。这就是第一个挑战。
第二个问题是大家不太注意到的,这就是光源自身的复杂性。想象一下引擎中常用的几种光源类型。方向光(Directional Light)一般用来表示从无穷远处照射过来的平行光线。比如太阳光,其实太阳光并不是无穷远,但我们可以假设太阳光是平行的。方向光的处理比较简单。接下来是点光源,点光源其实也不复杂,我们也有办法处理。然后就是聚光灯(Spotlight),常见的就是路灯,一个锥形的光源。上述几种灯光都不难处理。但是对于面光源来说,情况就会变得复杂。对于一个接受光照的物体来说,面光源也意味着当光源自身发生转动的时候,阴影形状也会发生变化。这就是光源自身的复杂性,也就是方程中的Irradiance给开发者的挑战。
下面我们介绍两个名词,Radiance和Irradiance(编者注:参考辐射度量学相关知识)。Radiance实际上是指辐射亮度或者物体辐射出去的能量。即光线照射在一个物体上,物体往外反射出的能量,就叫做Radiance。入射的能量以前被称为“Incoming Radiance”,但我们将这两个词合并一起,称为Irradiance,即入射能量。在我看来,如何获取球面空间上的整体Irradiance的分布,是第一个挑战,这个挑战已经很棘手。
第二个挑战是如何在硬件上进行高效积分。如下图红框中所示:
我们需要在整个球面上将光照项与表示BRDF模型的四维函数一起积分。这个积分很难求解。当然,我们可以使用数值方法,通过逐个采样来求解。大家可以想象一下,在实时条件下,我们是否能够这样处理?我们当然无法在实时条件下使用数值方法进行积分来求解光照方程。在实时条件下,快速计算出光照和材质之间的卷积结果是一件很麻烦的事,我们后面会讲到。这就是我们的第二个挑战,用图形学的行话来说,就是如何进行着色(Shading)。即在已知整个球面光照信息的条件下,如何实现正确的材质效果。
第三个挑战就是光线的弹射(Bouncing)问题,这会导致所有的物体都有可能成为光源。如下图所示:
上图中特别明显。墙顶的灯光照射到物体上,物体会被照亮,而照射到墙壁上的光又会被反弹回来,再次照射到物体上。然后光线被物体再次反弹,又会被照射到别的物体上,然后又会发生反弹。即光线可以发生一次、二次或者多次反弹。在图形学中有一个很著名的示例场景,在GAMES101和GAMES202课程中也曾经提到过,就是上图中展示的“Cornell Box”。
在上图的“Cornell Box”中,为什么用两个不同颜色的墙呢?这是为了展示光线在上图的场景中经过几次反弹之后,会形成一个非常复杂的光影效果。因此对于从事渲染工作的研究人员来说,经常会使用Cornell Box作为标准的全局光照效果,和自己使用的各种技巧性的算法所生成的结果进行对比,以确定自己的算法和标准效果的近似程度。这意味着场景中可能会存在着无穷多个光源。
这三个挑战结合在一起,对于从事渲染工作的人员来说,是一个很大的难题。需要说明的是,这是在GAMES104课程中,我自己个人总结的三点挑战。每个人的看法会有所不同,但这是我个人在从事渲染工作相当长的时间之后,个人认为最具难度的几个挑战。
第一个问题的难度在于,对于入射的Irradiance来说,我们实际上很难得到它。如果我们得到了Irradiance的值,就可以解决阴影的问题,包括面光源的问题也可以解决。第二个问题的难度在于,当我们有了所有光源的信息之后,就需要和表面材质进行着色运算,这是个积分运算,计算起来也很复杂。第三个问题的难度在于,因为光线是可以被反弹的,所以一旦打开灯光,场景中的每个物体都会变成光源。这意味着第一次计算出来的每个物体的光照输出实际上会作为下一次光照计算的输入,从而变成了一个无限递归的计算。所以问题三又会将问题一变得更棘手。
在早期计算场景光照时,有个很著名的算法叫做光线追踪(Ray Tracing)。在这类算法中,还有一种叫做“分布式(Distributed)”光线追踪的算法。分布式光线追踪方法会发射出一根根光线,每根光线不停发散,最后会发生“计算量爆炸”。现在已经没有人这么做,因为即使进行离线计算,这种计算量也无法承受。然而,它确实反映了光学的本质。因为对于自然界来说(相对于计算机进行比较),它的存储空间和算力都是无限的,因此可以使用最简单的光子(Photon)来解决这个问题。而对于计算机图形工程师来说,则需要在算力非常有限的计算机上来模拟光子的各种行为,以得到正确的光照效果。
自Kajiya提出渲染方程之后,整个行业已经探索了三十多年。直到现在,我们仍然没有完全解决渲染方程中所提出的问题。但是我觉得最近行业内的新技术已经展示了一定的希望,后面我们会进行介绍。
下节课我们将给大家介绍,如何在引擎中实现渲染效果,敬请期待。
本文编辑:Piccolo 社区编委会 彭渊
如对本节课有任何问题,欢迎加入我们的社群或给我们发送邮件:
Piccolo游戏引擎社区
Piccolo社区是中国开源游戏引擎社区,由游戏引擎行业大佬、共创官、学习者共同建立。你可以在我们的社区里交流技术、互助问答、参加活动,你也可以参与Piccolo 的共建,如撰写贡献代码、撰写技术文章、参与技术挑战等。
Piccolo游戏引擎
由中国游戏引擎社区Piccolo开源的一款Mini游戏引擎。采用世界-关卡-游戏对象-组件的简洁架构,便于理解游戏引擎架构思想,它不仅能有效的帮助开发者学习游戏引擎架构知识,也能帮助一线开发者实验引擎算法与第三方库、辅助个人项目快速启动。截止目前,Github点赞已突破3600+,累计下载量已超过20000+
Piccolo GitHub地址:https://github.com/BoomingTech/Piccolo/discussions
关注公众号GAMES104,回复【入群】,加入Piccolo社群