Unity性能优化汇总

https://blog.csdn.net/billcyj/article/details/79441193

CPU方面的优化

CPU优化不够会出现的问题:

  1. 由于短时间内的计算量太大,导致画面流畅性降低,俗称跳帧
  2. 发热严重,耗电量高

CPU优化方向:

  1. DrawCalls
  2. 物理组件
  3. GC(GC为处理内存,此项为CPU使用GC处理内存时产生的性能损耗)
  4. 代码

Drawcalls

Drawcall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。

如何优化:

1)使用Draw Call Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。

  1. 静态批处理Static Batching:只要是静态不动的物体且具有相同材质的话就可以使用静态批处理来降低描绘调用
  2. 动态批处理Dynamic Batching:动态批处理是引擎自动进行,无需设置,当物体共享相同的材质,则引擎就会自动对Drawcall进行优化,也就是动态批处理(如实例化预制体)。动态批处理存在约束,稍有不慎就会增加Drawcall

动态批处理有很多约束:顶点数、缩放、不同材质等约束都不会自动批处理,所以尽量使用静态批处理。

2)UGUI需将同一界面的UI元素打包成图集。

物理组件

1) 处理Rigidbody时,使用FixedUpdate,设置Fixed timestep(固定时间步),减少物理计算次数,运动起来也平滑,提高游戏性能。

2)减少FPS,即减少每秒的帧数,在ProjectSetting-> Quality中的VSync Count 参数会影响你的FPS,EveryVBlank相当于FPS=60,EverySecondVBlank = 30;这两种情况都不符合游戏的FPS的话,或通过代码手动设置。 
降低FPS的好处: 
1.省电,减少手机发热的情况; 
2.能稳定游戏FPS,减少出现卡顿的情况。

3)尽量不用MeshCollider

如果可以的话,尽量不用MeshCollider,以节省不必要的开销。如果不能避免的话,尽量用减少Mesh的面片数,或用较少面片的来代替。

4)粒子组件,屏幕上最大粒子数量建议小于200个,并关闭粒子的碰撞功能。

GC

虽然GC是用来处理内存,即回收垃圾的,但是却会增加CPU的开销。首先我们要明确所谓的GC是Mono运行时的机制,而非引擎的机制,而它管理的也是Mono的托管堆,而非引擎的本机堆。所以,GC不是用来处理引擎的assets(纹理,音效等等)的内存释放的,因为U3D引擎也有自己的内存堆而不是和Mono一起使用所谓的托管堆。其次我们要搞清楚什么东西会被分配到托管堆上?就是引用类型咯。比如类的实例、字符串、数组等等;而作为int,float,包括结构体struct其实都是值类型,它们会被分配在堆栈上而非堆上。所以我们关注的对象无外乎就是类实例、字符串、数组这些了,所以GC的优化说白了也就是代码的优化。

此部分的代码优化只针对是否会触发GC:

1)字符串处理。如需多次用String的+运算符来拼接字符串,就用StringBuilder的Append方法。

2)不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。

3)使用对象池,以实现空间的重复利用。

4)最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。

代码

除了使用使用合理的算法和数据结构外,还有如下需要注意:

2)不要频繁使用GetComponent去频繁获取组件。如果要使用,可申明为全局变量,并只需在Awake函数中GetComponent。

3)使用内建数组如使用Vector3.zero而不是new Vector(0,0,0);

4)脚本在不使用时,禁用之,需要时再启用;

5)可以使用射线Ray来代替OnMouseXXX类方法

6)尽量少用模运算和除法运算,比如a/5f,一定要写成a*0.2f

7)不要使用原生的GUI方法,即OnGUI函数

8)务必删除脚本中为空或不需要的默认方法

9)同一脚本中频繁使用的变量建议声明其为全局变量,脚本之间频繁调用的变量或方法建议声明为全局静态变量或方法

10)尽量使用整数数字,因为iPhone的浮点数计算能力很差

11)将计算分到多个逻辑帧中进行计算,避免短时间内的性能超过负荷,俗称“分帧”。

12)将可以缓存的数据尽可能的缓存起来,避免重复计算和重复分配内存。例如:不要重复实例化同一个对象,可以事先建好对象池

GPU的优化

GPU优化不够会出现的问题:

  1. 发热严重,耗电量高
  2. FPS降低

GPU优化方向:

  1. 像素的复杂度:比如实时阴影(手游中禁用),复杂的shader等等
  2. 顶点过多,即模型复杂面数多
  3. GPU的显存带宽

除了优化美术资源,包括合理规划图集、约定好模型的最大三角形面数、制定合理的粒子效果规范等;再除了使用平台推荐的压缩格式,比如安卓平台的ETC1和IOS平台的PVRTC。还有如下优化建议:

