前言

Unity的项目优化已经是老生常谈,很多人在项目完成之后,即便创意新颖,也会觉得差强人意,原因就在于没有做详细的项目优化。众所周知,Unity是一个综合性的3D开发引擎,其中包含图像渲染,逻辑处理,数据存储,发布测试等等各方面的内容。因此Unity各个方面都存在的待优化的内容,也可以说项目优化是项目开发中必不可少的一项工作。本篇文章会从项目的各个方面分析Unity待优化的内容,并给出优化方案,全面优化你的项目。优化项目无非是减轻系统的功耗负担,故下面从CPU、GPU、内存三方面的优化来讲解。

CPU优化

Draw Calls

Draw Call Batching(批处理)
两个或多个纹理相同或材质相同的网格模型可以批量处理他们的材质,这样就可以将多个模型的材质DrawCall合并为一个,从而达到减少DrawCall的目的。批处理是系统工作范畴,我们只需要选择即可,批处理又分为静态批处理和动态批处理。

Static Batching 静态批处理

场景中有很多游戏对象,其中静态对象(Inspector勾选Static的)可以通过静态批处理来优化DrawCall。

下面我们通过具体实例来验证:
在场景中创建多个游戏对象Cube,Sphere, Capsule, Cylinder,默认为静态。此时运行游戏,DrawCall如下。

Dynamic Batching 动态批处理

上面的静态批处理需要给对象设置成静态,而动态批处理,则不需要,非静态的对象,系统自动做批处理。
同样,我们举例说明,通过预设体创建25个cube。

for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
transform.position = new Vector3(x*3, y*3, 10);
Instantiate(cu, transform.position, transform.rotation);
}
}

  

不过动态批处理的约束挺多的:

  • 批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。
    如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你>- 只能批处理180顶点以下的物体。
  • 不要使用缩放。分别拥有缩放大小(1,1,1) 和(2,2,2)的两个物体将不会进行批处理。
  • 统一缩放的物体不会与非统一缩放的物体进行批处理。
  • 使用缩放尺度(1,1,1) 和 (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1) 和(1,3,1)的两个物体将可以进行批处理。
  • 使用不同材质的实例化物体(instance)将会导致批处理失败。
    拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。
  • 多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道。
  • 预设体的实例会自动地使用相同的网格模型和材质。

打包图集

每个材质/纹理的渲染一定是会产生DrawCall的,这个DrawCall只能通过打包图集来进行优化

图集一般遵循几个规则:

  • 从功能角度进行划分,例如UI可以划分为公共部分,以及每个具体的界面,功能上,显示上密切相关的图片打包到一起

  • 不要一股脑把所有东西打包到一个图集里,特别是那些不可能同时出现的东西,它们就不应该在一个图集里,这样的图集意义不大,减少不了DrawCall,并且一个你不需要显示的图片,会一直占用你的内存,这让我非常不爽

  • 注意控制图集的大小,不要让图集太大,一个超级大图集的DrawCall消耗或许顶的上十几个小图集的消耗

  • 字符图集,在使用BMFont或者其他工具生成图片字的时候,我们往往是直接导入一大串文字,然后直接生成图片,但实际上这上面的操作也有优化空间,例如BMFont生成的图片大小,是可以设置的,有两个规则,一个规则是导出的图片尽量小,另一个是导出的图片尽量少,默认的大小应该是512x512,假设你生成的图片256x256就可以容纳,那么多做一个操作你可以节省这么多空间,另外当你输入多几个字,就导致增加一张图片时,例如1024变成2048,那么你可以考虑使用3张512的图片,这样也会节省空间

  • 经过精心划分的图集在加上精心规划的渲染顺序,DrawCall会有一个质的优化

物理组件

物理组件是我们在游戏开发中经常用到的组件,比起设计超级复杂的伤害计算算法,一个Trigger就能解决很多问题,还有那可以模拟一切物理效果的Rigidbody。但如果物理组件使用过多,计算量过大,也会造成CPU过载。对于物理组件的优化,有以下两点。

  • 设置一个合适的Fixed Timestep。

