前言

本文仅作为性能优化的导读,依靠本文可以了解到一些Unity性能优化的方法,以及学习性能优化需要了解的知识,以此来入门如何性能优化。本文同时提供了很多参考阅读供读者阅读。

关于AOI,可参考我的另外一篇文章

GPU CPU的分工

GPU比较适合大量可并行的简单任务。比如场景渲染,光照处理等等。

你看到的图像都是显卡渲染出来的。同时现在的GPU也支持一些其他的运算,比如通过GLSL,HLSL和Cg支持并行运算等等。

GPU同时也对游戏中的一些物理效果提供支持,比如PhysX。

一般CPU用于一些数值运算,比如伤害,随机数等等。同时你的敌人AI也是CPU运算出来的。

https://www.zhihu.com/question/21475727/answer/18348647

Statistics统计面板

官方文档:https://docs.unity3d.com/Manual/RenderingStatistics.html

【U3d】渲染统计窗口详细介绍:https://blog.csdn.net/wdmzjzlym/article/details/51335915

  • FPS:
    • Frames Per Second,即一秒内渲染多少帧
    • 1s=1000ms
    • ms=1000/目标帧率,1s30帧,即1ms=33.3帧
    • 手游一般要求30帧以上
  • DrawCall:CPU每次调用图形API命令GPU进行渲染的操作,在Unity中它可以多个Draw Call合并成一个Batch去渲染
  • Batch:把需要渲染的数据加载到显存并设置好渲染状态,然后CPU调用GPU渲染的过程被称为一次Batch(批次)
  • Setpass Call:每次GPU切换一个Pass前,都会产生一次SetPassCall,主要出现在材质不一致的时候,进行渲染状态切换
  • 如何批处理以降低批次(Batching)
    • 动态合批(Dynamic Batching)
      • 将物体动态组装成一个个稍大的vbo+ibo提交。
      • 这个过程不要求使用同样的mesh,但是也一样要求同样的材质。
      • 但是,由于每一帧CPU都要将每个物体的顶点从模型坐标空间变换到组装后的模型的坐标空间,这样做会带来一定的计算压力。所以对于Unity引擎,一个批次的动态物体顶点数是有限制的。
    • 静态合批(Static Batching)
      • 将静态物体集合成一个大号vbo提交,但是只对要渲染的物体提交其IBO。
      • 这么做不是没有代价。比如说,四个物体要静态批次合并前三个物体每个顶点只需要位置,第一套uv坐标信息,法线信息,而第四个物体除了以上信息,还多出来切线信息,则这个VBO会在每个顶点都包括所有的四套信息,毫无疑问组合这个VBO是要对CPU和显存有额外开销的。
      • 要求每一次Static Batching使用同样的material,但是对mesh不要求相同。
    • GPU实例化(GPU Instancing)
      • 只提交一个物体的mesh,但是将多个使用同种mesh和material的物体的差异化信息(包括位置,缩放,旋转,shader上面的参数等。shader参数不包括纹理)组合成一个PIA提交。
      • 在GPU侧,通过读取每个物体的PIA数据,对同一个mesh进行各种变换后绘制。
      • 这种方式相比static和dynamic节约显存,又相比dynamic节约CPU开销。但是相比这两种批次合并方案,会略微给GPU带来一定的计算压力。
      • 但这种压力通常可以忽略不计。限制是必须相同材质相同物体,但是不同物体的材质上的参数可以不同。
    • 所以Unity默认策略是优先static,其次gpu instancing,最后dynamic。当然如果顶点数过于巨大(比如渲染它几千颗使用同种mesh的树),那么gpu instancing或许比static batching是一个更加合适的方案。
  • Batch, Draw Call, Setpass Call:https://zhuanlan.zhihu.com/p/76562300

DrawCall

渲染对象关系:一个可以有多个MeshRender共用一个Material,多个Material公用一个Shader,只要是一个Material,理论上就可以放在一个DrawCall中

走近DrawCall:https://zhuanlan.zhihu.com/p/26386905

Unity中DrawCall和openGL、光栅化等有何内在联系,为什么说DC降低有助于渲染性能优化?:https://www.zhihu.com/question/36357893

