性能调优,一直是游戏上线之前的很重要的一个环节, 游戏帧率过低,手机发烫, 低端机上跑不起来等, 这些都需要来做优化,今天我们来给大家分享Unity做性能调优的指导思想与解决方案。
这里有个unity学习交流小组点击可以直接加入,一起学习交流吧
性能调优的指导思想
接触过很多刚做性能调优的小伙伴,他们做性能调优最大的问题一开始就通过猜测,推断来做性能调优,缺乏一个做性能优化的系统指导流程, 导致优化的效果不好。性能调优首先要分析问题,定位问题,证明是这块有问题后再对症下药,着手优化,拿出解决对策。没有证明问题之前,不要随便动手优化,就像医生,看病做手术前先做检查。不确定问题就随便开刀,那是极不负责任的做法。
大家可以回想一下,当帧率低的时候,你马上就是去查Drawcall,去优化降低Drawcall,这里我想说,做性能优化之前,先定位,证明确实是Drawcall的问题导致性能下降,才动手。如何来定位问题,找出引起问题的代码或物体呢?接下来给大家介绍一些方法。
(1)对比法
例如, 刚开始的时候帧率是60FPS,同一个场景运行一段时间以后是30FPS,如何优化,像这种符合很明显的对比场景,我们可以通过前后对比,来找出60FPS,30FPS,最大的变化。一般可以通过打开stats, 来观察前后主要的变化,main thread耗时,render thread耗时, 三角形面数,内存等信息,看看前后明显发生的变化。看到数据变化以后,再来思考整个过程中代码做了哪些事情,出现过哪些物体,最可能出的问题的地方,然后再证明, 只有证明了问题的存在,再着手优化。
(2)隔离法
这个是我经常用的一个方法, 就是隔离可疑区域和代码,然后比较,逐步排查,缩小问题范围。隔离法其实是非常好的方法,看上去很笨,很慢,但其实效率远比你想像的要好。比如,场景的物体过多,这个问题,那么我就可以采取隔离方法,先减少掉一倍,看效果。某个代码引发了帧率下降,可以逐步的注释掉代码,来观察结果,隔离的时候,可以采用二分查找,一半一半的排查。排查范围很快被缩小,问题也就精确的定位了。
(3)反经验法
上面介绍了常用的两种方法,定位问题,还有其他的一些方法,但我更想说的就是反经验法。你没看错,反经验,在性能优化的时候,不要过分相信经验,一步一步的逐步分析,有条不稳。不管什么问题,按部就班,不要根据经验,武断的下结论,要严格证明问题所在后再动手, 很多小伙伴遇到性能问题后,把问题简单的描述一下,去请教一个经验丰富的人,问他可能是哪方面的问题,根据他的猜测,你来优化。这种方式是很难做好你项目的性能优化的,项目和项目都不一样,连具体的问题都没有定位,你去请教经验丰富的老者,从老者的角度来说也无法帮到你。不要迷信经验,在动手前一定要定位好谁的问题,然后才能针对具体问题下对策。
优化架构, 标准规范化开发流程
好的框架设计,不仅让大家能很好的在一起工作,同时更方便我们快速的反应和定位问题。你的项目不好定位问题,那么一定是框架设计做的不好。所以开始做项目的时候,框架设计要花点时间,模块尽可能的独立,模块入口尽量清晰,整个框架设计能很好的隔离错误的蔓延。资源视图与逻辑代码分离,游戏数据与代码逻辑分离。这些都有助于快速的定位问题,比如我要验证是否物体过多,导致性能问题,只要在表格上配置一下,少放一些物体就可以验证,如果觉得是某个功能,导致消耗CPU,可以把物体的这个功能的组件关闭或不添加,比如怀疑怪物AI,可以注释掉添加怪物AI的组件,来证明。好的框架设计,在定位问题的时候,就变得非常简单。如果是ECS,定位会更加简单,内存看Entity与资源,算法性能看System与物体规模……
接下来要给大家介绍的是我认为最重要的经验,就是标准化,规范化项目开发进度和流程, 把性能问题一开始就放到日常项目开发中。我带项目的时候,每个礼拜至少做一次发布版本的测试,包含各个平台,不同的手机等。严格监测性能各项数据,哪个礼拜出现性能问题,马上可以回溯,复盘数据,着手摸清楚并解决,尽快修正。项目立项后,尽快的验证核心玩法极限时的性能开销与测试。不要等游戏项目做完了,才去验证,在设计初期验证完成后,针对一些性能问题组织技术公关,可能还要修改策划需求和玩法, 早期把性能问题尽快越早摸清楚,是确保整个项目成功上线的关键,不要等到上线了才到各平台做测试,那个时候功能多了,不知道有些问题是从什么时候开始就有的。标准的严谨的开发流程,伴随项目监控性能数据,尽快验证核心玩法,提前引入测试, 让我们整个项目开发做到有问题尽早发现,尽早纠正。
Unity 性能优化方法集锦
讲完指导思想,和开发流程这些宏观问题后, 我们再来讨论一些具体问题的常用的优化方法。
(1)包体体积优化:
声音文件优化
将wav,压缩成mp3或ogg, 最好是ogg,没有版权问题, 将多声道变成单声道。改变声音的采样率与码率,减少声音文件体积。
字体文件优化
尽量多使用系统字库,这样不用额外带字体文件。可以使用一些位图字库,来替代额外的字库。如果你的文本内容是固定的,可以对字库进行裁剪,把不用的字从字库里面删除掉,减少自带字库的体积。
贴图文件大小优化
将png转jpg, 并使用图片压缩软件,压缩贴图体积, 尽量减少图片数目,复用图片,尽量能使用颜色等代码的方式来代替贴图。
3D模型文件
根据需求适当降低模型面数, 去掉多余不用的数据, 可以通过法线贴图,高度贴图等将低模做出高模的效果, 合并贴图通道数据(PBR工作流中,将金属度,粗糙度,环境遮挡放一个纹理里面, 将数据合并到贴图不用的通道,比如Albedo贴图,Alpha通道不用,可以存放环境遮挡等,节约贴图数目)。
将资源ab包化
把所有的资源分成一个一个的ab包,ab包本身有压缩功能,这样整体的包体也会减小,如果可能,可以把ab包全部或部分放服务器,第一次运行游戏的时候再下载,比如《王者荣耀》,这样来节省包体体积。
(2): 骨骼动画优化:
3D游戏中骨骼动画也是非常消耗性能的地方,因为每帧,我们都要通过动画组件采样,采样后重新计算出来我们的顶点的位置,传给GPU的渲染管线来处理绘制, 我们可以将骨骼动画的每帧顶点的位置缓存到一个纹理贴图里面,用空间换时间的方式来节约动画组件的开销,并尝试合并Drawcall。
(3): LOD的优化:
对于大规模多角色的场景游戏,我们要开启LOD,让远处的物体用尽量少的面,近处的物体用更多的面, 来提升我们运行时候的性能。
(4): 使用3D模型细节增强,提升计算性能:
使用法线贴图,高度贴图等细节增强手段来降低模型的面数同时,能获得和高模同样的细节和更好的计算性能。
(5): 光照优化:
静态光照烘培+反射探头来做更好的场景效果,代替实时的光照计算,减少光源的数目。对于一些物体的发光特效,可以通过shader来实现,而不用加光源。定制Shader,可以将逐像素光照改为逐顶点光照,节约逐像素光照的计算开销。
6: SetPassCall与Drawcall优化
SetPassCall
SetPassCall通俗的讲就是更换重新装载在渲染管线里面的Shader代码和配置,就像换画笔一样的。SetPassCall开销非常的大,所以尽可能的要少用一些不同的Shader,在一个场景里面。尽可能的让同一个Shader 绘制最多的物体后再切换下一个Shader的物体。尽量避免绘制物体的时频繁交叉的来回切换Shader,节约SetPassCall的次数。
DrawCall
DrawCall 优化,其实很简单, 三种优化手段,静态合批,动态合批,GPU Instancing合批, 然后我们针对合批的条件去达成对应的条件就可以了,比如,我们做一个捕鱼达人3D游戏,我们有很多的鱼,可以把鱼的所有纹理贴图,放到一个大图里面,这样,这些鱼就是同样的Shader,同样的纹理贴图对象,就可以达到合批的条件而做到合批, 节约Drawcall。
(7): 物理引擎的优化:
能不用物理引擎的游戏,尽量不用物理引擎,能自己实现的尽量用代码自己实现,因为物理引擎的开销毕竟摆在那里了, 物理引擎使用很简单,但是性能开销也不可忽视。有一些不是真正的物理游戏,比如Moba类的游戏,防止物体穿透等,其实我们可以不用物理引擎,通过制作地图格子的方式,标记障碍物,自己控制的时候,如果进入障碍物的标记,就不让它更新位置即可,这些可以替换物理引擎获得很好的性能效果。常用的有菱形的地图格子与六边形的地图格子。
暂时不用的刚体,或者不在视线范围内的刚体可以根据游戏的需要,显示和隐藏,达到节约物理引擎计算的目的。修改物理引擎全局的迭代参数,获得实用性的同时又有更好的性能。了解物理碰撞器的性能开销,球体碰撞器最小,网格碰撞器最大,尽量使用开销小的碰撞器, 对于静态物体,能够合并物理碰撞器的尽量用一个物理碰撞器。
(8): 阴影的优化:
通过开关控制阴影,在低端机上关闭阴影获得更好的运行帧率,在高端机上开启阴影,获得更好的运行效果。通过伪阴影技术来做贴图,节约阴影计算的开销, 可以自己定制Shader来实现高性能的阴影效果。
(9): Shader优化:
减少Shader中的条件判断和展开循环,来获得更好的指令缓冲cache 命中率, 减少一些复杂的计算,可以采用一些空间换时间的方法来缓存计算结果。逐顶点计算来替代逐片元的计算,减少计算次数。可以把Shader 设置为常驻内存缓存,这样节约SetPassCall所带来的开销。
10: C#良好编码习惯与算法思想
Update里面尽量不要使用GetComponent, Find等,可以在初始化的时候先找出来,能放Update里, 尽量不放FixedUpdate, 这样低端机上能节约计算次数。全局设定,降低FixedUpdate每秒调用迭代的次数,节约FixedUpdate迭代时的计算时间。避免大量反复的new 对象。一些重复使用的对象,可以通过节点池等先反复使用,避免重复创建与释放。
可以用多线程来做一些计算,避免卡住main thread游戏线程导致帧率下降。不要在游戏主线程里面直接做同步的IO以免卡住游戏线程,导致帧率下降。
从开始写代码起,要把每行代码写好,长期的坚持积累,才能形成良好的代码习惯,写出高性能,高质量,高效率的代码,学好数据结构与算法,能很好的分析算法的时间复杂度,空间复杂度等。
经过上面的一些讨论,可能还会有很多优化技巧,欢迎大家留言讨论。同时我们有很多免费的Unity 性能优化的视频课程,供大家可以学习,成为优化优化的高手,