我们都知道在计算物理逻辑的时候通常会将代码放到FixedUpdate里面,然而FixedUpdate的执行频率,就由Fixed Timestep决定,并不是所有的游戏中物理计算都需要0.02秒执行一次。因此这个值,可以针对项目慢慢调试,设置出一个比较合适的值,这样即完成了物理计算,也可以减轻CPU的负担。所以,想想自己上高中物理的时候做的物理大题,让CPU少做几道,确实可以轻松很多。换位思考也可以这样对吧,换到CPU的角度考虑。

GC(Garbage Collection垃圾回收)

GC是用来处理内存的,为什么会影响到CPU的开销呢?因为GC是CPU调度的。大量的调用GC确实可以回收内存,但如果内存占用量不是很大的情况下,调用GC的性价比就很低,因为GC对CPU的开销所造成的代价更大。所以优化GC,就是减少对GC的调用。

那么GC什么时候会触发呢?两种情况:

首先当然是我们的堆的内存不足时,会自动调用GC。
其次呢,作为编程人员,我们自己也可以手动的调用GC。
所以为了达到优化CPU的目的,我们就不能频繁的触发GC。
而上文也说了GC处理的是托管堆,而不是Unity3D引擎的那些资源,
所以GC的优化说白了也就是代码的优化。

代码质量

以物体的Transform组件为例,我们应该只访问一次,之后就将它的引用保留,而非每次使用都去访问。这里有人做过一个小实验,就是对比通过方法GetComponent()获取Transform组件, 通过MonoBehavor的transform属性去取,以及保留引用之后再去访问所需要的时间:
1.GetComponent = 619ms
2.Monobehaviour = 60ms
3.CachedMB = 8ms
4.Manual Cache = 3ms
所以最好不要频繁使用GetComponent,尤其是在循环中。
善于使用OnBecameVisible()和OnBecameVisible(),来控制物体的update()函数的执行以减少开销。
使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0);
对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值复制到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。

GPU优化

GPU主要处理图像渲染,与CPU不同,侧重点自然也不同。GPU需要优化的点主要有以下几点:

  • 填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。
  • 像素的复杂度,比如动态阴影,光照,复杂的shader等等
  • 几何体的复杂度(顶点数量)
  • GPU的显存带宽

减少绘制的数目

减少顶点数量,简化复杂度,举措如下。

  • 保持材质的数目尽可能少。这使得Unity更容易进行批处理。
  • 使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
  • 如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
  • 使用光照纹理(lightmap)而非实时灯光。
  • 使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
  • 遮挡剔除(Occlusion culling)
  • 使用mobile版的shader,简单。

优化显存带宽

压缩图片,减小显存带宽的压力

  • OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。
  • 使用MipMap。
    Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。

内存优化

  • Unity3D内部的内存
  • Mono的托管内存
  • 若干我们自己引入的DLL或者第三方DLL所需要的内存。

Unity内部内存

Unity3D的内部内存都会存放一些什么呢?

  • 资源:纹理、网格、音频等等
  • GameObject和各种组件。
  • 引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统等等

mono托管内存

因为我们的游戏脚本是用C#写的,同时还要跨平台,所以带着一个Mono的托管环境显然必须的。那么Mono的托管内存自然就不得不放到内存的优化范畴中进行考虑。那么我们所说的Mono托管内存中存放的东西和Unity3D内部内存中存放的东西究竟有何不同呢?其实Mono的内存分配就是很传统的运行时内存的分配了:

  • 值类型:int型啦,float型啦,结构体struct啦,bool啦之类的。它们都存放在堆栈上(注意额,不是堆所以不涉及GC)。
  • 引用类型:其实可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各种控件的封装。其实很好理解,C#中肯定要有对应的类去对应游戏引擎中的控件。那么这部分就是C#中的封装。由于是在堆上分配,所以会涉及到GC。

结束语

项目优化至关重要,因为现在的产品大同小异的很多,游戏也是如此,无非就是那么几种类型,在保持创新精神的基础上,还要着重关注的就是用户体验。用户体验这个词不是新词了,所以怎样提高用户体验呢,流畅、明朗、便捷。当然,这些都取决与项目优化。所以,是你的产品就好好优化它,因为每个产品都可以变得更好。