Profiler性能分析器

Window->Profiler或者ctrl+7进入

可以定位每一帧的性能情况,包括CPU、GPU、内存等的消耗情况

官方文档:https://docs.unity3d.com/Manual/profiler-module-editor.html

开启Deep Profiler模式,可以深度采样所有函数的执行消耗,如果不开启就只会采样最上层的部分,但是开启Deep模式自身也会消耗一定性能,可以对应着关闭Deep模式做对比,不能完全相信Deep模式的结果。

注意,避免为了性能分析增加了过多的临时代码,造成额外性能的消耗。

【Unity】性能调优: 如何用 Unity Profiler 做项目的性能分析#性能优化

性能分析方法

干扰:

  • 内部干扰:
    • Profiler(性能分析器本身)
    • Vertical Sync(垂直同步)
    • Log output(日志输出,是一种IO操作,性能要求高)
  • 外部干扰:
    • CPU
    • 内存
    • IO

工具:

  • Unity Profiler(定位每一帧的性能情况)
  • Custom Profiler(自己写脚本,来测试出性能数据,在Profiler中看,这样的性能消耗比Profiler会低很多)
  • Timer & Log(打包以后因为无法使用Profiler了,只能使用这种方法来做了)
  • Frame Debugger(可以定位每一帧GPU都做了哪些事情)
  • GPU Profiler(或者各个厂商的GPU检测工具)
  • Memory Profiler(PackageManager中下载)

CPU优化

优化方向

Unity性能优化 - CPU:https://blog.csdn.net/HelloCSDN666/article/details/124694266

  • 批处理(Batching)
  • 合并图集
  • 动静分离(防止面片的重复渲染,其实是GPU优化,当然也减少了dc)
  • 一些Unity脚本的最佳做法
    • 组件的缓冲和获取(一些可以卸载Awake/Start的对象赋值,不必卸载Update里面每帧调用)
    • 移除空的mono方法申明
    • 尽量避免Find和SendMessage(大概比直接调用慢2000倍)使用以下方法替代:
      • 直接引用有序对象
      • 静态类
      • 单例组件
      • 自定义消息系统
    • 禁用未使用的游戏对象和脚本
      • 生存周期结束的脚本/游戏物品
      • 场景中不可见的物体/脚本
      • 距离太远的物体
    • 对象池(后面详细说明了)
  • 选择正确的数据结构:
    • Array、List用于遍历
    • Dictionary用于查询
    • 合适的使用Stack、Queue
  • 算法优化,使用合适的算法需要考虑:
    • 时间复杂度
    • 密集计算分布式
    • 缓存(将一些不会频繁变动的对象缓存起来,不用每一帧都计算一遍)
  • 使用帧动画代替骨骼动画(骨骼动画是用CPU处理的,用内存换CPU时间)https://www.bilibili.com/video/BV1E54y177Tc
  • GPU Skinning(将计算量转到GPU中,用于CPU瓶颈时)https://www.bilibili.com/video/BV1E54y177Tc

动态合批

介绍:

  • 动态合批是静态合批在运行时的体现,动态合批是专门为优化场景中共享同一材质的动态GameObject的渲染设计的,目标是以最小的代价合并小型网格模型,减少Drawcall。
  • 动态合批在进行场景绘制之前将所有的共享同一材质的模型的顶点信息变换到世界空间中,然后通过一次Draw call绘制多个模型,达到合批的目的。
  • 模型顶点变换的操作是由CPU完成的,所以这会带来一些CPU的性能消耗。
  • 动态合批是Unity自己默认去执行的,你没有办法去控制动态合批,只能去满足合批的要求。
  • 动态批处理默认是关闭的,需要手动开启:Project Setting—Player—勾上Dynamic Batching