简化或者优化着色器(Shader)

1)少使用的函数:pow,sin,cos等

2)fixed、half、float慢慢斟酌着使用,切忌浪费,采取够用的类型就行

3)在游戏开始前就对Shader进行编译和加载。

减少绘制数目

抛开美术建模时应该注意顶点数和面数不说,说说程序方面该如何处理:

1)保持材质的数目尽可能少。这使得Unity更容易进行批处理。

2)使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。

3)如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。

4)使用光照纹理(lightmap)而非实时灯光。

5)使用LOD(细节级别渐变),好处就是对那些离得远,看不清的物体的细节可以忽略。也就是根据距离的远近使用不同模型级别,这样就可以减少模型上面的顶点和面片数量从而提高性能。

6)遮挡剔除(Occlusion culling)

优化显存带宽

1)压缩图片,减小显存带宽的压力。例如:可以通过减色的方式减少图片大小。许多UI其实用的色彩很少,用不到256色,这类图片就可以使用减色压缩。

2)使用MipMap(纹理贴图金字塔)贴图会根据摄像机距离模型的远近而调整不同的不同质量的贴图显示。 
Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。

内存的优化

内存优化不够会出现的问题:

闪退和卡死,比如安卓的低内存杀手Low Memory Killer会在低内存情况下杀掉内存占用过大的程序。

实际上Unity游戏使用的内存一共有三种:

  1. 程序代码
  2. 托管堆(Managed Heap)
  3. 本机堆(Native Heap)。

除了动态加载和卸载资源,比如在游戏内的时候,我们可以把游戏外的一些UI图集卸载掉;除了降低资源质量或屏幕分辨率,这是有损优化,一般作为最后的手段。还有如下优化建议:

程序代码

程序代码包括了所有的Unity引擎使用的库、以及你所写的所有的游戏代码。 
在编译后,得到的运行文件将会被加载到设备中执行,并占用一定内存。这部分内存实际上是没有办法去“管理”的,它们将在内存中从一开始到最后一直存在。一个空的Unity默认场景,什么代码都不放,在iOS设备上占用内存应该在17MB左右,而加上一些自己的代码很容易就飙到20MB左右。想要减少这部分内存的使用,能做的就是减少使用的库,稍后再说。 
也许,这就是,为什么不用Unity高效开发APP的原因吧?但还是可以考虑用Unity开发APP,毕竟还好吧~

托管堆

托管堆是被Mono使用的一部分内存。Mono是一个开源的.net框架的一种实现,对于Unity开发,其实充当了基本类库的角色。托管堆用来存放数组、字符串、类的实例等。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存,并且定时地使用垃圾回收(GC)来释放已经不需要的内存。

本机堆

本机堆是Unity引擎进行申请和操作的地方,比如贴图,音效,关卡数据等。Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能。基本理念是:如果在这个关卡里需要某个资源,那么在需要时就加载,之后在没有任何引用时进行卸载。 
听起来很美好也和托管堆一样,但是由于Unity有一套自动加载和卸载资源的机制,让两者变得差别很大。自动加载资源可以为开发者省不少事儿,但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你懂的),也是Unity给人留下“吃内存”印象的罪魁祸首。

优化程序代码的内存占用

这部分的优化相对简单,因为能做的事情并不多:主要就是减少打包时的引用库(即 库剥离),改一改build设置即可。

  1. 当使用Unity开发时,默认的Mono包含库可以说大部分用不上,在Player Setting面板里,将最下方的Optimization栏目中“Api Compatibility Level”选为.NET 2.0 Subset,表示你只会使用到部分的.NET 2.0 Subset,不需要Unity将全部.NET的API包含进去。
  2. 接下来的“Stripping Level”表示从build的库中剥离的力度,每一个剥离选项都将从打包好的库中去掉一部分内容。你需要保证你的代码没有用到这部分被剥离的功能,选为“Use micro mscorlib”的话将使用最小的库。库剥离可以极大地降低打包后的程序的尺寸以及程序代码的内存占用。

实际上,在游戏开发中绝大多数被剥离的功能使用不上的,因此不管如何,库剥离的优化方法都值得一试。

IO和网络

IO和网络优化不好会出现的问题:

  1. 网络延迟甚至掉线
  2. 加载资源导致的跳帧
  3. 加载时间过长

常见的优化手段:

  1. 使用独立的线程进行加载,Unity中还能利用协程
  2. 减少网络包里面的冗余数据
  3. 合并小包,减少请求数据的次数
  4. 分帧对回包进行处理
  5. 限制一定时间内的发包频率

Unity官方优化建议

