Unity移动端游戏性能优化简谱之 画面表现与GPU压力的权衡

Unity移动端游戏性能优化简谱之 画面表现与GPU压力的权衡

《Unity移动端游戏性能优化简谱》共分为四个部分,今天向大家介绍文章的最后一个部分:画面表现与GPU压力的权衡,共8小节,包含了带宽、Overdraw、渲染效果、后处理、渲染策略、Shader复杂度等多项常见的游戏画面表现讲解。(全文长约4400字,预计阅读时间约15分钟)

前三部分可点击以下对应文章查看:

一、《Unity移动端游戏性能优化简谱之 前言》
二、《Unity移动端游戏性能优化简谱之 常见游戏内存控制》
三、《Unity移动端游戏性能优化简谱之 CPU耗时调优》

1. 总览

现在,越来越多的移动端游戏开发团队对游戏的画面表现效果追求也越来越高。GPU端已经超越了CPU端,成了许多项目性能压力的最主要来源,但GPU端往往更多地涉及到硬件层面的问题,市面上不同的厂商不同的芯片会反应出不同的现象,为开发者进行机型分档和标准制定造成了困扰;从另一个角度来说,针对GPU性能的检测往往不是Unity引擎相关的工具所能提供的,而硬件厂商提供的各类工具则繁多而难用,看得人眼花缭乱而抓不住重点,实质上为GPU性能问题的排查和优化带来了巨大的困难。


2. 带宽

2.1 GPU压力与发热/耗能
虽然UWA内部做过一些带宽与发热/耗能关系的定量实现,但芯片底层的情况远比我们想象的复杂。我们从经验和跟芯片厂家的专业硬件工程师沟通后得出的结论是:移动设备上GPU带宽的压力还是比较影响能耗的,特别是在发热这一方面也有不小的影响。但这是定性的说法,目前我们和芯片厂商都没有特别定量的公式来具体说明其影响大小。因此,当一个项目的发热或者能耗较大,带宽是开发者特别需要关注的地方。

UWA工具能够监控游戏测试过程中硬件温度的变化,一般而言长时间维持在55℃以上就是需要警惕的温度了。在UWA进一步的GPU专项服务中,还会更详细的采集和展示GPU和CPU分别的温度。

2.2 GPU压力与帧率
上文中已提到过,GPU压力大会使得CPU等待GPU完成工作的耗时增加,与此同时,也会使得渲染模块CPU端的主要函数耗时上升,从而影响帧率、造成卡顿。

除此之外,由于移动端硬件客观上存在体积较小、散热难的问题,使得GPU发热会物理上显著影响到CPU芯片温度同时上升,严重的即产生降频现象。

除了通过UWA工具监控Gfx.WaitForPresent和渲染模块主函数的耗时数据外,GOT Online Overview模式下还反映了测试中GPU耗时数据,从而直观地监控GPU性能瓶颈;而目前GOT Online还集成了Mali GPU统计带宽的API,使用Mali芯片的测试机提交Overview报告,就可以获得GPU着色和带宽数据。后续也将逐步添加对于高通等硬件的相关功能支持。

在UWA进一步的GPU专项服务中,还会结合带宽数据和Clocks数据,针对高压场景逐DrawCall地分析其占用带宽和GPU耗时过高的原因,4.3开始的部分会讨论一些常见的原因。

2.3 优化带宽
带宽数据是衡量GPU压力的重要参考。以相对高端的小米10机型而言,全分辨率情况下,如果需要跑满30帧并发热情况稳定,则需要将总带宽控制到3000MB/s以下。为此,常见的优化手段有:

(1)压缩格式
在内存的章节中已经或多或少地讨论过,使用合理的压缩格式,能够有效降低纹理带宽。

(2)纹理Mipmap
对于3D场景而言,对其中的物体所用到的纹理开启Mipmap设置,能够用一小部分内存上升作为代价有效降低带宽。当物体离相机越远,引擎就会使用更低层级的Mipmap进行纹理采样。但Mipmap设置也与合理的纹理分辨率挂钩,一个常见的现象是在实际渲染过程中只用到或者绝大部分用了Mipmap的第0层进行采样,从而浪费了内存,所以要考虑降低这类纹理的分辨率。

