Unity中优化方案

2020-04-20 11:30发布


Unity每次在准备数据并通知GPU渲染的过程称为一次DrawCall。


说白了,就是CPU计算游戏对象的位置、使用的图片、运动状态等信息, 然后把这些信息通过BUS总线,传送给GPU绘制出来的一次完整流程,就是一次DrawCall。


DrawCall直接影响渲染的速度,渲染速度又与Update执行频率有关,所以通常来讲,降低DrawCall有两个方面。CPU和GPU方面。


一、从CPU角度


①静态和动态批处理:批处理相同材质静态对象或相同纹理的静态对象

静态批处理:将使用相同材质的对象,合并成一次DrawCall进行渲染。

动态批处理:Unity自动分析场景中哪些对象可以合并。我们并不能深刻干预,所以还是优先使用静态批处理。

②从物理组件角度优化:设置一个合适的Fixed Timestep、不要使用MeshCollider、网格合并 (MeshBaker)

从优化的角度来看,尽量少的使用物理组件,能通过数学计算的尽量计算,物理引擎相对来讲会更耗费性能。


③较少内存中垃圾数据的产生,减少CPU在GC方面的消耗。手段如下:

⭐️字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。


⭐️尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。


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


⭐️使用“池”,以实现空间的重复利用。对象池


⭐️最好不用LINQ 的命令,因为它们会分配临时的空间,同样也是GC收集的目标。在某些情况下LINQ无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。


④通过代码质量减少CPU压力:

⭐️不要频繁使用GetComponent\Find\,尤其是在循环中

⭐️善于使用OnBecameVisible()和OnBecameInvisible()

⭐️使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0)

⭐️善于使用ref关键字. 传参(值), 值拷贝, 取值类型的地址

⭐️将经常需要使用的属性查询缓存起来

⭐️不要频繁使用Instantiate和Destroy(GameObject)设置为非激活

以上为CPU方面的优化策略。


二、GPU方面降低DrawCall


①减少画面中需要绘制的数目

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

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

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

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

⭐️遮挡剔除(Occlusion culling)

⭐️使用LOD(多层次细节),好处就是对那些离得远,看不清的物体的细节可以忽略。

⭐️使用mobile版的shader,简单。


②使用MipMaps优化显存带宽

它的原理是当屏幕变小时,自动选择合适尺寸的图片进行渲染;但会多使用33%的内存。


三、从内存方面优化,主要使用对象池技术。有关对象池的实现,可自行百度,有大量、各式各样的实现方法。