CPU方面的性能消耗主要是引擎渲染、物理计算、脚本代码等方面的性能开销。因此针对这几个方面对CPU进行优化。
降低DrawCall
Unity生成一帧画面的过程:引擎先确定摄像机可以看到的物体,然后把这些物体的顶点(包括本地位置、法线、UV等),索引(顶点如何组成三角形),变换(物体的位置,旋转,缩放,摄像机位置等),相关光源,纹理,渲染方式(由材质/shader决定)等数据准备好,然后通知GPU开始绘制,GPU基于这些数据,经过一系列的运算,在屏幕上画出成千上万的三角形,最终构成一副图像。
Unity每次准备数据并通知GPU渲染的过程称为一次DrawCall,该过程是逐个物体进行。
1、Static Batching(静态批处理)
将静止的物体标记为Static,批处理相同材质或纹理的静态对象,合并材质。但有个缺点是它把批处理中的所有物体组合到一起,相当于创建了一个与这些物体加起来一样大的物体,这样就需要分配相应大小的内存,会导致内存损耗。因此,要平衡各方面的效率。
原理是化整为零,将多个场景物体预先合成一个大的物体进行绘制,unity会先整合成一个大的vbo(顶点缓冲区对象),而不整合IBO(索引缓冲区对象),一次性提交vbo给GPU,然后并不是把整个vbo都绘制,而是每次需要绘制其中某些物体时改变IBO,选择大vbo上的某一段进行绘制。静态合批可以将多个小物体的绘制合并成一个大物体的绘制,减少对渲染状态的改变,它一次并行绘制多个物体。一个vbo的大小是有限制的,如果物体数量过多,也会被拆分成多个绘制。
2、Dynamic Batching(动态批处理)
该处理是完全自动进行的,对于顶点数在300以内的可移动物体,只要使用相同材质,就会组成Batch。
Tips
- 动态批处理仅支持小于900顶点的网格物体。
- 如果着色器使用顶点位置,法线和UV值三种属性,那么只能批处理300顶点一下的物体;如果着色器需要使用顶点位置,法线,UV0,UV1和切向量,只能批处理180顶点以下的物体。
- 不要使用缩放。分别拥有缩放大小(1,1,1)和(2,2,2)的两个物体不会批处理。
- 统一缩放的物体不会与非统一缩放的物体进行批处理。
- 使用不同材质的实例化物体将导致批处理失败。
- 缩放大小(1,1,1)和(1,2,1)的两个物体不会进行批处理,但使用缩放大小(1,2,1)和(1,3,1)的两个物体可以批处理。
- 使用lightmap的物体含有额外(隐藏)的材质属性,比如lightmap的偏移和缩放系数等,不会批处理。
- 多通道的shader会妨碍批处理。
- 预设体的实例会自动使用相同的网格模型和材质。
合并图集
将多个纹理打包成图集来尽量减少材质的使用。合并贴图时应该注意选择同时出现在屏幕的对象贴图进行合并。
光照和阴影
实时光照和阴影可能增加DrawCall,带有光源计算的shader材质会因为光照产生多个DrawCall。使用灯光会打断DrawCall Batching,尽量使用烘焙灯光贴图来实现灯光效果。
物理组件
- 设置一个合适的Fixed Timestep。减少物理计算的次数,来提高游戏性能。
- 不要使用网格碰撞器(mesh collider)。
脚本代码
- 不要频繁使用GetComponent等接口,尤其是在循环中,最好将组件缓存。
- 使用内建的数组,比如用Vector3.zero而不是new Vector(0,0,0)。
- 善于使用ref关键字。
- 将经常需要使用的属性查询缓存起来。
- 习惯性的将暂时不用的Gameobject设置为非激活。
- 不要频繁使用Instantiate和Destroy,使用对象池。
- 尽量少使用Update,LateUpdate,FixedUpdate,这样提升性能,多使用事件(不是sendmessage,而是C#中的事件委托)。
- 同一脚本中频繁使用的变量建议声明为全局变量,脚本之间频繁调用的变量或方法建议声明为全局静态变量或方法。
- 数组、集合类元素优先使用Array,其次是List。
- 脚本不使用时禁用掉,需要时再启用。
- 可使用Ray来代替OnMouse***类方法。
- 需要隐藏/显示或实例化来回切换的对象,尽量不要使用SetActive,而是将对象远远移出相机范围和移回原位。
- 尽量少用模运算和除法运算。比如a/5f,要写成a*0.2f。
- 对于不经常调用或更改的变量或方法建议使用Coroutine&yield。
- 在Update里避免search,如FindWithTag,Getcomponent这样的调用,可以在Start中预先存起来。
- 尽量减少函数调用栈。用x=(x>0?x:-x)代替x=Mathf.Abs(x)。
- 善于使用OnBecameVisible()和OnBecameInvisible()方法。
- 若不需要每帧处理,可以隔几帧处理一次。
Void Update() { if(Time.frameCount%6==0) { DoSomething(); } }
- 尽量使用整数数字,因为iPhone的浮点数计算能力很差。
注:以上内容来源于网上搜集整理
Comments