首曝一年半后再亮相,网易这款新游的光影效果堪比3A

首曝一年半后再亮相,网易这款新游的光影效果堪比3A
2022年11月27日 21:29 手游那点事

去年5月,网易旗下的拟真赛车新游《巅峰极速》(Racing Master)在国内首曝。

实机画面中高质量的美术表现和法拉利、保时捷等全球知名车厂的授权,以及老牌赛车游戏厂商Codemasters合作开发的加持,为《巅峰极速》博得了巨大关注。

前不久,这款游戏在第十代iPad发布会上亮相,重回大众视野。

前几天(11月17日),在虚幻引擎举办的2022虚幻引擎技术开放日(以下简称 UOD 2022)上,网易游戏高级技术经理周潜向我们揭示了《巅峰极速》是如何运用光影技术让游戏画面达到高拟真的视觉效果的。

以下内容由手游那点事整理,为优化阅读体验,我们删减、调整了部分内容:

导语

各位好,我是来自网易游戏的高级技术经理周潜。《巅峰极速》是一款拟真的高品质赛车游戏,玩家可以在我们游戏中自由驾驶赛车,驰骋于全球都市和赛道,感受极致拟真的视听体验以及爽快的驾驶乐趣。

从产品演示中,可以看到游戏的一个特点:真实。在追求高要求的美术品质时,我们面临的最大难点就是真实的全局光照。

全局光照分为两个部分,一个是直接光照,一个是间接光照。在赛车游戏中就延伸出两个问题——实时多光源和实时环境捕捉。

01

 运用Grid Shading实现实时多光源 

多光源在我们游戏中是非常常见的。比如在夜晚赛道下,我们需要路灯、车灯效果以及回火特效等等来照亮周围的物体。

(赛车经过路灯时的光照表现)

路灯采用了实时的聚光灯,另外车辆本身也有两盏车头灯,并且路灯会对车辆产生一个投影,形成了漂亮的光影变化。

这种数量的多光源在传统Forward管线下是比较难实现的,之前有很多人提出了改进方案。

首先是屏幕空间的Tile Shading(又称 Forward+),它的原理是将屏幕空间划分为多个小格子,结合深度Buffer,求出对每个小格子有影响的灯光,这样就能减少每个像素内需要计算的灯光数量。

但是这个方案,一是需要预先绘制深度的Buffer,在场景比较丰富的情况下会带来更多的Draw Call;二是其求交计算必须在GPU层用Compute Shader计算,但很多手机机型会产生一定的兼容性问题。

另一个是Clustered Shading,在Forward+基础上对z轴进一步划分,形成了一个3D的视锥体分块。绘制时需要将灯光与分块进行求交计算,同样也需要一个额外的Pre-Z Pass,并且也需要Compute Shader在GPU进行计算。

为了解决这些问题,我们采用了Grid Shading的方案。

首先在世界空间XY方向进行划分,用一张灯光索引图,每个像素代表一个格子。RGBA 4个通道正好可以记录4盏灯光,如果超过了4盏,可以计算灯光的光照强度,保留最大的4盏灯光,灯光信息通过UniformBuffer上传。这个网格还可以在Z轴方向去扩展,从而覆盖更大的范围。

下图展示了我们如何进行Grid Shading的网格划分。XY方向是64×64的大小,在Z轴方向,从摄像机的高度出发,向上和向下各扩展两层,所以是64×64×4的一个网格。

右图是扁平的网格,虽然不能覆盖到整个视野,但赛车游戏的赛道基本是平铺的,所以基本能够覆盖视野范围内的多数物体,包括比较重要的赛车和路面。

在整个流程中,首先我们需要遍历所有的灯光,根据包围盒计算需要求交的格子范围;其次在CPU层对灯光和格子进行求交,以及贡献度的计算和排序(贡献度根据每个格子顶点和中心的光照强度的平均值来估算)。之后,我们填充灯光索引图并上传所有的灯光信息。

最后在渲染时,GPU在每个像素根据当前世界坐标所在的一个格子,采样灯光索引图对应的像素去查找灯光、计算光照。

其中最困难的一步是灯光与格子的求交计算。我们可以将立方体的格子近似为一个包围球,如果是一个点光源,光源的范围是一个球体,球体与球体之间的相交是非常好判断的。但如果是聚光灯,它的范围是一个圆锥体,怎样去计算圆锥体和球体的相交?

首先,聚光灯的圆锥可以看成是在一个大球的球面角范围内,大球球心也就是圆锥的顶点。根据大球与包围球的关系,我们可以分为四种情况。