合批规则:

  • 每个Unity版本的规则可能不一样,所以最好看看使用版本的官方文档。
  • 材质相同是合批的前提,但是如果是材质实例则一样无法合批。
  • 支持不同网格的合批。
  • 单个网格最多300个顶点,900个顶点属性(顶点的位置、法线、uv、切线等),顶点属性的上限可能未来unity版本会调整。
    • 即如果Shader中用到了网格的Position、normal和UV的话,则最多是300个顶点。
    • 即如果Shader中用到了网格的Position、normal、Uv0、Uv1和tangent的话,则最多是180个顶点。
  • 镜像的transform无法合批(scale中有负值,老版本只有缩放完全相同才能合批)
  • 所有实例必须采用UniformScale或者NonUniformScale,不能混用
  • Lightmap对象无法合批
  • 必须引用相同的Lightmap
  • 材质Shader不能使用Multiple passes
  • Mesh实例不能接受实时阴影
  • 隐藏规则:(后续可能就改掉了)
    • 每个Batch最大300个Mesh
    • 最多32000个Mesh可以Batch

静态合批

介绍:

  • 静态合批是勾选Static,Unity在Build的时候,会自动下生成合并的网格,并将它以文件形式存储合并后的数据,这样在当场景被加载时,一次性提交整个合并模型的顶点数据,根据引擎的场景管理系统判断各个子模型的可见性,然后设置一次渲染状态,调用多次Draw call分别绘制每一个子模型。
  • 静态合批的要求比动态合批低,只需要在一个材质球,且在运行时不能移动,旋转或缩放即可。
  • 当顶点数超过32k后会生成另一个合批。
  • 缺点:包体大小和内存的增加
  • 注意:静态合批并不减少Draw call的数量(但是在编辑器时由于计算方法区别Draw call数量是会显示减少了的),但是由于我们预先把所有的子模型的顶点变换到了世界空间下,并且这些子模型共享材质,所以在多次Draw call调用之间并没有渲染状态的切换,渲染API会缓存绘制命令,起到了渲染优化的目的。另外,在运行时所有的顶点位置处理不再需要进行计算,节约了计算资源。

Unity自动实现:

在Unity中选中相同材质的物体,设置为static,将layer设置为static batching即可

Mesh合并的代码实现测试:

  • 将需要合并的物体统一放到一个空物体下
  • 为空物体添加MeshRender与MeshFitter
  • 编写脚本合并子物体的Mesh:
void Start () 
{
    MeshCombine();
}
void MeshCombine()
{
    //获取子物体的MeshFilter
    MeshFilter[] filters = GetComponentsInChildren<MeshFilter>();
    //创建用于组合Mesh的unityapi,是一个结构体
    CombineInstance[] combiners = new CombineInstance[filters.Length];
    //对每一个子物体进行操作
    for(int i = 0; i < filters.Length; i++)
    {
        //将mesh给到combiners
        //(mesh和sharedMesh的区别)https://blog.csdn.net/wodownload2/article/details/53693268
        combiners[i].mesh = filters[i].sharedMesh;
        //获取mesh坐在的Matrix4x4矩阵
        //(Unity中的Matrix4x4)https://zhuanlan.zhihu.com/p/146918827s
        combiners[i].transform = filters[i].transform.localToWorldMatrix;
    }
    //创建一个表示合并后的mesh对象
    Mesh finalMesh = new Mesh();
    //合并网格
    finalMesh.CombineMeshes(combiners);
    //将合并后的网格给到父物体的MeshFilter中
    GetComponent<MeshFilter>().sharedMesh = finalMesh;
}
  • 合并成功后的网格赋予空物体,之后该空物体即是合并后的统一物体(可以直接隐藏合并网格的子物体)

Unity Mesh网格合并:https://blog.csdn.net/qq_42139931/article/details/121232276

官方文档(CombineInstance):https://docs.unity3d.com/cn/current/ScriptReference/CombineInstance.html

官方文档(Mesh.CombineMeshes)https://docs.unity3d.com/cn/current/ScriptReference/Mesh.CombineMeshes.html

GPU实例化

介绍:

  • GPU实例化可以对网格一致的对象但具有不同的材质属性(比如颜色)的对象进行合批并产生可观的优化。
  • GPU Instancing 没有动态合批那样对网格数量的限制,也没有静态网格那样需要这么大的内存,它很好的弥补了这两者的缺陷,但也有存在着一些限制
  • 与动态和静态合批不同的是,GPU Instancing 并不通过对网格的合并操作来减少Drawcall,GPU Instancing 的处理过程是只提交一个模型网格让GPU绘制很多个地方,这些不同地方绘制的网格可以对缩放大小,旋转角度和坐标有不一样的操作,材质球虽然相同但材质球属性可以各自有各自的区别。