UWA工具使用不同颜色来表示使用不同Mipmap层级采样的像素以便定位问题;在进一步的服务中,还会根据Mipmap各层级使用率罗列出每个场景中存在上述浪费现象的纹理。

(3)合理的纹理采样方式
除了合理使用Mipmap非0层采样外,还应关注项目中各向异性采样和三线性插值采样。概括来说,纹理压缩采样时会去读缓存里面的东西,如果没读到就会往离GPU更远的地方去读System Memory,因此所花的周期越多。采样点增多的时候,cache miss的概率就会变大,造成带宽上升。各向异性采样次数在Unity中设置有1-16,应尽量设置为1;三线性采样采8个顶点,相对于双线性采样是翻倍的。

UWA的本地资源检测服务能够统计并列出开启各向异性或三线性采样的纹理资源。

(4)修改渲染分辨率
直接修改渲染分辨率为0.9倍乃至更低,减少参与纹理采样的像素,更加有效地降低带宽。

此外,还应注意读顶点的带宽。相比纹理,它的占比一般会比较小。但不同于纹理的是,修改渲染分辨率可以有效降低读纹理的带宽,但读顶点的带宽不会受到影响。所以在上文中针对网格资源和同屏渲染面片数的控制卓有成效后,读顶点的带宽值应该占总带宽的10%-20%较为合理。


3. Overdraw

Overdraw,即多次绘制同一像素造成的GPU开销。在场景中渲染顺序控制合理的理想状况下,不透明物体的Overdraw应控制在1层。所以,造成Overdraw的主要元凶就是半透明物体,也即粒子系统和UI。

3.1 粒子系统
灵活使用UWA的性能分析工具,可以有效定位对GPU压力贡献大的粒子系统。

一种做法是,建立一个专门的空的测试场景,在其中顺次播放我们项目中要用到的粒子系统,然后使用UWA SDK进行打包测试提交GOT Online Overview报告,就可以在GPU耗时曲线处,结合测试截图找到播放时GPU耗时较高的粒子系统了。

还有一种做法是直接使用UWA本地资源检测报告,可以直接看到造成Overdraw较高的粒子列表作为参考。

在筛选出需要优化的粒子系统后,对于低端设备尽可能降低它们的复杂程度和屏幕覆盖面积,从而降低其渲染方面的开销,提升低端设备的运行流畅性。具体做法如下:

(1)在中低端机型上对粒子系统的Max Particles最大粒子数量进行限制;

修改前:

限制Max Particles为10后:

(2)在中低端机型上只保留“重要的”的粒子系统,比如对于一个火焰燃烧的特效,只保留火焰本身,而关闭掉周边的烟尘效果;

(3)尽可能降低粒子特效在屏幕中的覆盖面积,覆盖面积越大,越容易产生重叠遮盖,从而造成更高的Overdraw。

关于粒子系统的优化,我们曾在UWA DAY 2018中对移动游戏的GPU性能优化中做了些剖析,通过从Fillrate和Shader等几方面出发,结合大量优化过程中的实际案例分析游戏在GPU端的性能瓶颈:《移动游戏的GPU性能优化》

3.2 UI
(1)当某个全屏UI打开时,可以将被背景遮挡住的其他UI进行关闭。
(2)对于Alpha为0的UI,可以将其Canvas Renderer组件上的CullTransparent Mesh进行勾选,这样既能保证UI事件的响应,又不需要对其进行渲染。
(3)尽可能减少Mask组件的使用,不仅提高绘制的开销,同时会造成DrawCall上升。在Overdraw较高的情况下,可以考虑使用RectMask2D代替。
(4)在URP下需要额外关心是否有没必要的Copy Color或者Copy Depth存在。尤其是在UI和战斗场景中的相机使用同一个RendererPipelineAsset的情况下,容易出现不必要的渲染耗时和带宽浪费,这样会对GPU造成不必要的开销。通常建议UI相机和场景相机使用不同的RendererData。


4. 渲染效果

除了粒子特效外,我们往往还喜欢用一些炫酷的渲染效果来丰富游戏的表现,比如体积雾、体积光、水体、次表面反射等等,然而场景中用到的此类效果越多,Shader越复杂,给GPU带来的压力越是大到远远超出接受范围的程度。