毫无疑问,第一种情况下圆锥和包围球是相交的,第二种情况下是不相交的。

另外两种情况比较复杂。如果外围球只有不到一半在大球的内部,包围球与大球相交的部分构成的球面角和圆锥体是否相交,就代表包围球和圆锥体是否相交;如果包围球超过了一半在大球内部,大球球心到包围球的切线构成的球面角和圆锥体也可以判断出它是否相交。

通过这四种情况,我们分别计算,就可以比较精确地做格子与聚光灯的求交计算。

下图中,左边这张图是我们在实机场景里摆放的动态的路灯以及动态的车灯,右边是灯光的索引图。

灯光索引图有四层,每一层对应到不同高度的网格,每个像素代表一个格子,有颜色的像素就代表这个格子会受到灯光的影响,而每个蓝色的一片区域实际上就是一盏路灯覆盖的范围。

由于我们聚光灯的形状是上面小、下面大,从高往低,蓝色的区域是逐渐变大的,我们会采样这张索引图来获取当前格子中有影响的灯光。

那么,Grid Shading的优势在哪?

通过与之前两种方案的对比,我们可以发现Grid Shading不需要Pre-Z,减少了Pass以及非常多的Draw Call。它还支持在CPU层计算,增加了移动端的兼容性。此外,Grid Shading灯光求交复杂度比较低,即使是聚光灯,计算结果也比较准确。

从划分粒度来说,Grid Shading可以划分得比较细,而Tile Shading就非常粗。且在中配机型上,灯光求交只有一毫秒左右。

但Grid Shading的问题在于,其视野范围必须得是水平的。但是对于赛车游戏来说,视野通常是水平的,因此其仍然是非常适合的多光源方案。

除了灯光之外,投影也是十分重要的内容。UE4的默认做法是为每个CSM层级以及聚光灯分配一个n×n的Shadow Map,最后合成一个很大的Atlas。但在移动端ShadowAtlas会占用非常大的带宽,有时候还会浪费像素。

因此,我们把多个Shadow Map固定在一张1024×1024的Atlas内进行分配,根据光源在屏幕内的占比来决定它的Shadow Map的比重。我们划分了三个区域,最多可以支持三盏灯光或者三层CSM,根据具体情况灵活分配,以节省内存和带宽,还能增加Shadow Map的利用率。

02

 双抛物面环境映射实现实时环境捕捉 

有时,高亮物体会比直接光更加影响光照。而没有直接光的情况下,环境间接光是如何在游戏中照亮我们的赛道和路面的?

移动端做环境捕捉,通常采用双抛物面环境映射的方案。它相当于把周围360°的环境,通过两个抛物面映射到相反的两个方向上,这样只需两张图片,就可以包含整个环境的信息。

下图左是我们的一个实机场景,右边是实时捕捉的两张双抛物面环境映射图,而车辆和地面都需要采样这两张贴图来获取实时的环境信息。

为什么移动端会采用这种方法?

一般全环境捕捉的映射有三种,球面映射、立方体映射和双抛面映射,渲染目标分别是一张、六张和两张。渲染目标数越多,代表我们需要更多的Pass,更多的Draw Call。

而球面映射畸变最大,几乎无法用于实际的环境捕捉;立方体映射是完全没有畸变的;双抛物面映射的畸变比较小,大部分情况是可以接受的。

映射质量则表示环境捕捉的细节程度。球面映射在边缘处比较差,并且有奇点;立方体映射的信息量最大,相对地质量比较高;双抛物面的映射质量一般,但效果也可以接受。

此外,球面映射的顶点计算涉及到开方,因此最复杂;立方体映射实际上就是透视变换,比较简单;而双抛物面映射的计算也非常简单。

基于移动端对于性能的需求,双抛面映射的方案是最佳的。

确定方案后,还要讲究捕捉方向。捕捉方向可以是前后左右上下,但如果用前后或者左右的捕捉方向,两个剖面的分界处就会有部分三角面被裁剪掉,导致最后合成的环境是有缝隙的。

而如果采用上下的捕捉方向,由于赛道是平整的,缝隙不会那么明显,环境看起来就比较完整。

另外,我们对于捕捉点的位置也是有要求的。如果捕捉点和相机的垂直方向不一致,对于同一个反射方向,捕捉的内容和场景的位置在横向上会有偏差(图左)。如果捕捉点与相机的垂直方向一致,那只会在纵向上有偏差,而在横向上是对齐的(图右)