不用不是每个主流手机都支持的技术,就是如果可以不用就不用或有备选方案。

渲染

1.不使用或少使用动态光照,使用light mapping(光照贴图)和light probes(光照探头) 
2.不使用法线贴图(或者只在主角身上使用),静态物体尽量将法线渲染到贴图 
3.不使用稠密的粒子,尽量使用Animation和UV动画 
4.不使用fog,使用渐变的面片(参考shadow gun) 
5.不要使用alphatest,使用alphablend代替。移动端用alphablend代替alphatest实际是没有办法的办法,并不是说完全可以做到alphatest的效果 
6.使用尽量少的material,使用尽量少的pass和render次数,如反射、阴影这些操作 
7.如有必要,使用Per-Layer Cull Distances(摄像机分层距离剔除) 
8.只使用mobile组里面的那些预置shader 
9.使用occlusion culling(摄像机遮挡剔除) 
10.远处的物体绘制在skybox上 
11.使用drawcall batching(动态批处理和静态批处理): 
对于相邻动态物体:如果使用相同的shader,将texture合并 
对于静态物体,batching要求很高,详见Unity Manual>Advanced>Optimizing Graphics Performance>Draw Call Batching 
12.规格上限 
每个模型只使用一个skinned mesh renderer(蒙皮的网格渲染器) 
每个mesh不要超过3个material 
骨骼数量不要超过30 
面数在1500以内将得到好的效率

物理

1.真实的物理(刚体)很消耗,不要轻易使用,尽量使用自己的代码模仿假的物理 
2.对于投射物不要使用真实物理的碰撞和刚体,用自己的代码处理 
3.不要使用mesh collider 
4.在edit->project setting->time中调大FixedTimestep(真实物理的帧率)来减少cpu损耗

脚本编写

1.尽量不要动态的instantiate和destroy object,使用object pool(对象池) 
2.尽量不要在update函数中做复杂计算,如有需要,可以隔N帧计算一次 
3.不要动态的产生字符串,如Debug.Log(“boo” + “hoo”),尽量预先创建好这些字符串资源 
4.提前缓存一些东西,在Update里面尽量避免search,如GameObject.FindWithTag(“”)、GetComponent这样的调用,可以在Start中预先存起来 
5.尽量减少函数调用栈,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x) 
6.下面的代码是几个GC“噩梦”

String的相加操作,会频繁申请内存并释放,导致GC频繁,使用System.Text.StringBuilder代替

不要在函数中动态new array,最好将一个array通过参数传进函数里修改。

shader编写

1.数据类型

fixed / lowp - for colors, lighting information and normals,
half / mediump - for texture UV coordinates(坐标),
float / highp - avoid in pixel shaders, fine to use in vertex shader for position calculations.
  • 1
  • 2
  • 3

2.少使用的函数:pow,sin,cos等 
最简单的方法就是预先把所有值的结果保存起来,比如一个数组

GUI

1.不要使用内置的OnGUI函数处理GUI,使用其他方案,如UGUI、NGUI。

格式

1.贴图压缩格式:ios上尽量使用PVRTC,android上使用ETC

性能分析工具

  1. Unity官方就提供了Unity Profiler和其他有针对性的分析工具
  2. 英伟达提供的性能测试工具
  3. 腾讯WeTest联合Unity官方打造的性能分析工具UPA

总结

作为前端,你应当尽量少写业务逻辑,你关注过一下的模块吗?

性能:你有没有在自己的游戏中进行Profile,观察在以上各个参数有没有达到指标 
安全:你的游戏前端代码的Release版本是否还能被别人轻易反编译,你的游戏是否还能轻易被玩家截取网络包或修改内存数据 
工具:你能不能做出更优秀的工具来给美术和策划使用,解放他们的生产力?

CPU、GPU、内存、IO网络这四个方面的优化总是相互制衡的,你把一个方面的优化做好了,另一个方面的问题又会出现了,比如,我们如果使用动态加载和卸载资源,这就虽然减少了内存占用量,但会在IO上造成加载时间延长的问题。

所以,我们在做游戏优化的时候,不能太追求完美,刚刚好就是阔以了,使得以上这四个方面能达到均衡即可,切忌在某一方面优化过头。

【参考】 
【1】Unity开发-你必须知道的优化建议 
http://blog.csdn.net/leonwei/article/details/18042603 
【2】Unity3D 性能优化 
http://www.manew.com/blog-48504-3431.html 
【3】【编程杂记】游戏性能优化的一些经验之谈 
http://www.cnblogs.com/neverdie/p/Game_Profile_Optimize.html

猜你喜欢

转载自blog.csdn.net/zhaoguanghui2012/article/details/80988818