Unity优化需要从哪里着手
先引入了几个概念:
- DrawCall:对底层图形程序接口的调用,以在屏幕上绘制东西,由CPU调用
- Fragment:片源
- Batching:批处理,将多次DrawCall的内容合并,以减少DrawCall
- 内存的分配:Unity内存,Mono内存,dll…
优化时需要注意的3个方面:
- CPU方面
- GPU方面
- 内存方面
CPU方面
常见的影响CPU效率方面的因素:
- DrawCalls
- 物理组件
- GC
- 脚本代码质量
对DrawCall的优化
每一次调用DrawCall之前CPU都需要做很多准备工作,如果DrawCall过多,内存必定不堪重负。
对DrawCall的优化主要思路就是尽量减少渲染次数,多个物体最好一起渲染。按照这个思路有了以下3个解决方案:
- 使用DrawCallBatching批处理,在运行时可以将一些物体进行合并成一个DrawCall。
- 通过把纹理打包成图集尽量减少材质的使用。
- 尽量少的使用反光、阴影之类的效果,会使物体多次渲染。
DrawCallBatching批处理
- 首先理解为什么两个没有使用相同材质的物体无法合批?
因为被“批处理”的两个物体的网格模型需要使用相同材质的目的,在于其纹理是相同的,这样才可以实现同时渲染的目的。因此保证材质相同,是为了保证被渲染的纹理相同。
因此为了使两个纹理不同的材质合而为一,就需要将纹理打包成图集。这样就可以只用一个材质代替之前的两个材质。
- DrawCallBatching分为Static Batching静态批处理和Dynamic Batching动态批处理
Static Batching静态批处理
只要物体不移动,并且拥有相同的材质,静态批处理就运行引擎对任意大小的几何物体进行批处理操作来降低Drawcall
如何使用静态批处理减少DrawCall,只需要在Inspector面板中勾选“static”复选框,指明那些物体是静止的,在游戏中永远不会移动、旋转、缩放。
Dynamic Batching动态批处理
Unity中动态批处理是自动进行的,无须像静态批处理那样手动勾选“static”。
动态批处理的约束:
对物理组件的优化
- 设置合适的Fixed TimeStep
- 尽量不要使用mesh colider
GC优化
GC在释放内存的同时,增加CPU的开销,因此对于GC优化的目标就是尽量少的触发GC。
GC是Mono运行时的机制,而非Unity引擎的机制,所以GC主要是针对Mono对象来说的,而它管理的也是Mono的托管堆。
所以GC不是用来处理引擎的Assets(纹理、音效)的内存释放的,因为Unity引擎有自己的内存堆,而不是和Mono一起使用所谓的托管堆。
什么东西会被分配到托管堆上:
引用类型:类的实例、字符串、数组。
GC什么时候会被触发:
- 堆内存不足时,自动调用GC
- 程序手动调用GC
因为GC处理的是托管堆,而不是Unity引擎的资源内存。所以GC的优化其实质是代码的优化:
- 字符串的连接处理
- 尽量不要使用foreach语句,而是使用for语句。(foreach语句其实是会涉及迭代器的使用,据说每一次循环所产生的迭代器)
- 不要直接访问gameObject的tag属性。比如“
if(go.tag == "XXX")
// 最好换成
if(go.CompareTag("XXX"))
因为tag属性会在堆上额外的分配分配空间(如果在循环中,则会产生大量垃圾)。
- 使用“池”。
- 最好不要使用LINQ的命令,因为它们会分配额外的空间,同样也是GC收集的目标。(补充:LINQ在某些情况下无法进行很好地进行AOT编译,比如“OrderBy”会生成内部的泛型类“OrderEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。如果使用了OrderBy,那么在IOS平台上也许会报错)。
对代码质量的优化
- 不要频繁使用GetComponent,尤其在循环中。
- 善用OnBecameVisible()来控制物体的update()函数执行,以减少开销。
- 使用内建的数组,比如使用Vector3.zero而不是new Vestor(0,0,0)。
- 对于方法参数的优化,善用ref关键字。
对GPU的优化
GPC的瓶颈主要存在一下四个方面
- 填充率,可以简单的理解为图形处理单元每秒的像素数量
- 像素的复杂度,比如动态阴影、光照、复杂的shader等
- 几何体的复杂度(顶点数量)
- GPU的显存带宽
举措: - 减少顶点数量,简化计算复杂度
- 压缩图片,以适应显存带宽
减少绘制的数目(减少顶点数量)
- 保持材质数目尽可能少。这使得Unity更容易进行批处理
- 使用纹理图集
- 如果使用的纹理图集和共享材质,使用Render.sharedMaterial来代替Render.material。
- 使用光照纹理(lightmap)而非实时灯光
- 使用LOD
- 遮挡剔除
- 使用mobile版的sharder,因为简单。
优化显存带宽(压缩图片)
- OpenGL ES 2.0 使用ETC1格式压缩,在打包设置里有
- 使用MipMap
内存的优化
Unity引擎大致分为3类内存
- Unity内部内存
- Mono托管内存
- 自己引入的DLL或者第三方DLL所需的内存
Unity的内部内存
- 资源:纹理、网格、音频等。
- GameObject和各种组件
- 引擎内部逻辑需要的内存:渲染器、物理系统、例子系统等
Mono的托管内存
Mono托管内存中存放的东西和Unity3D的内部内存中存放的东西究竟有何不同?其实Mono的内存分配就是很传统的运行时内存分配。
- 值类型:int、float、结构体struct、bool之类的。它们都放在栈上(注意不是堆,所以不涉及GC)。
- 引用类型:可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各个控件的封装。
** using语句块
** IDispose接口
…