之后在PPR渲染里,还需要采用IBL对游戏进行一个间接Specular的计算。所以我们在IBL阶段把捕捉到的结果用作环境贴图,计算间接光的Specular,以达到全局光照的效果。

IBL在不同粗糙度下,我们需要采样滤波之后的环境贴图。如果完全按照卷积滤波的方法,会出现一些性能问题,所以我们直接对双抛物面的捕捉结果生成Mipmap,可以近似作为滤波后的环境贴图。

而这就带来一个问题。下图右是粗糙度比较高的球体,由于双抛物面的纹理分别形成Mips,导致上下半球场景亮度不一致,因此形成了一条明显的分界线。

而如果只用一张纹理来描述整个场景,就能解决这个问题。我们用双抛物面捕捉好环境贴图,再用球面映射绘制到一张纹理上,用这张纹理去生成Mips,这样就不会产生不一致的问题。

这里还有一个优化技巧。在双抛物面捕捉时,可以将Render Target绑定在一个纹理的两级Mips上,朝上时绑定到Mips0级,朝下时绑定到Mips1级,减少朝下捕捉的分辨率,就可以缩减一定带宽和内存,以及一定的渲染开销。

此外,我们只需要采样一次纹理,通过Z轴的一个符号去判断到底要采样哪一张Mips层级,以减少采样次数。

接下来是实时环境捕捉的流程。

为了减少Pass和Draw Call,我们将上下半球的捕捉分为两帧,交替进行。分帧捕捉可能会导致反射出现抖动,但是实际表现不太明显。

接下来每一帧捕捉的结果去做球面映射、生成Mips。最后将球面映射的内容作为环境贴图在IBL中进行采样,应用到物体的PPR渲染中。

但是当我们把车开进隧道,又发现了另一个问题。

这是一台白色的918,进了隧道后,它变成了一辆黑色的车。因为隧道里有很多静态光,照亮了整个隧道,但我们捕捉的环境缺少静态光的信息。由于静态光存在我们预烘培的ILC的光照探针里,所以我们还需要探针来提供更多的信息。

UE4为了解决间接光与RADIUS和环境贴图亮度不一致的问题,加了一个叫做mixen waiting的机制。它能剥离环境的亮度信息,叠加上Irradiance的亮度信息,同时还保留了原有方向域上的变化。

UE4还根据粗糙度做了一个lerp,粗糙度越低就越倾向于保留环境贴图的亮度。另外,UE4还给了几个参数来重新映射Roughness,如果对效果不满意,还可以手调参数。

由于缺少静态光的信息,我们可以用mixing weighed来补充indirect Irradiance的信息,但UE4是静态的环境贴图,而我们是动态的,这就导致平均亮度就没法做离线计算。

我们将球面映射环境贴图的最高一级Mips的像素取出来,点乘0.3、0.59、0.11(RGB各个通道对于亮度的权重值),就可以获得一个基本上可以表征环境的平均量的数值。

有了平均亮度,就可以用Irradiance除以平均亮度,然后用Roughness做差值,用这个差值去乘以环境贴图采样的结果,最后用于eval计算,即可以还原出车辆在隧道里的光照效果。

这样车身的颜色就能还原,同时能较好地融入环境,达到了全局光照的真实效果。

不过我们还缺少了车对路面的环境光影响。

下图右的车子更加扎实可信。因为在阴影里,地面基本靠环境光照亮,如果车辆不遮挡环境光,就会显得车子比较飘。

解决办法就是增加一个车辆倒影,对地面提供环境光遮蔽的信息。我们用一个相对地面对称的相机去捕捉车,再生成一个Mipmap,根据屏幕空间的UV对这张贴图进行采样,叠在环境反射上就可以了。

有了车辆倒影之后,我们的美术设计也会更加大胆。他们在路面上布置了非常多的积水,即使车辆经过这些积水路面,它的反射也不会穿帮,进一步地提升了画面的真实性和可信度。

通过游戏内置的拍照功能,也给玩家提供了更多的创作空间。带有车辆倒影的作品,会更加接近于真实的表现。

03

 结语 

我们的这套方案可以用于实时性要求比较高的光照环境的游戏里,除了赛车品类之外,也可以用于MMORPG、FPS等游戏中。

未来我们也会把这套方案进行延展,适应大世界以及昼夜天气变化等,以及玩家自定义的赛道。

最后,以一段来自于东京御苑的实机演示作为结尾。

我的分享就到这里,谢谢大家。

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部