【莉莉丝研习社】第42期 Unity游戏开发中的性能优化技巧

 

本期研习社邀请到侑虎科技创始人张鑫,他针对使用unity引擎开发的游戏和VR项目研发中实际遇到的问题,与大家分享了优秀手游的性能数据和优化方法,包括引擎的主要模块、逻辑代码、资源管理和内存使用等。让大家受益匪浅。...



本期研习社邀请到侑虎科技创始人张鑫,他针对使用unity引擎开发的游戏和VR项目研发中实际遇到的问题,与大家分享了优秀手游的性能数据和优化方法,包括引擎的主要模块、逻辑代码、资源管理和内存使用等。让大家受益匪浅。
(本文根据张鑫在莉莉丝研习社的演讲整理而成,有删节)

分享者|张鑫

(侑虎科技创始人)

分享实录:

大家好!今天下午由我跟大家分享一下我们在unity游戏方面积累的关于性能分析和优化上的经验。

我想在座的或多或少都听过UWA,很多人可能对我们和我们所做的事情不是很了解,希望通过我与大家的交流,让大家更加深刻的了解我们在做哪些事情以及我们所做的事情有哪些是对大家有帮助的。

去年我们创办了侑虎科技,主要做的是游戏和VR应用方面的技术咨询,目前主要是以性能优化为切入点,因为性能优化基本上是所有团队都需要做的事情,而且是我们最擅长的一方面。

优化是一个永恒不变的话题

为什么要做性能优化呢?性能优化是一个永恒不变的话题,在20年前开发游戏的时候或者说我们的老前辈在开发游戏的时候,性能优化就是一个大问题。无论硬件设备多么先进,无论开发团队经验多么丰富,玩家的需求和项目的要求永远在不停增长。今年开始,VR开始萌芽,VR游戏对于性能的要求更高,因为一款VR要有好的体验,需要达到90帧甚至120帧,因此我们开发了UWA,我们希望通过这个工具可以帮助大家解决在目前项目开发中遇到的性能问题。

在性能优化上我们做了哪些事情?

截止到上个月,我们已经进行了1600次的性能测试,同时生成和分析了2417万帧的性能数据。之所以在这方面积累了很多经验,是因为我们走到项目里面,一帧一帧的去看unity profile。同时我们也推出了线下服务,比如unity游戏深度性能优化,深受很多游戏团队的喜欢,目前为止,我们一共进行了35次的项目。我们在报告中分门别类的报告各种各样的模块现在会出现的问题,以及如何解决。每个报告在60-80页不等,累积总页数超过2500页,总字数超过80万字。

一般来说渲染是游戏里面开销最大的地方,在MMO游戏里可达到44.3%,在ARPG里可达到38.9%。在MMO里,第二项是UI,UI的开销很大,并且会成为一个影响大家性能开发的阻碍。在APRG里,第三项才是UI,第二项是代码和加载。这其实是有道理的,MMO的UI界面会更加复杂,各种icon很多,而ARPG的UI界面很简单,所以它本身的开销不高,排在第三项。ARPG的代码加载比MMO高很多,这并不是说MMO的团队写代码的能力或者对于性能的可控性有多么好,而是因为ARPG里面有更多的状态机,以及为了保证它的流畅性,在很多时候会选择一次性加载,这就造成它的开销比MMO要大得多。再往后就是动画、物理和粒子,基本上都是差不多的。

CPU优化:

第一部分是渲染模块,也是来自于我们统计的报表,MMO和ARPG的draw call差不多,MMO的渲染面片数均数是七万五千面,而ARPG可以达到九万面,我们推荐的值是小于十万面,MMO大概有54%的项目可以达到这个标准,ARPG大概有55.2%可以达到这个标准。

半透明渲染耗时这两者时间是差不多的,而MMO的不透明渲染耗时比ARPG的高,MMO是3.5ms,ARPG是2.9ms。

随着游戏越来越精品化,重度化,相机的后处理特效也有很多,MMO有58.3%的项目使用了相机后处理特效,ARPG有54.4%的项目使用相机后处理特效。