规则:

  • 要保证GPU实例化的网格一致
  • opengles3.0以上才支持,但不是所有opengles3.0都是支持的
  • 用于对多个对象(网格一样,材质一样,但是材质属性不一样,材质属性通过shader修改)进行合批,单个合批最大上限为511个对象
  • 在对应材质的AdvancedOptions的Enable GPU Instancing勾选

具体的shader编写可以参考下面的链接:

GPUInstance的使用:https://blog.csdn.net/gzg_restart/article/details/120687835

GPU Instancing教学,Unity降低DrawCall不再是梦想:https://www.bilibili.com/video/BV1UV411B79X

合并图集

介绍:

  • 通过将碎图打包成图集,可以降低内存;
  • 打成图集相同材质,可以通过静态合批降低drawcall

Unity 将Sprite打包进图集:https://blog.csdn.net/xinzhilinger/article/details/116043662

Unity3D打包图集与动态加载图集的两种方法:https://blog.csdn.net/qq_34406755/article/details/103699215

UGUI性能优化——图集:https://zhuanlan.zhihu.com/p/391885850

Unity3D图集讲解:https://www.bilibili.com/video/BV1wo4y1D7tW

大型游戏中UI优化的几个原则:https://zhuanlan.zhihu.com/p/198999167

合理划分合集:

  • 一个Common图集
  • 一个道具图集
  • 一个头像图集
  • 若干功能图集(登录、背包、技能、角色、商店等)
  • 散图(loading图之类的)

穿插问题:

  • 图集尽量减少穿插,不然会打断合批
  • 字体和UI尽量不要穿插,因为材质不一样会打断合批

动静分离

  • 动静分离就是说同一个界面下的UI,可活动的元素(血条,蓝条,伤害数字,CD)放在一个Canvas下,不可活动的元素(人物头像,商城,和平,抽奖等按钮)放在另一个Canvas下。
  • 虽然两个Canvas打断了合批,但是却减少了网格的重建时间,总体上是有优化的。
  • 为什么要避免网格重建那? 如果重建的网格过大,就会很卡。比多一个drawcall还卡。
  • 假如同一个Canvas里面有100个Image,Canvas将会把100个单独的Mesh合并成一个大的ShareMesh,用于渲染。
  • 如果刚好这100个Image都是使用了相同的图片或者是同一个图集里面的图片,那么由于使用的Mesh只有一个(ShareMesh),假如UI元素非常多,ShareMesh可能有2M大小(这是一个比较极端的例子,实际上一般的ShareMesh都是几百K,出现这么大的ShareMesh本身就需要注意了),材质球都是同一个(内置的默认材质球),贴图也是同一张,所以得到的结果就是,DrawCall只有一个。
  • 如果这个100个Image是不会动的,合并ShareMesh没有太大的问题,但是如果是上面的血条,蓝条,名字板,CD,文字聊天信息这些需要动的UI元素和其他UI元素合并同一个ShareMesh的,那么问题就来了,血条由长变短,顶点发生了改变,导致2M的整体ShareMesh也需要重新的生成顶点信息,并且整个ShareMesh重新渲染,这明显是得不偿失的。
  • 当然动态的UI还包括文字部分,其中Text是UGUI产生顶点数量的重灾区,一个字符产生4个顶点,如果再加上Shadow则相当于又把Text复制了一遍产生8个,Outline则会将Text复制4遍产生20个顶点。
  • 所以Shadow、Outline不但会产生额外的OverDraw外还会产生过多的顶点数,一定慎用,确实需要请选择用图片背景替代,其次选择相对较省的Shadow。

对象池ObjectPool

介绍:

对于一些需要不停实例化和销毁的物体,例如发射的子弹对于CPU的消耗十分的大,因此可以使用对象池技术来储存子弹资源,提前实例化足够的物体,要使用的时候直接从对象池中调用,不需要用时直接隐藏即可,减少了频繁的销毁和初始化。

