开发手札:unity性能优化(一)

      unity性能优化一直是一个综合的问题,涵盖了CPU、内存、GPU、资源、UI、3D等方面,实际上我自己都不敢说懂unity优化,只能结合自己这些年开发的经历来谈一下,算是抛砖引玉,如果有人有更好的优化建议和文章,希望留言分享出来。

      就我的经历中,优化分为前期优化和后期优化,不知道是不是因为大环境的原因,我参与的项目开发,几乎全都是立刻开发项目,出第一版,然后迭代更新。后期的时候就会发现很多性能方面问题,这样以后的项目我们就需要在开发前期做一些初步的优化处理,免得后期项目改动太大容易崩盘。

      1.代码写法优化

      不要频繁使用耗时较高的API,比如getComponent尽量少用,使用CacheObject方式添加对组件的引用。

      string类型使用特别注意,因为容易引起的GC,必要时使用stringbuilder。

      节省数值类型精度,能尽量使用低位数整形,能使用16位就不使用32位,能使用int就不使用float。

      foreach使用要注意,因为会产生GC,当然mono对于foreach的问题后面已经修改了,只不过长久的影响用的多的还是for。

      函数栈缓存数据再使用,避免不必要的频繁调用堆内存开辟消耗,比如调用数组链表等获取接口,先栈中缓存再使用。

      update/ongui等逐帧函数不用就删除,用不到的脚本disable掉,至少能省电。

      ui管理模块中uiwindow.gameobject.setactive(true/false)不要用,移出/移入视口区域就行,因为不管是ngui/ugui每次active元素都会导致dc的变化,结果导致顿卡,体验不太好。

     2.内存优化

     内存优化一般涉及两点就是Alloc(heap)和GC,堆内存也就是内存条(虚拟磁盘内存),速度相对于CPU缓存是极慢的,特别是手机上那些个RAM/ROM更慢了,所以申请释放堆内存就变成了一个卡顿的源头。

     c/c++中我们都是自己alloc free和new delete,管理较为方便,但是c#中是由GC处理的,所以无法完美把控,只能根据它的特性优化一些处理方法。

     缓存池ObjectPool的创建,因为instantiate和destroy属于消耗操作,所以尽量不要在用户使用过程中调用,可以在某些loading界面实例化一堆需要用到的gameobject,同时循环利用,创建alloc和recycle接口,随取随用,用完归位。当然缓存池技术也是一个空间换时间的做法,根据实际情况取舍。

     避免类型拆装箱操作,使用泛型T,因为拆装箱会产生额外的内存垃圾,这样触发GC的频率就更高了。

     LINQ也会产生额外的堆内存,虽然我基本没用过。

     我们要更好的管理适当时机的GC,比如游戏打完一局结算完毕后主动GC一次。

     3.GPU优化

     DC优化,优化的重点对象,其实DC应该属于CPU优化范畴,CPU将应用层几何数据等整理成一份一份提交给GPU渲染的过程就叫drawcall,只是最终作用的对象是GPU,所以就归纳为GPU优化了。

     因为从根源上还是CPU的问题,所有优化起来还是有一些特定的门路的。

     首先从ui(ugui/ngui)来说,打图集算是最常规操作,一般我们公用image资源打到一个common图集,独立模块image资源打到xxx_module图集,那么一般情况下一个ui模块也就只用得上两个图集了。

     为什么要打图集的原因是,小的sprite被CPU依次提交会产生大量drawcall,而合并图集后,提交一个GPU能支持的分辨率的大图,然后GPU在显存内部通过instance和uv采样就可以完成所有小图的渲染了。

     Ngui提供手动打包图集的工具AtlasMaker,打包完生成的大图附带uv信息,uiwidget渲染的时候根据uv信息采样就可以了。

     Ugui则提供了一个SpriteAtlas的封装功能,和Ngui的atlas类似,提供sprite预览和代码加载,使用起来很方便。

     ps:unity5的ugui刚出来的时候,我为了将ugui的图集生成,AB包打包,Sprite加载整出来,也花了一些功夫,当时的ugui意在让我们不管图集的概念直接放手用,实际上开发中还要自己再封装一套,而现在的ugui的SpriteAtlas出现则官宣以前那套不完美。

      还要注意ui部件动静分离,写过ui管理器的童鞋大概都知道一个模块使用Uipanel或者canvas作为合并渲染载体,如果一个canvas(uipanel)中有元素发生变化,那么整个canvas(uipanel)会重新batch Mesh提交渲染,而一个ui窗体中,也分为前景、背景、固定部件(image、button等)和动态部件(scrollview,listview等),那么把固定不变的部件放在一个canvas(uipanel),频繁变化的则独立出来。

      同时在ui管理做法中,不要频繁使用uiwindow.gameobject.setactive(true/false)或者uiwindow.transform.scale = vector0或1,因为这个也会导致drawcall的生成,使用移入移出视口区域就行了。

      再来说下3D部分。

      一般情况下,我们的美术同学都会优先进行场景优化处理,如下:

      1.在保证场景质量的前提下,尽量减少网格顶点/三角面的数量,优化纹理分辨率和质量。

      2.复用相同贴图和着色算法的材质球。

      3.永远看不见的“背面”就不会去构建网格了,就构建“正面”的网格就可以。

      4.非特殊情况使用单面渲染着色,除非需要双面显示。

      5.“无限远”处背景使用贴图或者天空盒。

      6.提供LOD多层次模型网格和纹理贴图(mipmap)等技术,但是同样也会出现内存显存增加的问题,主要看实际运行环境的性能平衡。

      7.法线贴图/高度贴图等光照细节拟真技术。

      8.减少场景实时光源,尽量一个平行光,特殊场景多一些其他类型光源。

      9.使用场景烘焙技术预处理光照贴图或者直接上原画的模拟贴图,关闭不必要光源。

      做完这些只是前提,接下来是我们程序需要做的事情,如下:

      1.合并网格,unity提供static和dynamic合并,静止的网格个体,拥有相同材质缩放标记static,引擎会运行时自动合并网格提交,当然动态的网格个体,引擎也会有相关算法进行合并计算,不过限制就比较大了,这两个统称为批提交技术,衡量一个引擎的渲染能力,其纹理/几何数据等提交效率算是绝对的一个参考标准,合并过勤会增加合并时间以及提交缓存buffer,反之则DC过高GPU又过于闲置。

      2.制作假阴影,一般情况场景烘焙或者原画模拟完毕,就只存在一些个动态的monster和player的脚下阴影了,模拟一个挺好。

      3.对于一些永远正对着“眼睛”的物体使用“公告板”技术, 让他的单面永远对着“眼睛”即可,比如二维面片树木等。

      4.使用遮罩剔除技术,直接从根本上避免提交渲染那些看不见的模型。

      5.优化shader,因为从3dmax和maya导出的模型shader就是个diffuse的,最后还是要我们程序实现相关渲染shader,所以了,我们可以优化光照算法,优化顶点函数和片段函数中的相关计算。

      6.粒子等特效尽量少用,可能的话跟主美产品协商使用uv动效以及公告板结合的拟真粒子。

      7.优化物理效果,假如产品物理效果单一,比如做一个桌球游戏,根本不需要physics引擎,只需要程序模拟一个反射衰减计算路线公式就行了。

      8.碰撞器使用矩形/长方体等形式,因为碰撞检测算法在这两种情况下效率最高。

      9.渲染关系优化,渲染关系是整个场景渲染逻辑的一部分,简单来说就是从前向后还是从后向前,最原始的“画家算法“是从后向前渲染,从距离眼睛最远处的物体开始渲染,一直渲染到最近处,性能消耗最高,上一篇我提到的深度缓冲/深度测试技术则解决了这个问题,因为depth的存在,每一个片段都需要经过深度测试才能决定是否被渲染,则剔除掉了实际上完全不需要渲染的遮挡物(这个涉及比较复杂,后面要详细讲解)。

      嗯,暂时到这里,因为想睡觉了,后面继续记录一些优化策略。

      

 

      

       

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值