关于draw call和triangle的优化,大家可以查到大量的文章,这里就不多说了。除了draw call,半透明不透明之外,还有一个很大的杀手是粒子系统。在5.3之前都是比较高的开销,在5.3之后unity做了很多优化。如果使用的是unity5.3之前的版本,可以简化粒子系统,并且减小屏幕上的覆盖面积,尽可能将相同材质的粒子系统放在比较接近的深度下,这样unity本身可以把粒子系统拼合在一起从而减少draw call。

除了粒子系统之外,由于角色越来越多,包括MMO和ARPG,蒙皮网格渲染也开始了,大家如果看到蒙皮网格的渲染开销比较高,实际上没什么特别大的问题,造成这个问题的原因是面片数和骨骼数,还有shader。

第一我们建议在local里面写一些小工具,看看它的顶点数,面片数是不是合规的。

第二是骨骼数,如果骨骼数可以降下来,那它的skinned mesh还是可以降下来的。

第三是简化shader。

前面说的几个问题,几乎所有的游戏都会遇到。

相机后处理在很多精品游戏或者重度游戏里面慢慢多了起来,相机的后处理耗时是比较高的,一般我们建议这几点:

第一,尽可能使用Asset Store中mobile ready的插件。

第二,在中低端机器上适度使用复杂特效。

第三,开启多线程渲染功能,这有哪些好处呢?如果不开大概耗时10ms左右,如果开启,基本在2ms以下,开启多线程渲染对于图像的后处理非常有效,特别是使用复杂的shader。

需要说明的是,不是所有的游戏开启多线程都一定是好的。我们建议多线程可以开启,尝试使用,因为它可能会带来一些副作用,5.3以后多线程渲染在中高端机器上测下来都是不错的。

接下来是阴影,现在实时阴影用的越来越多。如果要使用unity本身自带的shadow map,一般来说适合多人游戏。但是有的游戏不需要多人,只需要主角,把主角弄得很漂亮就可以了,那就可以选择projector,它的好处在于可以投软影,而且看到的效果很棒。

如果大家用了shadow map,在整个游戏运行的测试下,它的渲染模块一共耗时是17万ms,shadow map的渲染占了2万ms,占13%,这只是shadow map的渲染,同时还有其他的,加在一起大概是15%~17%。

那么如何去优化呢?这里面的主要瓶颈是shadow receiver,我们推荐Fast Shadow Receiver,它的好处在于可以动态的选择并且可以生成它的shadow map receiver。

第二部分是UI模块,UI模块经常会出现一些问题,MMO和ARPG的类型基本差不多,在这里我们只看MMO的。目前使用NGUI的达到70%,使用NGUI的达到30%。

UGUI的性能大大好于NGUI,NGUI模块CPU耗时主体范围为0.8~10.6ms,UGUI是0.1~5.4ms,我们推荐主体范围是0~5ms,NGUI只有21.6%的达标率,UGUI有48.6%的达标率。

UIPanel.LateUpdate是NGUI最耗时的函数,0.4~8.4ms,每一万帧里堆内存的分配大概是7.6~180.8M,我们建议在20M以下,有35.8%的达标率。在UGUI里面Canvas.BuildBatch和Canvas.SendWillRenderCanvases加在一起也没有它高。每一万帧的堆内存分配是0~39M,我们对于UGUI要求更高,推荐每一万帧小于2M,但是有54%的项目都可以达到这个标准。

对于UI有以下几点是需要注意的:

第一,NGUI是按照Panel进行重建的、UGUI是按Canvas进行重建的。

第二,动态元素和静态元素尽可能的进行分离。

第三,合理配置UI Canvas/Panel,一个Cavas下的Widget不宜过多。

第四,不推荐通过Instantiate/Destory或Active/Deactive来频繁切换UI界面。

第五,控制UI的Draw Call,首先通过UISprite来替代UITexture,然后UI界面要避免重叠,此外在使用unity5.2以上的时候,z值尽可能为0,这样可以减少Draw Call。

第三部分是加载模块,加载模块也是一个很大的问题。对于引擎来说,最大的开销是loading和preloading。

第一,前一个场景资源卸载往往会占到50%;

第二是场景资源的加载,基本都是在loading这儿;

第三是GC耗时;

第四是GO销毁。

资源卸载我们建议在切场景的时候调动一次就好了,不要频繁调动,因为每一次调动都不会小于500ms。

资源加载是非常值得注重的,特别是纹理,网格,shader,动画片段。在我们的blog里面都有说明,大家有兴趣可以去看一下。

