次世代,是日本流传过来的叫法,意思是下一个时代,未来的时代,常说的次世代科技,即指还未广泛应用的现金技术。后来这个名次变成了一个人们的习惯,将次世代变成一个名词,用来代表某类具备特定属性的游戏类型。
随着科技的发展,手机硬件不断升级优化,使我们可以在移动端上实现次世代游戏的画面效果。虽然如此,我们还是受到一些硬件限制的瓶颈,我们不可能像之前制作主机游戏那样“肆意浪费、随心所欲”,必须要优化处理各种美术资源,以便在不同的平台,不同的硬件终端上有一个好的游戏体验。
性能瓶颈有哪些?
对于一个游戏来说,有两种主要的计算资源:CPU和GPU。它们会互相合作,来让我们的游戏可以在预期的帧率和分辨率下工作。CPU负责其中的帧率,GPU主要负责分辨率相关的一些东西。
总结起来,主要的性能瓶颈在于:
CPU:
过多的Draw Calls
复杂的脚本或者物理模拟
GPU:
填充率:图形处理单元美妙渲染的像素数量
像素的复杂度:比如动态阴影,光照,复杂的shader等等
几何体的复杂度:顶点数量
GPU的显存宽带
涉及的优化技术
对于CPU来说,限制它的主要是游戏中的Draw Calls,那么什么是Draw Calls呢?Draw Calls是CPU调用底层图形接口,比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做很多工作,那么CPU必然不堪重负。但是对于GPU来说,图形处理的工作量是一样的,所以对Draw Calls的优化,主要就是为了尽量解放CPU在调用图形接口上的开销。
上面说到过,我们需要绘制图像时,就一定需要调用Draw Calls。例如,一个场景有水有树,我们渲染水的时候,使用的是一个matenall以及一个shader,但渲染树的时候需要一个完全不同的matenall和shader,那么就需要CPU重新准备顶点数据、重新设置shader,而这种工作实际上是非常耗时的。如果场景中,每一个物体都使用不同的matenall、不同的纹理。那么就会产生太多的Draw Call,影响帧率,游戏性能就会下降。其它COY的性能瓶颈还有物理、布料模拟、粒子模拟等,都是计算量很大的操作。
而对于GPU来说,它负责整个渲染流水线,它会从处理CPU传递过来的模型数据开始,进行Vetex Shader、Fragment Shader等一系列工作,最后输出屏幕上的每个像素。因此它的性能瓶颈可能和需要处理的顶点数目的、屏幕分辨率、显存等了解了上面基本内容后,下面涉及到的优化技术有:
CPU优化
减少Draw Call数量
GPU优化
顶点数优化
像素优化
带宽优化
如何减少Draw Call数量?
1批处理(Batching)
最常见的就是通过批处理了。从名字上来解释,就是一块处理多个物体的意思,那么什么样的物体可以一起处理呢?答案是使用同一个材质的物体。因此,对于同一材质的物体,它们之间的不同仅仅在于顶点数据的差别,即使用的网络不同而已。我们可以把这些顶点数据合并在一起,再一起发送给GPU,就可以完成一次批处理。
Unity中含有两种批处理方式:一种是动态批处理,一种是静态批处理。
对于动态批处理来说,好消息是一切处理都是自动的,不需要我们自己做任何操作,而且物体是可以移动的。但坏消息是,限制很多,可能一不小心我们就会破坏这种机制,导致unity无法批处理一些使用了相同材质的物体。对于静态批处理来说,好消息是自由度很高,限制很少,坏消息是可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。
首先来说动态批处理。
Unity进行动态批处理的条件是,物体使用同一材质并且满足一些特定条件。Unity总是在不知不觉中就为我们做了动态批处理。动态批处理虽然自动的令人感动,但是它对模型的要求很多。
1、顶点熟悉的最大限制为900。
2、物体都必须需要使用同一个缩放尺度(可以是(1,1,1)、(1,2,3)、(1.5、1.4、1.3)等等,但必须都一样)。但如果是非统一缩放(即每个维度的缩放尺度不一样)。例如(1,2,1)),那么如果所有的物体都是使用不同的非统一缩放也是可以批处理的。
3、使用lightmap的物体不会动态批处理,多passes的shader会中断批处理,接受实时阴影的物体也不会批处理。
动态批处理的条件这么多,因此unity提供了另一个方法,静态批处理。方法很坚定,只需要把物体的“Static Flag”勾选上。
有一些小提示可以使用:
1、尽可能选择静态批处理,但得时刻小心对内存的消耗
2、如果无法进行静态批处理,而要使用动态批处理的话,那么小心上面提到的各种注意事项。例如:
尽可能让这样的物体少并且尽可能让这些物体包涵少量的顶点属性。
不要使用统一缩放,或者都使用不同的非统一缩放。
3、对于游戏中的小道具,可以使用动态批处理。
4、对于包含动画的这类物体,我们无法全部使用静态批处理,但其中如果有不动的部分,可以把这部分标识成“Static”。
虽然批处理是个很好的方式,但很容易就打破它的规定。例如, 景中的物体都使用Diffuse材质,但它们可能会使用不同的纹理。因此,尽可能把多张小纹理合并到一张大纹理中是一个好主意。
GPU的优化
1顶点优化
(1)优化Mesh,减少顶点数量。
3D游戏制作都是由模型制作开始,而在建模的时候,有一条我们需要记住,尽可能减少模型中三角形的数目,一些对于模型没有影响,或是肉眼非常难察觉到区别的顶点都要尽可能去掉。
(2)LOD技术
LOD技术有点类似于Mipmap技术,不同的是,LOD是对模型建立了一个模型金字塔。根据摄像机距离对象的远近,选择使用不同精度的模型,它的好处是可以在适当的时候大量减少需要绘制的顶点数目。它的缺点同样是需要占用更多的内存,而且如果没有调整后距离的话,可能会造成模拟的突变。
在unity中,可以通过LOD Group来实现LOD技术。
(3)遮挡剔除(Occlusion culling)技术
遮挡剔除是用来清除躲在其他物件后面看不到的物件,这代表资源不会浪费在计算那么像看不到的顶点上,进而提升性能。
2像素优化
(1)控制绘制顺序
像素优化的重点在于减少overdraw。之前提过,overdraw指的就是一个像素被绘制了多次,关键在于控制绘制的顺序。
Unity还提供了查看overdraw的视图,当然这里的视图只是提供了查看物体遮挡的层数关系,并不是真正的最终屏幕绘制的overdraw。也就是说,可以理解为它显示的是如果没有使用任何深度检验时的overdraw,这种视图是通过把所有对象都渲染成一个透明的轮廓,通过查看透明颜色的累计程度,来判断物体的遮挡。
需要控制绘制顺序,主要原因是为了最大限度的避免overdraws,也就是同一个位置的像素可以需要被绘制多变。在PC上,资源无限,为了得到最准确的渲染结果,绘制顺序可能是从后往前绘制不透明物体,然后再绘制透明物体进行混合。但在移动平台上,这种会造成大量overdraw的方式显然是不合适的,我们应该尽量从前往后绘制。从前往后绘制之所以可以减少overdraw,都是因为深度检验的功劳。
在unity中,哪些shader中被设置为“Geometry”队列的对象总是从前往后绘制的,而其它固定队列的物体,则都是从后往前绘制的。这意味着,我们可以尽量把物体的队列设置为“Geometry”。而且,我们还可以充分利用unity的队列来控制绘制顺序。
(2)警惕透明物体的使用
对于透明对象,由于它本身的特性决定如果要得到正确的渲染效果,就必须从后往前渲染,而且抛弃了深度检验。这意味着,透明物体几乎一定会造成overdraws。如果我们不注意这一点,在一些机器上可能会造成严重的性能损失。例如,对于GUI对象来说,它们大多被设置成了半透明,如果屏幕中GUI占据的比例太多,而主摄像机又没有进行调整而是投影整个屏幕,那么GUI就会造成屏幕大量的overdraws。
因此,如果场景中有大量的透明对象,或者有很多层覆盖的多层透明对象(即使它们每个的面积都不大)。或者是透明的粒子效果,在移动设备上也会造成大量的overdraws,这是应该尽量避免的。
对于上述的GUI这种情况,我们可以尽量减少窗口中的GUI所占的面积,如果实在无能为力,我们可以把GUI绘制和三位场景的绘制交给不同的摄像机。而其中负责三位场景的摄像机的视角范围尽量不要和GUI重叠。对于其他情况,只能说,尽可能少用。当然这样会对游戏的美观度产生一定影响,因此我们可以在代码中对激起的性能进行判断,例如首先关闭所有的耗费性能的功能,如果发现这个激起表现非常良好,再尝试开启一些特效功能。
(3)尽量避免实时光照
实时光照对于移动平台是个非常昂贵的操作。如果只有一个平行光还好,但如果场景中包含了太多光源并且使用了很多Passes的shader,那么很有可能会造成性能下降。而且在有些机器上,还要面临shader失效的风险。例如,一个场景里如果包含了三个逐像素的点光源,而且使用了逐像素的shader,那么很可能将Draw Calls提高了三倍,同时也会增加overdraws,这是因为,对于逐像素的光源来说,被这些光源照亮的物体要被再渲染一次。更糟糕的是,无论是动态批处理还是静态批处理,对于这种逐像素的pass都无法进行批处理,也就是说,它们会中断批处理。
3带宽优化
(1)使用Texture Atlas
使用Texture Atlas可以帮助减少Draw Calls,而这些纹理的大小同样是一个需要考虑的问题。在这之前要提到一个问题就是,所有纹理的长宽比最好是正方形,而且长度值最好是2的整数幂,这是因为很多优化策略只有在这种时候才可以发挥最大的效用。
(2)关于Generate Mip Maps
Generate Mip Maps会为同一张纹理创建出很多不同大小的小纹理,构成一个纹理金字塔,而在游戏中,可以根据距离物体的远近,来动态选择使用哪一个纹理。
这是因为,在距离物体很远的时候,就算我们使用了非常精细的纹理,但肉眼也是分辨不出来的,这种时候完全可以使用更小、更模糊的纹理来代替,而这可以节省大量访问的像素的数目。但它的缺点是,由于需要为每个纹理简历一个图像金字塔,因此它会需要占用更多的内存。
(3)优化纹理尺寸大小、压缩格式
纹理尺寸大小:
纹理尺寸大小通过MaxSize设置,它决定了纹理的长宽值,如果我们使用的纹理本身超过了这个最大值,unity会对其进行缩小来满足这个条件。
这里特别说明一点,所有纹理的长宽比最好是正方形,而且长度值最好是2的整数幂,例如64、128等。这是因为有很多优化策略只有在这种时候才可以发挥最大的效用。
压缩格式:
压缩格式通过Format设置,Format负责纹理使用的压缩格式。通常选择这种自动模式就可以了。Unity会负责根据不同的平台来选择合适的压缩模式。对于GUI类型的纹理,我们可以根据对画质的要求来选择是否进行压缩。我们还可以根据不同的激起来选择使用不同的分辨率的纹理,以便让游戏在某些老机器上也可以运行。例如在安卓平台上,我们的压缩模式会采用RGB Compresses ETC 4 Bits。在ios平台上,我们采用的压缩模式是RGB Compresses PVRTC4 Bits。
注:手游圈内人整理自创梦天地开发者邵雷的“关于次世代手游美术资源的优化”的主题演讲。