本文主要是介绍经典3A游戏光照解决办法| GAMES104实录 - 现代游戏引擎:从入门到实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本期为GAMES104《现代游戏引擎:从入门到实践》视频公开课文字实录第18期。本课程由GAMES(图形学与混合现实研讨会)发起,游戏引擎技术专家王希携手游戏引擎一线开发者共同研发。
课程共计22个课时,将介绍现代游戏引擎所涉及的系统架构,技术点,引擎系统相关的知识。为配合学习实践,课程组在 GitHub 上开源了小引擎Piccolo,上线1个月即获得了2900+star, 累计下载量已超过20000+。
以下内容为公开课视频转文字版本,为阅读通顺,有删减
01「基于图像的光照」
下面我们介绍基于图像的光照(IBL,Image-Based Lighting)。一个点处的光照是来自于四面八方的,我们一般说来自于一个球面。在计算机中,我们可以用Cube Map来表达球面上的光照信息。如果我们将Cube Map展开,就是如下图所示的十字架图形。
如果能够对真实环境的光照做一些预处理,我们就可以快速地将整个环境对着色点的光照和着色点处的材质通过卷积运算直接计算出来,这就是IBL的核心思想。前面我们介绍过球面调和函数,但球面调和函数的表达比较粗糙。它能够产生明暗的感觉,却无法达到我们想要的效果要求,即具有细节感和凹凸感。当角色在游戏中行走时,周围的环境一直在变,被光所照亮的部位也一直在变化,我们希望主角身上没有直接被光照亮的部分也具有我们想要的细节感。
人们提出了IBL的思想来解决这个问题。回忆一下前文中介绍的BRDF方程,我们发现,BRDF方程可以分为两个部分,一部分是漫反射项,一部分是镜面反射项。
对于漫反射项来说,它本质上就是一个余弦函数在球面上的分布。对于任何一个法向的朝向点,在给定一个光照时,我们在计算出这个面和球面上所有点的余弦波瓣积分之后,就可以提前计算出漫反射结果。这样计算出的图叫做“Diffuse Irradiance Map”。当我们知道一个环境上的光照信息之后,对于漫反射部分,无论表面的法线如何旋转,通过对Diffuse Irradiance Map进行采样,我们都可以得到和整个光场的卷积结果。这仍然是一种空间换时间的思想。使用一张经过预计算得到的纹理,并在运行时对其进行采样和少量的计算,就节约了每帧对屏幕上的每个像素点实时计算漫反射信息的运算量。我们也可以将其理解成一个表格,通过查表就能够得到卷积的结果。这就是IBL最简单的一个思想,同时,漫反射也是最容易处理的。
对于Specular项来说,由于推导比较复杂,受课程篇幅所限,我们在这里只介绍几个核心的想法。第一,对于Specular项的求解,我个人认为这个方法并不是一个准确的解法,因为求解过程进行了大量假设。该方法本质上是将三个方程乘法的积分变成了三个独立的方程的积分的乘法(Split Sum)。最核心的想法是对于粗糙度的处理,不同粗糙度的积分结果是不一样的。该方法利用了硬件Cubemap中的mipmap功能,将不同粗糙度的结果存储在mipmap的不同层级中。就像大家现在看到的这张图。
这样做是很有道理的。首先,我们可以在三维空间中查询这些数据。其次,大家会发现,粗糙度越高,对光照的敏感度就越低,数据就越低频。因此,我们可以将数据存储在mipmap的最低级中。请注意,这里的mip和前面我们介绍的纹理的mipmap不同,这里不是在两个层级之间进行插值,而是每一层mip需要单独计算。当粗糙度从0到1变化时,我们就可以在mipmap中进行查找。
一般我们称之为Lookup Table(LUT)。这个LUT有两个维度,一个是粗糙度,另一个是余弦项。这时我们会发现,方程中的Fresnel项变成了一个线性项。这样我们基本上能够模拟Cook-Torrance在环境光照下的效果。
IBL对于Specular项的求解,实际上是一个近似解。但是我们可以在环境光照中看到一些高光信息。请注意,这种高光不是很闪亮的高光,而是给人一种这个地方好像有点光亮,但又不完全光亮,而且它能模模糊糊的让我觉得那边有东西的感觉。将漫反射项和高光项合并到一起,就是一个完整的材质在环境光照下的效果。大家可以看下图:
左图是没有IBL的结果,右图是添加了IBL的效果。很明显,右图的画面会让眼睛更舒服,而且如果大家仔细观察,会发现图中的物体的层次结构很清晰。这就是IBL的神奇之处,这也是十几年前所有3A大厂义无反顾地转向IBL的原因,这也是在3A行业中大家最后总结出来的一个方法。
02「经典阴影方法」
下面我们介绍在经典的3A游戏中,对于阴影的主流的解决方法——“Cascade Shadow”。对于现代游戏来说,世界地图越做越开阔,而对于阴影贴图来说,贴图的精度永远是不够的。对于1000米、100米、10米外的物体,比如远处的山,数十米之外的树木,还有离玩家很近的武器,都会产生阴影。如果要将这些物体的影子全部解决,无论如何设置精度,都会出现问题。
于是人们提出了一个想法,即将阴影分成几层。通过相机视锥体的方向,在近处做一个很高密度的阴影贴图,一般距离只有十米左右。然后对于后续的距离,比如30米、100米、2公里的距离,分别生成不同精度的阴影。如下图所示:
从相机位置看过去,绿色区域的阴影密度最高,而蓝色区域的阴影密度最低。这样形成的阴影贴图,最后绘制出来的阴影,近处看着足够清晰,而远处看着足够稀疏,也符合光学原理。因为对于远处的阴影来说,即使投影到相机中,相对来说也不会很大,这符合近大远小的原则。所以远处的采样率也下降了,远处人眼采样率的下降和阴影贴图采样率的下降形成了完美的匹配。这就是Cascade Shadow的思想。这么淳朴的一个想法在游戏行业中流行了十几年,直到最近一些新的想法得到了应用。
Cascade Shadow有一个经典的挑战,即在不同的层级之间需要进行插值,否则可能会出现一条硬的边界。如果我们不做任何处理,当推动相机时,会发现有一个固定的地方,在这个地方的阴影会破掉。这是因为不同层级阴影的分辨率不同所造成的。我们需要进行特殊处理,主要是在着色器代码中进行一些“Hack”,以解决这个问题。大家在实现Cascade Shadow的不同层级之间的混合算法时,一定要认真研究一下几个不同的方法和套路,然后再选择最佳方案。
Cascade Shadow也是一种空间换时间的方法,占用了很大的存储空间。同时,当生成远处的阴影时,相当于需要将整个场景都绘制一遍,这个成本是不低的。在游戏引擎中,阴影渲染是游戏引擎中最昂贵的部分。而且阴影渲染并不会真正绘制一些出彩的部分,比如材质和光效等,它只是确保你的光的可见性是正确的。假设一帧的绘制时间预算是30毫秒,阴影部分很多时候会消耗4-5毫秒。如果场景比较复杂,我自己的经验是阴影很少能够做到2毫秒以下的时间消耗,但是我不知道有没有别的公司能够做到这个时间以下。所以阴影渲染是非常昂贵的,尤其是Cascade Shadow。我们需要绘制四次,然后对场景做四次裁剪。因为在绘制最近处的阴影时,我们不希望它处理很多物体,所以需要重新计算可见性,然后再计算阴影。所以在实现过程中的细节很多。
关于阴影的另外一个知识点是软阴影,GAMES202课程中有详细介绍,闫令琪老师也介绍了很多算法。我们重点讲几个方法。
第一个方法就是PCF(Percentage Closer Filter),PCF方法非常科学,使用了滤波的方法来处理阴影的软硬。基于PCF,在实战中又出现了PCSS(Percentage Closer Soft Shadow)方法。PCSS方法更精妙,因为PCSS考虑了物体离光源的远近,然后进行一次采样,就能够渲染出阴影。PCSS在很多引擎中都属于标配,因此大家看到的很多引擎中的阴影效果,都会带一点软阴影。软阴影能够极大地缓解阴影的走样问题。
最后我们介绍一下“Variance Soft Shadow Map”(以下简称VSSM),这个方法在GAMES202中也有详细的介绍,我们也借用了GAMES202的课件。VSSM的想法非常简单。使用数学方法计算出深度的均值和方差,然后利用切比雪夫不等式进行估算,得到阴影的软硬比例。VSSM方法在很多阴影中也有应用,实际效果也不错。大家如果进行阴影相关的处理,理解了上面这些方法,然后根据自己的具体情况进行选择,基本上就可以达到自己想要的结果。
下面我们介绍在经典的3A游戏中,对于阴影的主流的解决方法——“Cascade Shadow”。对于现代游戏来说,世界地图越做越开阔,而对于阴影贴图来说,贴图的精度永远是不够的。对于1000米、100米、10米外的物体,比如远处的山,数十米之外的树木,还有离玩家很近的武器,都会产生阴影。如果要将这些物体的影子全部解决,无论如何设置精度,都会出现问题。
总结一下,在上个时代,光照一般使用光照贴图和光照探针来解决。一般来说,很多引擎这两个方法都会用,因为这两个方法分别解决了不同的问题。而对于材质来说,主要就是PBR方法。而对于背光面即环境光照的表达,基本上以IBL方法为主。PBR只需要掌握两个模型,一个是SG模型,一个是MR模型。掌握了以上知识,基本上就可以处理光照效果了。对于阴影,则可以使用Cascade Shadow方法。然后再使用VSSM或者PCSS,给阴影加上软阴影效果。这时,我们基本上有能力实现5-10年前的3A游戏渲染引擎了。
03「前沿技术」
时代一直在变,各种软硬件技术也在不断进步,尤其是硬件的飞速发展。随着DirectX 12和Vulkan的出现,渲染的底层方法和逻辑几乎被彻底颠覆。因为它们将整个计算全部开放出来。开发者们过去很多想做而做不到功能,现在的硬件几乎都可以支持。第二点就是硬件算力的提升,基于一个Warp或Wavefront的算力非常强大,而且相互之间的数据通讯和同步也非常方便。第一节课我讲了一个概念,如果大家真的在实践领域精心钻研,大家会发现,实践领域比理论领域更累。因为我们需要不断地跟进最前沿的知识和体系,特别是在硬件发生革命的时候,各种新的算法和方法就如雨后春笋般出现了。比如实时光线追踪技术等。本质上来说,实时光线追踪相当于硬件提供了一个功能,我们输入几何结构,硬件会帮助我们构建好BVH结构。这时我们输入一条光线,硬件会告诉我们有没有命中一个物体,如果命中,会提供一个回调函数,如果没有命中,也会提供一个回调函数,然后由程序员进行处理。
对于现代游戏来说,实时光线追踪技术都是标准配置。然而,很多现代游戏只是使用实时光线追踪技术来实现反射功能。包括微软的《FORZA:HORIZON》。实际上,实时光线追踪技术不仅能够解决高光的问题,而且会彻底改变整个光照体系,进而可以实现实时全局光照效果。在虚幻5中,Lumen的效果令人震撼,这就是硬件的能力解放出来了。
很多能够实现全局光照的方法都在互相竞争。比如最高效的屏幕空间全局光照(Scree-Space Global Illumination),可以在屏幕空间快速形成GI信息。包括基于有向距离场(SDF)的GI,以及基于将世界分成各个体素(Voxel)的GI方法,比如SVOGI(Sparse Voxel Octree GI),还有VXGI,RSM(Reflective Shadow Map)和RTX GI。当这些实时全局光照方案占据主导之后,整个游戏的画面会有一次质的飞跃。我个人认为,对于现在的商业级游戏引擎来说,实时全局光照是必须要解决的问题。而且它的解决方案并不简单,我们会在本门课程的最后讲解一些前沿技术,其中就包括了虚幻引擎的Lumen技术的详细介绍。
Lumen并不是一个单一算法,而是多种算法的组合,最后产生了我们想要的效果,所以Lumen是一个非常复杂工程问题。同学们如果想要进入图形学行业,一定要密切关注这个领域,因为这个领域现在正在蓬勃发展,有大量的前沿技术和观点不断被人提出。如果几个月不看论文或者不和同行交流,就会有些落伍。我个人认为,一场巨大的(光照)革命正在发生,这个时代已经到来了。
在现代3A游戏的开发中,还有一个主题值得大家关注,就是对于比较复杂的材质的渲染。比如说3S的皮肤材质、毛发渲染,现在都被推到了一个非常高的维度。以毛发渲染为例,由于Geometry Shader技术的发达,我们可以迅速生成无数的细节,而且可以实现非常复杂的光影效果。3S材质可以利用现代硬件强大的算力,模拟光在材质中的反射和折射效果。由于硬件的发展,对于3S材质来说,最近这两年的新方法和论文也非常多。
最后提醒大家注意一个新的方向,这是我们前段时间研究虚幻5发现的。虚幻5的研发人员提出了一个叫做“Virtual Shadow Map”的技术。游戏引擎的前辈John Carmark曾经提过过一个概念,叫做“Virtual Texture”,将游戏环境中所有要用到的纹理全部打包到一张巨大的纹理上,这个纹理就叫做“Virtual Texture”。需要使用的时候将纹理调出来使用,不用的时候就将纹理卸载掉。Virtual Shadow Map的想法和Virtual Texture的想法有异曲同工之妙。在对一个很大的区域生成Cascade Shadow时,该方法会计算哪些地方真正需要生成Shadow Map,所生成的Shadow Map的密度是多少?然后在一块完整的虚拟的Shadow Map分配所需要的空间,分块来生成Shadow Map。相机进行渲染时,每次命中一个物体,就可以知道应该取哪个Shadow Map的值,然而再去获取Shadow Map的值。这个想法能够解决Cascade Shadow在有些场合下空间利用率不高、占用空间过大的问题。如果大家现在开始研发现代游戏引擎,我建议大家关注一下这些新方向。
04「Shader的管理」
最后,我们介绍一下大家可能注意不到的一个问题,这就是Shader的管理。在游戏引擎中,会有非常多的Shader,这会引发不少麻烦。以我以前参与的Bungie工作室的《Destiny》来说,在一帧的渲染过程中,会运行几千个Shader。这些Shader来源于两个方面。
第一个来源就是艺术家。艺术家会制作大量的Shader Graph,每个Shader Graph最后都会变成一段Shader代码。而艺术家的想象力并不受控,所以会生成海量的Shader。第二个来源来自于程序员自身。在游戏中,各种限定条件非常多,比如材质是在点光源下、在面光源下、全局光照下,还是在球面调和函数下?材质是单面渲染,还是双面渲染?材质有没有透明度等等?总而言之,围绕材质就会有各种变化。
如果为每种变化组合都单独实现一个Shader,这种Shader的数量会非常庞大。因此,我们一般会写一个完整的Shader,叫做“Uber Shader”。然后用宏定义对各种情况进行分支处理,每一种宏定义就代表了函数的一种可能的分支。而对于GPU来说,分支会极大降低GPU的运行效率。因为如果一个函数中有分支语句,就会导致函数的执行时间不一致,而GPU采取的是SIMT架构,GPU期望一批指令在不同计算核心上的计算时间是一致的。因此,我们会将所有这些分支编译成大量的Shader。
下图是我们自己研发的Chaos引擎中的示例:
项目中大约有165个手写Shader模板,作为Uber Shader,而这些模板一共生成了7万多个Shader的变体。然而,使用Uber Shader是很有必要的。从项目开发的角度来说,当这7万多个Shader中出现了Bug之后,我们需要对某一类Shader算法的实现进行修改,如果我们不使用Uber Shader,我们需要依次修改所有可能的组合,在这个过程中很容易出现错误。如果我们使用了Uber Shader,我们修改了出现问题的起始位置,编译器会自动编译出各种分支的组合。这些Shader也叫做变体(Variants)或组合(Permutation),这是现代游戏引擎中非常重要的一个方法论和概念。
最后一个问题就是Shader语言的多样性,比如GLSL、HLSL,以及苹果自己的Metal。所以当我们在开发商业级引擎时,也需要支持Shader的跨平台编译功能。
开源组织提供了一个开源的SPIRV第三方库,它能够帮助我们编译各种Shader,包括Playstation的PSSL。大家在从事引擎开发、编写Shader时,我建议大家一开始就尝试将Shader编译成各种平台的版本,比如Vulkan、Metal等。这样可以避免需要针对不同平台再单独写一套Shader的工作,因为Shader的调试很麻烦。这样就可以对Shader进行管理了。
本文编辑:Piccolo社区编委会 彭渊
如对本节课有任何问题,欢迎加入我们的社群或给我们发送邮件:
piccolo-gameengine@boomingtech.com
关于我们
Piccolo游戏引擎社区
Piccolo社区是中国开源游戏引擎社区,由游戏引擎行业大佬、共创官、学习者共同建立。你可以在我们的社区里交流技术、互助问答、参加活动,你也可以参与Piccolo的共建,如撰写贡献代码、撰写技术文章、参与技术挑战等。
Piccolo游戏引擎
由中国游戏引擎社区Piccolo开源的一款Mini游戏引擎。采用世界-关卡-游戏对象-组件的简洁架构,便于理解游戏引擎架构思想,它不仅能有效的帮助开发者学习游戏引擎架构知识,也能帮助一线开发者实验引擎算法与第三方库、辅助个人项目快速启动。截止目前,Github点赞已突破3600+,累计下载量已超过20000+
Piccolo GitHub地址:https://github.com/BoomingTech/Piccolo/discussions
关注公众号GAMES104,回复【入群】,加入Piccolo社群
这篇关于经典3A游戏光照解决办法| GAMES104实录 - 现代游戏引擎:从入门到实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!