干货 | 米哈游技术总监:开局一个人,我把《原神》搬上了PS4

2020-11-19 14:09
来源:手游那点事

整理|手游那点事|恶魔摸摸猫

近日,首届中国Unity线上技术大会顺利开幕。和往届相似,大会邀请了行业内许多专家聚首一堂,给观众们分享关于游戏制作的经验和技术。

在昨晚的游戏分会场上,米哈游的技术总监弋振中以「从手机走向走主机」为主题,分享了《原神》在PS4的渲染技术。「原神」相信你们都很熟悉了,它是一款开放世界游戏,目前登陆了PC、iOS、Android、PS4四个平台。

弋振中曾在美国和中国的游戏公司工作多年,有十余年的主机游戏开发经验。回国前,他在西雅图的微软Xbox任职,19年初回国加入了“米哈游”,组建了自己的主机开发团队。

在项目经验方面,他曾负责《正当防卫3》《正当防卫4》《腐烂国度2》等主机游戏的开发。

以下是手游那点事整理的部分演讲实录,为提升阅读体验,内容有删减和调整:

大家好,我叫弋振中,今天我的分享主要分为三个部分。

1.《原神》主机平台的一些基本情况;

2.按照我们开发时改造渲染管线的思路,选择部分的技术点进行解析;

3.个人的一些开发体会。

首先,Unity是一款灵活度很高的游戏引擎,代码风格简洁,可以让我们更方便地定制化开发《原神》的渲染管线。在这里,我对Unity中国的技术支持表示感谢。

《原神》的主机版本主要是针对PS4,而我们开发过程中超过一半的精力都用在如何利用主机硬件架构的开发和优化上面,对此我们也积累了相当多的经验,也有不错的技术实现。今天分享的技术主要涉及渲染管线部分。

一、《原神》主机基本情况和渲染管线简介

我们在Unity上对《原神》进行了深度的开发,手机平台和主机平台分别采用了两种不同的渲染管线。但是,游戏的基调都是基于PBR的风格化渲染。

游戏基于PBR的渲染模式,是想让整个大环境的光影效果保持统一。

因为我们的光影系统是实时计算的,有24小时循环,也有动态的天气系统。而PBR可以确保在不同的光照条件下,不会脱离我们预期的渲染效果。

作为风格化的游戏,也需要根据美术的需求修改不同的材质。

《原神》在PS4上,我们把1440P作为渲染分辨率,最后输出到1080p上面,这样得到的图像会更加清晰和锐利。

《原神》的风格化渲染是很独特的,尤其是光影效果,大家可以看到「脏、黑、死、焦、噪」这些词都频繁地出现了美术和程序的沟通当中。

二、技术难点和思路分析

《原神》的主机版开发可以说是从零开始。

一开始的主机团队就我一个人,光杆司令。为了全球同步上市,留给我们的开发时间大概只有一年半,还伴随着大量PS4独有的问题需要解决,工作量和工作难度都非常大。

因为开发的时间很紧,所以我们在选择技术改造点的时候,遵循下面几个原则:

1.拒绝堆砌“feature”,排除开发周期长,需要过多前期研究工作的功能;

2.尽量选择成熟实用的技术,因为没有时间试错;

3.选择对画质帮助更大的地方去提升,希望新功能之间相互互动,让画面更加系统化,得到的画质提升也会有1+1>2的效果。

下面就是我们做一些技术点的解析,以及提供一些优化思路。

场景光影方面,第一个是关于方向光的阴影。

《原神》的大量游戏时间是在室外,室外方向光的阴影质量非常重要。一方面,近处的阴影细节需要更加细腻,才显得画面更干净;另一方面,阴影覆盖范围需要足够大,因为游戏的可视距离非常远,有800米的范围。

大家可以看下这张贴图。即使在远处墙壁上一小片的绿植产生的阴影,在放大之后都能够看到树叶的轮廓,而且非常稳定。

我们阴影的技术比较常规,使用了Cascaded shadow map (一种游戏实时阴影技术)加上基于Poisson disc(泊松分布算法)的soft shadow(软阴影),我们游戏没有使用通常的4级cascades(一种数据优化器),而是用了8级,这属于“大力出奇迹”的方式。

虽然这样带来了更好的阴影效果,也带来了更多的性能开销。更多的drawcall(CPU命令GPU进行的图片渲染操作)和cascades,也会加重GPU和CPU的负担。

质量上去了之后,我们就要想办法解决性能问题。

首先在CPU端,我们做了一个shadow cache,8级cascades前4级我们每帧都更新,后面4级是采用轮流更新的方式,确保每8帧所有的cascades都能至少更新一次。每一帧的话,我们只更新5级cascades。

主要的工作量其实在GPU端。8级cascades下,screen space shadow map(屏幕空间深度阴影贴图)的开销长期是大于2毫秒的,在某些情况之下能够超过2.5毫秒。GPU比4级cascades的情况下,也爆涨了0.5到0.8毫秒。

因为软阴影采用的是泊松分布的采样,这一整套的操作都是很重量级的。但实际上不需要对每个像素都要做这么多操作。

所以我们优化思路是尽量只在必要的地方做软阴影计算,我们会生成一张Mask贴图,在贴图里面标出阴影、半影和非影片区。

阴影区和非阴影区只需要直接返回0和1就好了,只有在半影区才会去计算软阴影,通过这种方式,我们的GPU开销大致减少了30%左右。甚至比采用4级cascades还要再快一些。

(图里面被红色标注的区域,是半影区,需要做软阴影处理。其他的区域,就是在阴影区域或者是非阴影区域,直接返回0和1)

上面这张Mask贴图的分辨率是屏幕分辨率的1/4×1/4,也就是说一个Mask值对应的是一个4×4的block。然后我们对4×4的block里面的每一个像素,来判断它是不是在阴影中,最后汇总成一个阴影、半影和非阴影的三个状态,保存到Mask贴图里。

这样我们能够得到一个准确的半影信息,但是它不够快,所以我们做了进一步的优化,只选择4×4这个block里面很少的几个像素,来判断是不是在阴影当中。

这样会容易出现一些误差,所以我们把这样计算得到了Mask贴图做了模糊处理,让半影的区域稍微扩散出去。整个Mask贴图的生成,包括模糊处理大概的开销是在0.3毫秒左右。大家可以看一下对比图。

(肉眼几乎看不出来区别)

这样优化完之后,我们的GPU开销时间大概稳定在1.3到1.7毫秒。

把阴影搞好以后,下面我们来看看AO(Ambient Occlusion环境光遮蔽)。

大家先考虑一种情况,人物和场景的物体都已经处在山或建筑物的阴影当中,这个时候人物和物体的投影跟山和建筑的投影是融为一体的,画面缺乏对比,人和物体就会显得浮空。

为了解决这个问题,我们在游戏里面采用了多种的AO技术,针对不同的情景生成不同的AO。

1.常规的HBAO

大家可以看一下这是HBAO开关的对比图,效果还是很明显的。

2.针对静态物体的AO Volume

下面这个是AO Volume的开关情况,大家可以重点看一下我们在红圈里面的区域,椅子对地面产生了柔和的投影。

因为技术原理和性能的限制,HBAO是没办法针对类似桌子或者椅子产生大面积AO,AO Volume这个时候就体现很好的补充。

要实现AO volume,首先我们是在离线的时候对需要产生AO volume的物体做一个遮挡信息的计算。这个计算是在物体的本地空间(Local space)去做的,生成的遮挡信息我们保存下来,在运行的时候注入到volume texture中去使用。

3.针对动态物体的Capsule AO

下面是关于Capsule AO的对比图,大家可以重点看一下被我们红色的圈给圈出来的区域。大家可以看到相邻在屏风和地面,能够产生出能够反映体形和人影的投影。而且如果在游戏中大家去观察的话,随之相邻动作的改变,阴影的形状也会随之产生变化。

Capsule AO的做法就是用一些胶囊体包裹住人物的四肢和躯干,这些胶囊体和角色的骨骼动画绑定进行同步更新。然后这些胶囊体会被用来做遮挡计算,计算的时候我们把它分为无方向的环境遮挡计算,以及带方向的遮挡信息计算。

带方向的遮挡信息计算采用的方向是主光源方向和法线进行混合之后的得到的虚拟遮挡方向。通过这种方式,角色可以同时在周围的墙和地面等投出多个阴影。

4.关于AO的优化技巧

《原神》的AO都是在1/2×1/2分辨率的RT(Render Texture)上去做计算。我们对AO还做了一个模糊处理(blur)。然后再Upsample一个全分辨率的贴图上面去。

所有的模糊处理和Upsample pass(上采样),我们都用了一个Bilateral filter(双边滤波算法),确保不会有无效的AO渗透到周围的区域。

模糊处理和Upsample加起来一共有三个pass,这就意味着AO需要被读取和写入多次。所以我们采用的优化方式是将所有的计算都放到一个compute pass里面去做。

然后通过LDS来保存blur的中间值,通过同时输出四个像素的方式,来重用相邻像素的计算。最终我们还可以通过async compute pipe(游戏引擎中的异步计算)把性能开销进一步降低。

5.关于Local Light

我们在游戏里面采用了Clustered deferred lighting(集群延迟和前向着色,支持是视野内同时出现最多1024盏灯。大概的做法是我们将屏幕分成64×64像素的tile,然后每一个tile在深度的方向上面继续分为16级的clusters。

(游戏内许多灯源表现)

多个不同的Local Light,它们的照明范围是交错存在的,然后角色也投下多个不同的阴影朝不同的方向,画面就显得细节很丰富。

(通过Local Light的阴影提升画面)

我们的Local Light 阴影系统支持接近100盏灯的实时阴影。阴影的分辨率是根据优先级和距离进行动态调整,最终的阴影是通过烘焙的静态场景阴影和实时生成的动态场景阴影结合得到的。

然后我们需要一个好的算法来对于烘焙的shadow texture做一个压缩。这个压缩需要在精度损失足够低的同时,还要保持压缩率足够高,同时我们的解压开销要非常小才行。

我们开发的这个系统是在离线制作的时候,对于shadow texture做一个压缩,尽量地去保持精度,运行的时候解压的速度也非常快,用compute shader(一种Unity的技术)去解压的情况,1K×1K的shadow texture,我们解压只需要0.05毫秒。

接下来我介绍一下算法思路。

首先我们对于shadow texture按照一个2×2的block来进行编码,每4个深度值,我们用32bit来保存。如果想要降低精度损失,可以选择高精度压缩,这种情况之下每个block的大小变成64bit。

编码的方式有两种,一种是基于深度平面方程的方式,或者是通过压缩的浮点数方式。编码完成之后,还要进一步通过一个quad tree来合并编码以后的数据,进一步提高压缩率。quad tree是每个tile要保存一个,而每个tile又包含了16×16个block。

(三个图从左到右分别是,没有压缩的深度贴图,中间是我们的平面方程编码的视图,最右边是我们quad tree 0到4级的深度视图。)

大家可以看一个对比,默认精度压缩,能够看到红圈里面有一些瑕疵。这个实际上是我们找到的可以说是最差的一个情况。

这是高精度压缩,基本上看不出任何瑕疵来。

6.关于添加体积雾

体积雾是可以接受Local Light的照明影响,在灯的影响范围内形成一圈光晕,可以极大地提升画面的体积感。

(近处的灯笼周围会有一圈泛光。包括画面远处的建筑物,因为笼罩在灯光下会使得周围的体积雾也被照亮,而显得有一丝的朦胧)

下面这个图是一个更有意思的情况。如果我们给Local Light加一个projection texture,也就是我们通过这个贴图来控制Local Light光照的形状,就像右边这样,体积雾也会产生相应的变化。

为了让体积雾更加稳定,画面更加细腻,我们给体积雾添加了Temporal filter( 时间滤波器),进行了多帧的混合。整体的GPU开销,也控制的不错,在PS4 Pro下面大致在1毫秒甚至更少。

实现思路:

7.关于God Ray效果

(游戏中GodRay的表现)

对于方向光进行遮挡就可以产生God Ray的效果,我们的做法是有一个单独的pass来生成God Ray,然后是在1/2×1/2分辨率下面。

God Ray也是通过Ray marching的方式去生成的,我们会去采样shadow map,但是最多会采样5级的cascades。 God Ray生成完之后,我们会提供美术一些可以调整的参数,然后将God Ray的结果叠加到体积雾上面去。

8.关于God Ray和体积雾相结合的例子

这个就是一个很好的技术和美术磨合的例子,体积雾直接生成的God Ray,在游戏里面实际效果其实不能够让美术满意,原因有两点:

①分辨率不够,因为体积雾的分辨率是靠Voxel,而我们的Voxel是不会划分的特别精细;

②God Ray强度是完全依赖于体积雾的浓度,浓度一旦提高了,画面就会显得不通透,太脏。

所以我们是采用了单独的pass去生成God Ray,这样可以得到更锐利、更清晰的效果,美术调整也更灵活。

9.关于IBL(Image Based Lighting)系统

(图中左边的是Reflection probe(反射探针),右边是Ambient probe)

我们先看一下左边的Reflection probe,是用来给场景提供反射信息的。因为游戏的光影不断变化,我们是不能够简单地为反射探针烘焙一张环境贴图作为反射信息之用。所以对于每一个Reflection probe,我们是烘焙了一个mini GBuffer。

然后在运行的时候,我们会去更新场景的Reflection probe的cubemap。

整个过程大概分为三步:

第一步是Relight。

第二步Convolve和Compress。

最后这个贴图需要再通过一个Compute Shader的做法,压缩成BC6H的格式,然后送到渲染管线里面去使用。

下面是我们的Ambient probe也是实时生成的。

这样,我们这个系统就基本完成了。但实际上还有很多可以改进的地方,比如说在Relight完成得到的环境贴图是漏光的,本来应该处于阴影当中的地面也会变得非常明亮。

我们的做法是,我们把24小时的shadow都烘焙下来,就是隔一段时间我们烘焙一下,把shadow转化成一个shadow SH保存起来。在运行的时候简单通过当前的时间对shadow SH进行插值,用来压暗Relight以后的结果。

这样得到的效果是出乎意料的好,而且我们需要保存的数据非常的少。

同样的方式,我们还可以把Local Light的信息也保存下来,作为Local Light的SH在Relight的时候也加上去,这样可以得到非常好、非常廉价的一个Local Light 反弹的效果。

接下来我们要处理的是个室内室外光照环境不一致带来的问题。

我们是把Reflection probe分成室内、室外两种,然后美术通过摆放一个室内环境用的网格(interior mesh)来标记受室内光影响的像素。Ambient probe也会相应地为室内、室外生成不同的环境光。

看下对比,如果不区分的话,室内跟室外一样都会受天光的影响而变得很蓝,然后做了室内(interior)标记,室内的像素就能够正确地反应出室内的光照条件,会显得更黄一些。

下面这个就是室内环境用的网格(interior mesh)生成的Mask标记图,红色区域就是室内的区域,大家可以看一下对照关系。

除了通过Reflection probe得到的反射,我们还有Screen space reflection来提供实时的反射信息。SSR在PS4 Pro上面的GPU开销大概是在1.5毫秒左右,我们对SSR也加了一个Temporal filter,通过当前帧的SSR信息和历史信息混合起来,来提高SSR计算结果的稳定性,让画面也更平滑一些。

为了得到更多的反射信息,我们为SSR生成了Hi-Z的buffer,我们可以让每条射线通过Hi-Z最多能够跟踪的距离达到整个屏幕。

(SSR效果开关对比图,在比较光滑的地板上效果尤其明显。)

10.关于HDR Display

这里面的HDR Display包含了两个方面。

在这里我们称使用了ST2084和Rec.2020色彩空间的渲染管线为HDR管线,而使用的Rec.709色彩空间的非HDR管线,我们把他们叫做SDR管线。

下面我主要讲一下,为了让这个技术放到《原神》里面去,做了哪些调整。

这张图是《原神》 的SDR和HDR的管线对比图,和SDR管线相比,HDR管线没有了tone mapping,color grading变成了HDR的color grading。而代替tone mapping的是RRT+ODT(reference rendering transform + output display transform)的组合,这就是很多人熟悉的ACES调色。

另外,UI在HDR下面,也是单独画到一张RT的。然后再跟场景做一个合并,这是因为UI的亮度处理方式跟场景是不太一样的。

前面管线图的RRT+ODT部分是灰掉的,虽然这个是一个主流的调色方式,但是我们并没有采用它,因为和我们的游戏风格不太搭。

为了保证在低亮度范围内的画面和SDR版本的游戏一致,我们将HDR的渲染画面和tone mapping处理之后的画面做了一个基于亮度的混合,然后在亮度不高的地方,就尽量保证了filmic tonemapping的关于toe部分的处理。

但是这里面存在一个问题,就是1886的gamma是2.4,但是SDR的OETF是一个分段函数,大致上可以看作是gamma 2.2,这就出现PPT上写的问题。OETF处理完的颜色,经过EOTF,它得到的并不是它本身,就产生了一个误差。

在HDR下面,因为我们OETF和EOTF是被很好的定义了的情况,所以他们是互逆的,于是这个颜色在经过这两个处理之后能够得到原来的颜色,所以在HDR下面没有这么一个问题。

但是大家已经习惯了在HDR下面有这么一个误差的画面,所以为了模拟这个误差,我们是在HDR管线里面添加了OOTF,把差异给补上去了。

做完这些,是不是HDR就彻底没问题了呢?并不是。这还有一个大坑,叫做Hue Shift。

举个例子,大家可以看一下游戏里面火焰制作的示意图,我们通过一个灰度图,就是下面像馒头一样的东西,加一个噪声贴图,然后进行扰动,得到了火焰扰动的纹理,然后我们用橘色去染色。

最后我们把火焰的整体亮度往上提,在SDR下面因为有tone mapping的原因,是有一条曲线的,会让亮度增加逐渐变慢,于是橘色的R通道跟G通道的差异本来是很大,但是随着亮度的增加,tone mapping曲线介入了,R通道的增长就变慢,G通道就逐渐赶了上来,于是就产生了Hue Shift,画面渐渐开始发黄,于是就得到了后面看到的SDR下面火焰的效果。

问题来了,因为在HDR下面,是没有tone mapping的,Hue Shift是不会发生的。

那怎么去修改呢?一种常用的方法,也是很多大作都用的方法,叫做黑体辐射。而《原神》使用的是另一种方法,在shader里面去模拟了Hue Shift,并且把模拟放到了color grading pass里面去,合并到Look-Up-Table的计算中。

这样的好处是,我们不仅仅是让火焰特效在HDR下面的效果跟SDR几乎一致,而且因为通过引入tone mapping的方式模拟了Hue Shift,所以我们前面提到的在非HDR的亮度部分的画面一致性的问题,也被顺手解决掉了,不需要跟tone mapping处理之后的画面做混合。

而且这个操作,也是在生成Look-Up-Table的时候去做的,所以它的性能增加是可以忽略不计的。

三、个人的经验和体会

《原神》的主机版开发是第一次尝试,时间、资源和人才都很缺乏。通过一年多的开发,我们积累了很多的经验,尤其是如何把写实的渲染技术跟风格化游戏结合的经验,非常的宝贵!

随着新的主机平台的到来,我们又面临了一大波的技术升级。不过和在美国的时候相比,国内主机开发的从业人员太少了,希望大家能够多多交流,和我们探讨一下技术,交流一下开发经验。

我这次的分享就结束了,感谢大家!

Q:第一个是关于这些AO贴图是整合到一起进行计算的吗?

A:我们有三种不同的AO技术,每一个AO技术都是独立去做计算的。计算完的结果,我们都是混合到同一个RT上面去,这个RT就是我刚刚提到的是一个半分辨率的RT。

这些东西混合完之后,我们会统一地去做Bilateral的Blur加一个 Bilateral的Upsampling,最后把它们输出到一个全分辨率的贴图上面去。分别计算,统一整合,这可以算是一起计算吗?

Q:Local Light是Unity还是自己开发的?

A:这个是我们自己开发的。我们一开始是先做了一个tile based deferred lighting,然后发现因为我们游戏的光照布局方式,可能cluster deferred lighting 更适合我们的,于是我们做了这么一套东西。

恶魔摸摸猫

这家伙很懒,什么都没有留下!

评论已关闭!

相关资讯