实现步骤:

  • 生成一定数量的子弹在对象池中,使用list存储,并初始化为隐藏状态。
  • 开枪时从对象池中获取一个子弹对象,并将其设置为激活,位置初始化在枪口位置,使用协程3秒后自动隐藏对象。

代码演示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour 
{
    public GameObject bulletPrefab;
    private BulletPool bulletPool;
    void Start () 
    {
        bulletPool = GetComponent<BulletPool>();
    }
  
    void Update () {
        //模拟开枪
        if (Input.GetMouseButtonDown(0))
        {
            GameObject go = bulletPool.GetBullet();
            go.transform.position = transform.position;
            go.GetComponent<Rigidbody>().velocity = transform.forward * 50;
            StartCoroutine(DestroyBullet(go));
        }
    }
    //删除子弹的协程
    IEnumerator DestroyBullet(GameObject go)
    {
        yield return new WaitForSeconds(3);
        go.SetActive(false);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletPool : MonoBehaviour 
{
    public int poolCount = 30;
    public GameObject bulletPrefab;
    private List<GameObject> bulletList = new List<GameObject>();
    private void Start()
    {
        InitPool();
    }
    //初始化对象池
    void InitPool()
    {
        for(int i = 0; i < poolCount; i++)
        {
            GameObject go = GameObject.Instantiate(bulletPrefab);
            bulletList.Add(go);
            go.SetActive(false);
            go.transform.parent = this.transform;
        }
    }
    
    //开枪时调用,激活子弹
    public GameObject GetBullet()
    {
        foreach(GameObject go in bulletList)
        {
            if (go.activeInHierarchy == false)
            {
                go.SetActive(true);
                return go;
            }
        }
        return null;
    }
}

https://blog.csdn.net/FiveRicer/article/details/120770445

https://www.taikr.com/article/3930

移动设备优化要点

  • 尽量少的Drawcall
  • 尽量少的材质数量
  • 尽量小的纹理尺寸
  • 方形与POT纹理(压缩为PVR(IOS)、ETC(安卓))
  • Shader中使用尽量低的数据类型
  • 避免Alpha测试,性能消耗高

资源优化

资源类型

  • 模型
  • 动作
  • 纹理
  • 声音
  • 字体
  • 场景
    • 地形
    • 光影
  • UI
    • 图集
  • 粒子系统

资源制作规范和确定

注意:下面的标准仅供参考,主要还是美术资源需要有一个标准,每个项目的情况不同也会有不同的优化标准。

Mesh:

动态模型:面片数<3000,材质数<3,骨骼数<50,时长,帧率

静态模型:顶点数<500,UV,LOD

Audio:

长时间音乐(背景音乐): (压缩格式,不经常播放) mp3    压缩方式:Compressed In Memory

短时间音乐(音效): (非压缩格式,经常播放) wav         压缩方式:Decompressed on load

Texture:

贴图长宽<1024

格式要求

Shader:

尽量减少复杂数学运算,减少discard操作。

discard

美术风格:

写实? 像素? 卡通?

视角:

2D/3D/2.5D

固定/自由

场景/地图:

无缝大地图?超大规模?

玩法:

单人/多人

休闲/棋牌/动作/冒险

更多:

unity优化—资源优化

Unity性能优化 - 内存

制作优化

模型导入优化:

  1. 模型压缩(在保证可接受的质量的前提尽量选择压缩率高的)
  2. 网格优化(一般可以开启,在网格层面对GPU进行优化)
  3. 可读写(不需要操作模型的时候不用勾选)
  4. LightMap UV(自动为模型生成光照贴图 UV,不需要时不勾选)
  5. 动作导入(模型没动作的时候不用勾选)

动作导入优化:

  1. 动画压缩(减少关键帧进行压缩)
  2. Rig->优化游戏对象(动作的骨骼节点数减少为只有一个子节点)

纹理导入优化:

  1. 导入Unity支持的格式(BMP,TGA,GIF,JPG,PNG等)否则不支持二次处理
  2. 纹理格式(在保证可接受的质量的前提尽量选择压缩率高的)
  3. 制作成符合POT要求的形式(2的幂),否则就是NPOT格式,资源占用大
  4. 可读写
  5. Mipmap(2D的UI不需要开启)
  6. 纹理大小
  7. 压缩选项

UI制作优化:

  1. 能用九宫格就用九宫格
  2. 使用图集

多平台优化:

针对不同平台选定不同的设置比如资源的压缩格式

  1. Standalone
  2. IOS
  3. Android
  4. ......

减少冗余资源与重复资源:

  1. Resources目录下的资源不管是否被引用,都会被打包进安装包,不使用的资源不要放在Resources目录下。
  2. 不同目录下的相同资源文件,如果都被引用,那么也都会被打包进资源包,造成用余应该保证同一个资源文件在项目中只放在一个目录位置。

优化流程的自动化: 快速有效的进行资源监测和优化

继承AssetPostprocessor来制作自动化优化脚本

Unity:资源优化和内存优化

资源监测与分析

  1. 导入资源时进行检查,降低项目冗余度
  2. 使用前面前言中提到的Profiler等性能分析工具和方法
  3. 第三方检测,比如UWA是一家做性能优化的公司,其里面的工具可以对游戏安装包大小和性能做优化。

https://www.uwa4d.com/

https://blog.csdn.net/uwa4d/article/details/73466142

GPU优化

优化方向

  • 减少绘制的数目
    • 保持材质的数目尽可能少。这使得Unity更容易进行批处理。
    • 使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图,它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
    • 如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
    • 使用光照纹理(lightmap)而非实时灯光。
    • 使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。
    • 遮挡剔除(Occlusion culling)
    • 使用mobile版的shader,因为简单。
    • 动静分离(防止面片的重复渲染,其实是GPU优化,当然也减少了dc)
  • 优化显存带宽
    • OpenGL ES 2.0使用ETC1、PVRTC格式压缩等等,在打包设置那里都有。
    • 使用mipmap

Unity优化之GPU优化:https://zhuanlan.zhihu.com/p/47056964

LOD 技术

LOD技术即Levels of Detail的简称,意为多细节层次。LOD技术指根据物体模型的节点在显示环境中所处的位置和重要度,决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。

在需要加入LOD的位置创建空物体并增加LODGroup组件,在对应的LODn中的Renderers中加入不同精细度的模型即可实现LOD分层,可通过拖拉鼠标调节每个LOD层级的占比。

https://docs.unity3d.com/Manual/LevelOfDetail.html

遮挡剔除Occlusion Culling

介绍:

只渲染视锥体内的物体,视锥体外或者是椎体后的不渲染。

视锥体剔除能减少顶点和面的个数,从而降低GPU的负担,和CPU的工作量,提高性能。

请注意,如果使用了 LOD 组,则 Unity 会使用静态遮挡物的基础细节级别游戏对象 (LOD0) 来确定要遮挡的对象。

如果游戏对象的轮廓在 LOD0 和其他 LOD 级别之间变化很大,这个游戏对象可能不适合设定为静态遮挡物。

要被设定为静态遮挡物,游戏对象必须满足以下条件:

  • 具有 Terrain 或 Mesh Renderer 组件
  • 不透明
  • 在运行时不移动

遮挡剔除步骤:

  • 选中要渲染的物体,将其Static属性设置:
    • 静态遮挡物 (Static Occluder)(这些游戏对象不会移动,但会阻挡后面的游戏对象)或
    • 静态被遮挡物 (Static Occludee)(这些游戏对象不会移动,但会被静态遮挡物遮挡)
    • 一个游戏对象可以同时是静态遮挡物和静态被遮挡物
  • Camera组件选择好Occlusion Culling选项为真
  • 打开设置面板Window->Render->OcclusionCuling
  • 选中面板的Visualzation,点击Bake.
  • 选中Cameras,点击Bake

官方文档:https://docs.unity3d.com/Manual/OcclusionCulling.html

https://www.bilibili.com/video/BV1qt411i7sC?share_source=copy_web

光照贴图Lightmapping

介绍:

Unity中的光照贴图其实就是把场景中的静态物体做间接光的计算,并把计算结果存储到图片上,这些图片会附在相应的物体上,光照贴图会跟随相应的物体。

Lightmapping实现步骤:

1.首先确认渲染的物体,将其Static属性设置为LightMapStatic

2.设置参与Bake的渲染光源的Mode改为Baked

3.Window->Lighting->Setting->点击Generate Lighting,生成光照贴图

官方文档:https://docs.unity3d.com/cn/current/Manual/lighting-window.html

Mipmap

介绍:

  • 如果纹理过大,会产生远处会出现摩尔纹,近处会出现锯齿,可以使用Mipmap来优化。
  • Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。
  • 因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。
  • 同时可以根据实际情况,选择适合的小图来渲染。
  • 所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。

mipmap步骤:

mipmap2

  • 在屏幕空间中取当前像素相邻的像素并查询其对应的uv坐标。
  • 计算出当前像素点与其他像素点距离的最大值L。
  • 根据最大值L通过上面公式计算得到该点所处的层数D。

https://www.lengyueling.cn/archives/37

OverDraw

减少OverDraw区域:http://t.csdn.cn/mUjzx

介绍:

  • 单位像素在单位时间内,被多次渲染
  • UWA分析报告中,以总填充总数来表达一帧内渲染的像素数量,过多Overdraw可能会引起GPU过载,影响动画的播放和界面响应速度。

减少OverDraw: 优化OverDraw的方向就是尽可能的减少不同UI的重叠区域

  • 使用rect mask 2d代替mask组件,因为mask组件自带2层overDraw
    • Mask合批规则:mask内的元素可以和mask内的其他元素进行合批,不能和非mask元素进行合批
    • 之所以不能合批,是因为不同的材质不能合批,mask用的是stencil模板材质,非mask用的是default uimat材质
    • rectmask2d合批规则:只能合批自己遮罩范围内的ui对象,不会像mask一样额外生成两个drawcall
    • 本质区别:mask是通过gpu模板剔除实现,rectmask2d是通过cpu顶点剔除实现的
  • 使用Text组件的OutLine和Shadow,Shadow会增加一层OverDraw,而OutLine是复制了四份Shadow实现的
  • 对于弹出窗口,位于底层的窗口如果被上层遮挡,请将它从Camera渲染层级里移除并将不可见的Canvas设置enable = false,不在Camera渲染层级里的Canvas.enable = true,它下面的UI仍然会产生OverDraw
  • UI上的特效粒子,尽量简单,如果可以请用序列帧动画实现
  • 不使用空白或透明的Image,尽管alpha = 0,还是会渲染并增加一层OverDraw
  • CanvasGroup中Alpha=0 不参与绘制,没有drawcall和overdraw,顶点不会参与重建,而SetActive会把他的顶点和材质弄成脏标记,会导致重建(Canvas 和 Canvas Group:https://www.jianshu.com/p/3a32e01a0bb1

编译优化

Unity3D研究院新方法加快代码编译速度:https://www.xuanyusong.com/archives/4474

Unity的程序集Assembly与加快代码编译速度:https://blog.csdn.net/u012685888/article/details/121747831

内存优化

内存占用组成

  • Unity(native堆,unity资源)
    • 纹理Texture
      • 大小
      • 格式
      • mipmap
      • 压缩
      • 图集
    • 网格Mesh
      • 顶点数
      • 骨骼数
      • 压缩
      • 合批
    • Animation
      • 长度
      • 关键帧数量
      • 压缩
    • Shader
    • Font
    • ......
  • Mono(写的C#代码,注意GC)
  • GfxDriver(显卡驱动,渲染时使用的纹理材质Shader等)
  • FMOD(Unity使用的音频引擎资源)
  • Profiler(性能分析器本身也会占用内存)
  • ......

内存GC

优化要点:

  • 逐一定位,逐一分析
  • 避免频繁的对象创建
  • 尽量避免装箱拆箱
  • 善用缓存
  • 使用对象池
    • 内存(逻辑对象)对象池
    • 游戏物体对象池

关于GC,可以参考我的另外两篇文章: