一些优化常识
Atlas:
每个材质和纹理的渲染都会产生DrawCall,把所有密切相关图片做成一张大图,从而减少DrawCall。但是要注意图集粒度,选择什么图片和多少图片取合成一张图集,对内存效率有很大影响等问题,如果是不可能同时出现的东西放在同一个图集,会增大内存占用。
LOD及其优缺点:
LOD为Levels of Detail的简称,简单来说即为多细节层次。
LOD技术指根据物体模型的节点在显示环境中所处的位置和重要度,决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。
在Unity中,LOD是最常用的游戏优化技术,根据物体在游戏画面中所占视图的百分比来调用不同复杂度的模型。简单而言,就是当一个物体距离摄像机比较远的时候使用低模,反之使用高模。
优点是优化游戏渲染效率,解决运行时流畅问题,缺点是会占用大量内存,是一种空间换时间的方式。
MipMap及其优缺点:
Mipmap技术有点类似于LOD技术,但是不同的是,LOD针对的是模型资源,而Mipmap针对的纹理贴图资源。使用Mipmap后,贴图会根据摄像机距离的远近,选择使用不同精度的贴图。
优点是优化显存带宽,用来减少渲染,根据实际情况选择适合的贴图来渲染,距离摄像机越远,显示的贴图像素越低,缺点是占用内存,因为mipmap会根据摄像机远近不同而生成对应的八个贴图。
为什么dynamic font在unicode环境下优于static font:
Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。
使用动态字体时,Unity将不会预先生成一个与所有字体的字符纹理。当需要支持亚洲语言或者较大的字体的时候,若使用静态纹理,则字体的纹理将非常大。
NGUI优化:
- 根据各个UI控件的设计安放Panel,隔开DrawCall
项目中发现NGUI的UIPanel.LateUpdate函数的CPU开销非常大,是合并了太多的DrawCall所致,尤其是将运行时会运动变化的UI控件和静止不变的UI控件的DrawCall合在了一起。
当一个UI控件(UIWidget)的位置、大小或颜色等属性发生变化时,UIPanel就需要重建这个控件所用的DrawCall,某些情况下还要重建Panel上的所有DrawCall。
这需要重新计算这个DrawCall上所有控件的顶点信息,包括顶点位置、UV和颜色等,因此将UI控件分组,将一段时间内才会发生变化的控件分离,两类控件就被隔开到不同的DrawCall不同的Panel中,DrawCall重建的时候,就不需要遍历那些没有变化的控件。比如怪物头顶的血条和伤害跳字放在同一个Panel上,其余不变化的控件就放在别的Panel上。 - 优化锚点内部逻辑,使其只在必要时更新
在上一点优化了Panel的DrawCall重建效率之后,我们发现NGUI锚点自身的更新逻辑也会消耗不少CPU开销。
即使是在控件静止不动的情况下,控件的锚点也会每帧更新(见UIWidget.OnUpdate函数),而且它的更新是递归式的,使CPU占用率更高。
因此我们修改了NGUI的内部代码,使锚点只在必要时更新。一般只在控件初始化和屏幕大小发生变化时更新即可。
不过这个优化的代价是控件的顶点位置发生变化的时候(比如控件在运动,或控件大小改变等),上层逻辑需要自己负责更新锚点。 - 降低贴图素材分辨率
显著降低美术品质的,画面视角效果会变得更模糊,因此一般不到程序撑不住的时候不会采用。
UI血条优化方案
- 如果是用UGUI开发的,当头顶文字数量较多时,确实很容易引起性能问题,可以考虑从以下几点入手进行优化;
- 尽可能避免使用UI/Effect,特别是Outline,会使得文本的Mesh增加4倍,导致UI重建开销明显增大;
- 拆分Canvas,将屏幕中所有的头顶文字进行分组,放在不同的Canvas下,一方面可以降低更新的频率(如果分组中没有文字移动,该组就不会重建),另一方面可以减小重建时涉及到的Mesh大小(重建是以Canvas为单位进行的);
- 降低移动中的文字的更新频率,可以考虑在文字移动的距离超过一个阈值时才真正进行位移,从而可以从概率上降低Canvas更新的频率。
Unity的内存优化
- 压缩自带类库
- 将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉
- 释放AssetBundle占用的资源
- 降低模型的片面数,降低模型的骨骼数量,降低贴图的大小
- 使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)
- 代码中少产生临时变量
Unity在程序上优化资源的方法
- 务必删除脚本中为空或不需要的默认方法
- 只在一个脚本中使用OnGUI方法
- 避免在OnGUI中对变量、方法进行更新、赋值,输出变量建议在Update内
- 同一脚本中频繁使用的变量建议声明其为全局变量,脚本之间频繁调用的变量或方法建议声明为全局静态变量或方法
- 不要去频繁获取组件,将其声明为全局变量
- 数组、集合类元素优先使用Array,其次是List
- 在不使用时脚本禁用之,需要时再启用
- 可以使用Ray来代替OnMouseXXX类方法
- 需要隐藏/显示或实例化来回切换的对象,尽量不要使用SetActiveRecursively或active,而使用将对象远远移出相机范围和移回原位的做法
- 尽量少用模运算和除法运算,比如a/5f,一定要写成a*0.2f
- 对于不经常调用或更改的变量或方法建议使用Coroutines & Yield
- 尽量直接声明脚本变量,而不使用GetComponent来获取脚本
- 尽量使用整数数字,因为iPhone的浮点数计算能力很差
- 不要使用原生的GUI方法
- 不要实例化(Instantiate)对象,事先建好对象池,并使用Translate生成对象
Unity在移动端优化资源的方法
- 使用assetbundle,实现资源分离和共享,将内存控制到200m之内,同时也可以实现资源的在线更新
- 顶点数的渲染主要依赖CPU和GPU,降低顶点数到8万以下,fps稳定到30帧左右
- 只使用一盏动态光,不使用阴影,不使用光照探头
- 剪裁粒子系统
- 合并同时出现的粒子系统
- 自己实现轻量级的粒子系统
- 把不需要跟骨骼动画和动作过渡的地方全部使用animation,控制骨骼数量在30根以下
- animator出视野不更新
- 删除无意义的animator
- animator的初始化很耗时(粒子上尽量不用animator)
- 除主角外都不要跟骨骼运动apply root motion
- 绝对禁止掉那些不带刚体带包围盒的物体(static collider)运动,NUGI的代码效率很差,基本上runtime的时候对cpu的贡献和render不相上下
- 每帧递归的计算finalalpha改为只有初始化和变动时计算
- 去掉法线计算
- 不要每帧计算viewsize和windowsize
- filldrawcall时构建顶点缓存使用array.copy
- 代码剪裁:使用strip level ,使用.net2.0 subset
- 尽量减少smooth group
- 给美术定一个严格的经过科学验证的美术标准,并在U3D里面配以相应的检查工具
降低Unity在手机端的占用
- 资源角度:资源分配合理,了解Unity的内存机制(资源从加载到释放的内存分配及释放方式),不用的资源及时释放掉。
- 贴图优化角度:比如Android采用ETC格式,然后将贴图的透明通道进行分离,关掉Mipmap。
- 代码角度:尽可能的避免使用Unity自带GUI,避免GUI.Repaint过度的GC Allow。尽可能少的使用foreach,因为每次foreach都会产生一个Enumerator(约16B的内存分配),尽量改为for。
- lambda表达式使用不当会造成内存泄露。尽量少使用linQ语句。
- 控制StartCoroutine的次数,开启一个协程,至少分配37B的内存,coroutine实例21B,Enumertor16B。
- 使用stringBuilder代替string。对一些组件进行缓存比如transform,gameObject等。