Unity全面优化的更多相关文章

  1. Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译

    本文是Unity官方教程,性能优化系列的第三篇<Optimizing garbage collection in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  2. Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译

    本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  3. Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译

    本文是Unity官方教程,性能优化系列的第二篇<Diagnosing performance problems using the Profiler window>的简单翻译. 相关文章: ...

  4. Unity性能优化(1)-官方教程The Profiler window翻译

    本文是Unity官方教程,性能优化系列的第一篇<The Profiler window>的简单翻译. 相关文章: Unity性能优化(1)-官方教程The Profiler window翻 ...

  5. Unity内存优化(贴图层面)

    聊聊近况: 距离上一篇文章已经过了好久,主要原因是我懒了.公司项目也到了开始优化的阶段,上网找的资料,看过了就忘.还是想把它整理一下,写出来.其实我说的东西,网上都有,我只是搬运工而已. 贴图压缩: ...

  6. Unity内存优化技术测试案例

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  7. Unity内存优化

    [Unity内存优化] 1.在Update方法或循环中,少用string类,因为string类的每次操作都会调用new生成新字符串对象.用StringBuilder代替string,StringBui ...

  8. Unity 性能优化(力荐)

    开始之前先分享几款性能优化的插件: 1.SimpleLOD : 除了同样拥有Mesh Baker所具有的Mesh合并.Atlas烘焙等功能,它还能提供Mesh的简化,并对动态蒙皮网格进行了很好的支持. ...

  9. Unity动画优化

    Unity动画优化 https://blog.csdn.net/TracyZly/article/details/79991593 Unity中Animator做UI动画的一些细节 https://b ...

随机推荐

  1. 几种不同程序语言的HMM版本

    几种不同程序语言的HMM版本 “纸上得来终觉浅,绝知此事要躬行”,在继续翻译<HMM学习最佳范例>之前,这里先补充几个不同程序语言实现的HMM版本,主要参考了维基百科.读者有兴趣的话可以研 ...

  2. 如何用jQuery封装插件

    引子 现在网上关于js和jquery封装的插件很多,我刚刚接触前端的时候,就很敬佩那些自己写插件的大牛们!因为是他们给网站开发更多的便利,很多网页效果,网上很多现成的插件!那么这些插件是如何写的呢?首 ...

  3. R0—New packages for reading data into R — fast

    小伙伴儿们有福啦,2015年4月10日,Hadley Wickham大牛(开发了著名的ggplots包和plyr包等)和RStudio小组又出新作啦,新作品readr包和readxl包分别用于R读取t ...

  4. 【CODEVS】1034 家园

    [算法]网络流-最大流(dinic) [题解] 飞船有可承载人数限制,地球为源点,月球为汇点,人像水流一样从以飞船上限为容量的边流向汇点. 人在各站点都面临着上船与否的选择,难以用DP解决最优策略,于 ...

  5. 【洛谷 P2147】 [SDOI2008]洞穴勘测(LCT)

    题目链接 LCT裸题.. #include <cstdio> #define R register int #define I inline void #define lc c[x][0] ...

  6. inviteflood 洪泛滥工具

    inviteflood是一种通过UDP/IP执行SIP/SDP INVITE消息泛洪的工具,描述可以参考:inviteflood Package Description 使用inviteflood工具 ...

  7. css_input[checked]复选框去掉默认样式并添加新样式

    效果对比: “\2713”实体符号√ :如有兴趣查看详细实体符号请点这里 代码实现: <!DOCTYPE html> <html> <head> <meta ...

  8. OGG生成数据定义文件的参数NOEXTATTR

    ./defgen paramfile ./dirprm/jzjj.prm NOEXTATTR In OGG 11.2, there is a new parameter NOEXTATTR. This ...

  9. perl6正则 5: [ ] / | / ||

    也就是可以把多种要匹配的写进[ ] 中, 第种用 | 分开就行了. | 与 || 有差别 |的话, 当匹配位置 相同时, 会取最长的, 而 || , 当前面的匹配成功, 后面的就不会再去匹配. / / ...

  10. elk系列6之tcp模块的使用【转】

    preface tcp模块的使用场景如下: 有一台服务器A只需要收集一个日志,那么我们就可以不需要在这服务器上安装logstash,我们通过在其他logstash上启用tcp模块,监听某个端口,然后我 ...