优化和权衡是决定最后留下哪些渲染效果的主要手段。

一方面,从多个方案中对比选取效果和性能较优的,对开源方案根据自身项目需要进行精简优化;另一方面,根据机型分档和当前GPU压力,取重点而舍次要。可以在UWA社区的博客、学堂和开源库中发现一些优化良好、通过实践检验的优秀方案。


5. 后处理

Bloom几乎是最受开发者喜爱、最为常见的后处理效果了。常见的一个问题是,Bloom默认是从1/2渲染分辨率开始进行下采样的。对此,可以考虑在中低端机型上从1/4分辨率开始进行下采样,或减少下采样次数。

各种后处理效果的性能开销和实际使用场景并不相同,在实际项目中遇到的问题也往往各不相同。UWA社区中也有大量后处理相关的文章和资源,比如《屏幕后处理效果系列之常见后处理效果篇》


6. 渲染策略

6.1 绘制顺序
当场景中存在先画离相机较远的不透明物体,再画离相机较近的物体,而且两者有所重合时,较远物体被较近物体所遮挡部分的像素就有可能被绘制两次,从而造成Overdraw。

这种情况常发生在地形上。本来当不透明物体的Render Queue一致时,引擎会自动判断并优先绘制离相机更近的物体。但对于地形而言往往有的部分比其他物体离相机更近,有的却更远,从而被优先绘制。

所以,需要通过对Render Queue等设置,使得离相机越近的物体(如任务、物体等)越先绘制,而较远的如地形等最后绘制。则在移动平台上,通过Early-Z机制,硬件会在片段着色器之前就进行深度测试,离得较远的物体被遮挡的像素深度检测不通过,从而节省不必要的片元计算。

6.2 无效绘制
存在一些视觉效果不明显可以关闭,或者可以用消耗更低的绘制方案的情况。

比如一种较为常见的情况是,某些绘制背景的DrawCall,本身屏占比较大,开销不小,但在引擎中开关这个DrawCall没有明显的视觉变化,可能是在制作过程中被弃用或者被其他DrawCall完全掩盖了的效果,则可以考虑予以关闭。

还有一种情况是,一些背景是用模型绘制的并带有模糊、雾效等额外的渲染效果。但场景中视角固定、这些背景也几乎不发生变化,则可以考虑用静态图替代这些复杂的绘制作为背景,在低端机上把更多性能留给主要的游戏逻辑和表现效果。

6.3 渲染面积
渲染面积过大造成的性能问题已经在粒子特效中有所反映和讨论了。但事实上对于不透明物体也适用。对于一个DrawCall而言,当它的渲染面积较大、且渲染资源多而复杂时,两者遍呈现出一种乘积的作用,它意味着有更多的像素参与纹理采样,参与Shader计算,给GPU带来更高的压力。


7. Shader复杂度

除了纹理、网格、Render Texture,还有一种对GPU压力贡献极大的渲染资源,也就是Shader。UWA尤其关注Fragment Shader的屏占比、指令数和时钟周期数,渲染的像素越多、复杂度越高,说明该Shader资源越需要予以优化。

其中,使用Mali Offline Compiler工具可以获得Shader的指令数和时钟周期数。UWA的进一步服务中会详细测试项目中所有使用率较高的Shader在不同关键字组合下变体的复杂度,从而定位需要着重优化的Shader资源及其变体。


8. 总结

这篇文章之所以称为简谱,实在是因为这些笔墨远不能达到面面俱到,很多内容还未涉及到,或者限于篇幅和重点不能深入讨论。它更多地是立足于如何以用好一套完善完整的性能工具为基础,构建发现问题-解决问题-监控问题的优化思维和优化体系,使得性能优化的工作事半而功倍。更多的优秀内容,欢迎在UWA社区中进行搜索。


至此《Unity移动端游戏性能优化简谱》已经全部更新完毕,文章从Unity移动端游戏优化的一些基础讨论出发,例举和分析了近几年基于Unity开发的移动端游戏项目中最为常见的部分性能问题,并展示了如何使用UWA的性能检测工具确定和解决这些问题。内容包括了性能优化的基本逻辑、UWA性能检测工具和常见性能问题,希望能提供给Unity开发者更多高效的研发方法和实战经验。