GC调用在切场景的时候如果出现的很频繁,我们是不建议的。我们对于整个项目建议是达到1000帧以上,目前来看,只有8.2%的项目能够达到1000帧以上。

怎么去优化GC调用呢?第一,优化代码堆内存,一般我们建议除了NGUI的开销之外,其他加起来尽可能小于30M,这是比较合理的。

第二当我们发现小于30M之后,那就不需要再切换场景手动调用GC,因为GC来的时候会很慢。

对于加载模块shader的解析,我们在所有的项目里面做了shader解析的耗时分布做了一个统计,平均的主体分布在4.3~146.8ms,均值是25.1ms。小于30ms的大概占53.4%,大于100ms的有9.7%。

代码优化:

在性能报告里面我们会把高CPU和高内存的top10函数全部归列,一般来说是二八原则,80%的性能基本都在20%的函数里面。

一般在报告里面我们是宏观筛选,微观定位。同时可以基于整体时间,基于特定场景,基于特定帧数。

目前来看,很多的性能问题从技术上来说都没问题,而需要从策略上进行解决,所以我们建议大家不要放过calls,很多时候我们会发现策略上存在一些疏忽。

另外还有mono的堆内存,在安卓上大部分游戏还是用的mono,mono是只升不降,我们要避免不必要的堆内存分配。

第一,避免频繁new class/constainer;

第二,控制Log输出;

第三,用For代替Foreach;

第四,用string连接;

第五,避免大量装箱操作;

第六,Lambda表达式、LINQ等合理的使用。

资源管理:

第一是AssetBundle的卸载和加载,在报表里我们不光可以看到它的加载方式,还可以看到到底是哪些AssetBundle加载,另外可以特定AB查看,同时还可以看到它的卸载方式以及卸载具体信息。

第二是具体AssetBundle的使用情况,我们可以看到它的驻留情况,是否有泄露,看每一帧的使用情况。

第三在资源加载和卸载里,可以看到加载方式有Res Load和LoadAsync这两种方式,也有AB Load和LoadAsync。可以看到资源信息加载的次数,还有就是资源的卸载。

第四是GameObject实例化与激活,一个很大的问题是经常有很多的实例化,但是我们看不到有哪些问题被实例化了,在这个里面我们可以看到多少个instantiate,多少个destory,多少个active,多少个deactive。实例化的时候不仅可以看出它的次数,并且可以看到所有详细的信息。

内存优化:

在MMO里面,内存在200M以下的占24.2%,大部分集中在200~400M,也有400~600M,600M以上的。ARPG的大部分在200~400M,主体的范围在133~544M,内存峰值的均值达到282.6M,我们推荐是小于200M比较好,现在有16.6%的达标率。

内存主要包括以下几块:

第一是Mono,主要是代码的堆内存使用情况;

第二是GfxDriver,比如Graphics Driver(DX/OGL等)分配的内存,包括Texture、Mesh、Shader等资源;

第三是FMOD,就是音频资源;

第四是unity,除上之外的所有的引擎开销,包括各种Manager、WWW、AnimationClip、Scrip。

在内存里面,最大的消耗是资源这块,而资源主要是texture,同时,Mesh和RenderTexture也是需要大家去注意的。

除了资源使用,还有内存泄漏,资源被强行Hold无法释放,表现症状为内存增长趋势明显、资源无法回收。

在这个地方我们提出了生命周期,可以进行场景比较,来查看是哪个资源被泄露了。

除了泄露还有就是资源冗余,80%的情况是AssetBundle打包出现了一些问题,所以我们也推出了资源检测工具,来检测AB的资源包含情况,AB的依赖关系,资源的冗余程度,资源的具体使用情况等等。

最后说一下,我们也一直在研究GPU,GPU这边主要有三大块,第一是带宽,我们建议大家精简纹理和网格数据;第二是填充率,是单位时间内填充的像素数量。

总结:

具体的优化经验其实并不重要,两年前的经验已经不适用于今天,今天的经验也不适用于两年后,优化的时效性很强。真正想让大家看到的是研究问题的方式和厚积薄发的态度,这才是以后能帮到大家的东西!这也是我们做UWA的真正原因,谢谢大家!




    关注 莉莉丝研习社


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册