目录

Unity优化

  • 对cpu性能优化 主要从drawcall、物理组建、GC、脚本等几个方面

Drawcall

unity分动态批次和静态批次。两种批次都是需要对象的材质是共享的,不同材质的对象无法进行批次。

  • 注在脚本中调用材质时,使用Renderer.material会造成材质的拷贝,而使用Render.sharedMaterial来调用则不会拷贝

静态批次

静态在场景中不可移动(标记static),静态批次会把多个对象合并成一个打对象,会导致内存损耗,有时候也需要避免太多的静态批次造成内存过高。优化就是要平衡各个硬件的压力不能只针对一方面极致

动态批次

  1. 动态批次unity自动运行,也有一些自己的规则需要注意 动态批次是逐点处理的,仅对于小于900个顶点的mesh有效,如果shader使用了顶点位置、法线和UV那么仅支持低于300个顶点的mesh。如果再次基础上使用UV1、UV0和切向量,则最多支持180个顶点的mesh
  2. 缩放对动态批次也有影响,三轴同比例缩放的对象不会进行动态批次。非均匀缩放复制了具有烘培比例的网络-所以从渲染角度来看,它们根本没有被缩放,而是两份mesh(既然已经复制了mesh就顺带进行批次)。而对相同比例缩放来说使用的是同一份mesh此情况复制mesh来进行批次渲染不值得。
  3. 一定要使用同一个材质,即便两个一模一样也不会进行批次
  4. 拥有lightmap的物件因为有额外的材质属性(比如偏移和缩放系数)所以不能进行批次
  5. 多通道的shader会妨碍批处理操作,接收实时阴影的物件无法进行批次处理
  • 渲染的顺序可能打断批次
    • 比如一个材质夹在两个相同材质中间,这个时候两个相同材质的被隔开无法核批
    • 渲染的顺序是根据物件到摄像机的距离进行从远到近的渲染,相同材质的对象尽量在一层。修改shader中渲染队列值,当小于等于2500时,unity会认为其不透明,对于不同材质但Z值相同对象,unity不对其进行排序,这样就算有其他材质插入也不会打破批次。如果队列值高于2500则会按顺序被打乱
  • 知道渲染顺序这个规则,我们可以根据对象到相机的深度或者距离进行排列规则
    • 场景里可以用Z轴来进行空间划分
    • UI里用深度来进行划分
    • 人物模型等场景中的对象相对复杂,空间划分难度较大,只能尽量避免
    • 一个特效使用多个材质,过程中也会引起drawcall波动,也可以进行相应的空间划分。
    • 使用过后的特效等对象,在不起效的时候,应该设置为未激活状态,否则会继续占用drawcall,消耗设备的计算力,所以当一个对象使用后应当执行销毁或者未激活状态
  • 打包图集
    • 每个材质、纹理的渲染一定是会产生drawcall的,这个drawcall只能通过打包图集来进行优化,将多个纹理进行打包图集来减少材质,多个对象共享一个材质(纹理shader)合并的时候也要注意对同时出现的放在一起,不然会加载很多无用资源,造成内存占用升高
  • 制作图集一般遵循几个规划 :
    • 从功能角度进行划分,如公共部分、每个具体的界面、功能上密切相关的打包到一起
    • 不能将所有的东西打包到一个图集里面,特别不可能同时出项的东西,一个你不需要显示的图片会一直占用你的内存。
    • 控制图集大小,不能让图集太大,一个很大的图集的drawcall消耗或许顶上十几个小图集的消耗
  • 经过精心划分的图集与渲染顺序,drawcall一定有质的优化
  • 使用灯光会打断drawcall,尽量使用烘培来实现光照效果

优化GC

  • 尝试再适当的时机,手动触发GC和扩展堆大小,以便GC可控
  • 将局部函数中的局部引用变量写成公共的
  • 使用对象池
  • 减少字符串创建,针对常用的字符串用id替代
  • 注意装箱,装箱会产生垃圾源于底层,当一个值类型变量被装箱时,unity在堆上创建一个临时的object来包装值类型变量。一个object是一个引用类型的变量,所以当这个临时对象被处理时会产生垃圾。
  • Linq和正则在后台有装箱操作而产生垃圾
  • 构建代码最小化GC
  • 代码的构建方式可能会影响GC,即使代码中没有堆分配,也可能会增加GC的负担。可能增加GC的负担之一是要求检查他不改检查的东西,Struct是值类型的变量,但是如果包含一个引用的类型变量的Struct,那么垃圾收集器必须检查整个结构体。
  • 另外一个增加GC负担的操作是使用不必要的对象,当垃圾收集器搜索堆上的对象的引用时,它必须检查代码中的每一个当前对象的引用,更少的对象引用意味着更少的工作量
  • foreach每次迭代会产生垃圾内存

脚本

  1. Getcomponent 有三种获取方式 Getcomponent(string)—->在编写自定义调试台的时候,解析用户输入的字符串来获取组建的时候才适用,对于产品级的应用程序没理由使用 Getcomponent() Getcomponent(typeof(T)) 这两个方法获取的效率相差不大,根据每个版本优化的关系,经测试在untiy5的后续版本中最好使用 Getcomponent()
  2. Update函数调用Getcomponent等接口最好缓存
  3. 移除空的回调定义 如monobehaviour的Start()、Update(),完整的回调列表可通过untiy文档查找。当场景就有成千上万个空定义也会造成可观的性能消耗(可利用正则查找空的并移除)
  4. 在update中处理,如果可以隔多帧处理一次,如将 update(){DoSth();}修改为update() { if(Time.framecount %5 == 0) DoSth();} 脚本主要是针对update中复杂跟耗费的逻辑进行优化
  5. 共享计算输出,避免重复堆一个结果进行多次的计算
  6. 从gameobject取出字符串属性 从gameobject检索字符串属性是另一种意外跨越本机-托管桥接的微妙方式(gameobject的tag和name受到这个影响)应该只在性能无关的地方调用。 对于tag的属性常用比较Gameobject提供了CompareTag()方法(避免本地-托管桥接,不会导致内存分配与对应的垃圾回收),name属性没有对应的办法,应该尽量少使用
  7. 对transform的属性修改,会通过其父对象的transform为对象生成正确的transform(意味着在hierarchy窗口越深的位置计算的越多),在复杂的时间中对同一transform组建属性修改它每次都会触发内部消息进行一系列计算,因此属性修改可以通过缓存值在针尾来更改它来减少计算

Unity官方给出的一些优化建议

  1. PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU。
  2. 如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unlit目录下的。它们更高效。
  3. 尽可能共用材质。
  4. 将不需要移动的物体设为Static,让引擎可以进行其批处理。
  5. 尽可能不用灯光。动态灯光更加不要了。
  6. 尝试用压缩贴图格式,或用16位代替32位。
  7. 如果不需要别用雾效(fog)
  8. 尝试用OcclusionCulling,在房间过道多遮挡物体多的场景非常有用。若不当反而会增加负担。
  9. 用天空盒去“褪去”远处的物体。
  10. shader中用贴图混合的方式去代替多重通道计算。
  11. shader中注意float/half/fixed的使用。
  12. shader中不要用复杂的计算pow,sin,cos,tan,log等。
  13. shader中越少Fragment越好。
  14. 注意是否有多余的动画脚本,模型自动导入到U3D会有动画脚本,大量的话会严重影响消耗CPU计算。
  15. 注意碰撞体的碰撞层,不必要的碰撞检测请舍去。