您的当前位置:首页正文

不一样的视角,深度解读unity性能优化。unity性能优化,unity内存优化,cpu优化,gpu优化,资源优化,资源包、资源去重优化,ugui优化。

2024-11-10 来源:个人技术集锦

不知不觉,以从事unity游戏开发将近十年了。一路前行,终会站在巨人的肩膀上。愿你我都安好。

直奔主题吧。知其然,更知其所以然。

从unity运行到看到游行画面的整个工作流来说,分为两个计算处理阶段。

CPU 计算处理阶段(应用阶段)

GPU 计算处理阶段(渲染阶段)

概述

在经过 CPU 的应用阶段后,GPU 会处理 CPU 在一帧内计算处理的所有 draw call。一个 draw call 是 GPU 处理一个网格数据的流水线操作。这里的网格是由 CPU 合批后的大网格数据,下文将对此进行详细介绍。

Draw Call 工作流程简述

Shader 加载

要了解 GPU 的处理逻辑,首先需要了解其整个处理流程。当 GPU 接收到一个 draw call 指令时,会将与材质球相关联的 shader 加载进 GPU,设置 shader 的状态(如深度缓冲区、颜色缓冲区、材质属性等),并加载所需数据(模型网格数据、纹理数据、变量初始化数据)。

纹理和属性将存放到缓冲区

  1. Uniform Buffers(统一缓冲区)
    统一缓冲区用于存储 Shader 中的全局变量或属性,如颜色、光照参数、材质属性等。每次渲染调用时,统一缓冲区会被传递给 GPU,并在 Shader 中访问这些属性。MaterialPropertyBlock 可以用来修改这些属性,而无需改变整个材质。

  2. Texture Units(纹理单元)
    纹理单元用于管理 Shader 中绑定的纹理资源。当通过 MaterialPropertyBlock 修改纹理时,它切换的是 Shader 中使用的纹理单元,而不是改变材质本身绑定的纹理。

  3. Constant Buffers(常量缓冲区)
    常量缓冲区类似于统一缓冲区,但它们用于存储在渲染过程中保持不变的一组常量值。MaterialPropertyBlock 可以修改这些常量值,但修改仅在当前渲染调用中有效,不会影响其他实例化的对象。

  4. Structured Buffers(结构化缓冲区)
    结构化缓冲区用于存储和处理复杂的数据结构,特别是在计算着色器或高级渲染效果中。虽然 MaterialPropertyBlock 通常不会直接操作结构化缓冲区,但理解这些缓冲区在 Shader 中的作用有助于理解属性的存储和传递过程。

通常,Shader 包含一个顶点函数(#pragma vertex vert)和一个片元函数(#pragma fragment frag),有时还包括曲面着色器和几何着色器。这些函数对应渲染管线中的顶点着色器和片元着色器。

纹理处理

Shader 加载完成后,GPU 会为 Shader 分配对应的纹理单元,用于存储纹理对象。一个纹理对象可以是单个图片纹理(如 _Texture("Texture", 2D)),也可以是一组纹理数组(如 _TextureArray("Texture Array", 2DArray))。这些纹理通过材质球绑定后会被加载进来。

流水线计算处理

  1. 顶点着色器
    首先通过顶点着色器(调用顶点函数),将模型从模型空间转换到平面的裁切空间。

  2. 曲面着色器和几何着色器
    如果存在曲面着色器和几何着色器,则依次执行这些着色器。

  3. 三角裁切、三角形遍历、光栅处理

    • 三角裁切:对模型进行三角裁切。
    • 三角形遍历:将模型裁切空间中的三角形映射到屏幕像素。
    • 光栅处理:进行深度测试、透明测试等处理。
  4. 片元着色器
    最后调用片元函数,执行片元着色器,完成对一个模型的处理。

完成 Draw Call 处理

一个 draw call 处理完成时,通常对应一个材质球。


流水线计算处理

顶点着色器

首先,顶点着色器通过调用顶点函数,主要功能是将模型从模型空间转换到平面的裁切空间。

曲面着色器和几何着色器

如果有曲面着色器和几何着色器,它们将在顶点着色后执行。

三角裁切、三角形遍历、光栅处理

接下来,GPU 进行三角裁切,然后将模型裁切空间的三角形映射到屏幕像素。接着,GPU 执行光栅处理,包括深度测试和透明测试等。

片元着色器

最后,片元着色器通过调用片元函数,完成对模型片元的处理。


完成 Draw Call 处理

在渲染阶段,GPU 处理完一个 draw call,即完成了一个材质球的处理。在合批过程中,如果相同材质的合批被打断,就会产生新的模型和材质,这意味着需要进行新的 draw call 处理。

从上面知识点反推。

一.首先画面渲染要经过顶点着色器和片元着色器

1.从着色器编程角度来说

在满足效果需求的情况下尽量使用fixed类型变量,然后能在顶点计算的的一定要在顶点进行计算。少使用if和分支语句。在shader编程中大部分逻辑都是数值运算的。还有个知识点就是纹理数组可以处理多张纹理切换,这个要比单张纹理设置要高效,因为每次设置纹理有可能处罚纹理切换(从内存重新绑定新纹理然后加载到gpu内存)

2.从顶点着色器角度。

减少顶点函数的调用,进一步说就是减少网格顶点数据。那么如何减少顶点数据呢。

减少顶点模型数据,首先从cpu出发,

1.Lod技术。根据摄像机远近,进行模型精度切换,进而减少模型顶点。
2.遮挡剔除技术。剔除摄像机视角看不到的物体模型。
3.相机视锥剔除。这个不在考虑范围内。因为unity默认优化掉了

3. 从 draw call 的角度来看

draw call 优化的主要目的是优化 GPU 的性能。之所以单独列出 draw call,是因为每个 draw call 都会导致重新绑定着色器、重新设置渲染状态和重新绑定数据,即使多个 draw call 使用的是同一个材质也是如此。具体来说:

  • 重新绑定着色器:每次 draw call 需要将当前使用的着色器程序绑定到 GPU,以便进行渲染。
  • 重新设置渲染状态:包括设置深度测试、混合模式、剔除模式等渲染状态,确保渲染行为符合预期。
  • 重新绑定数据:将顶点缓冲区、索引缓冲区等数据重新绑定到 GPU,以供渲染使用。

虽然在最新的图形 API(如 Vulkan 和 OpenGL)中,这些操作已经得到了优化,但这些优化依赖于硬件设备,导致技术不能通用。

  1. 减少 draw call

    减少 draw call 意味着减少材质的切换次数,并尽量将相同的网格数据一次性传递到 GPU,用一个 draw call 来渲染。这样可以减少着色器的重新加载、纹理单元的重新分配、渲染状态的重新设置和缓冲区的重新绑定,从而提升渲染效率。

衍生出的两种技术:

  • 动态批处理(Dynamic Batching)

    这种技术合并多个具有相同材质的动态物体的渲染调用。动态批处理特别适用于顶点数量少的对象(通常限制为 300 顶点以内)。它在粒子特效和 UGUI(Unity UI)中应用广泛。例如,在 UGUI 中,每张图片通常是一个长方形网格,整个 Canvas 的元素会合并成一个网格来减少 draw call 数量。

  • 静态批处理(Static Batching)

    静态批处理合并场景中静态物体的渲染调用。所有使用相同材质的静态物体会合并成一个大的网格,这样可以减少 draw call 的数量。根据材质的种类,一个场景可能会生成多个合并的大网格,每个网格都只需一个 draw call 来渲染。

在减少 draw call 的过程中,还衍生出了其他技术:

通过 MaterialPropertyBlock,你可以在保证渲染性能的同时,实现实例化模型的多样化表现。

  • GPU 实例化技术(GPU Instancing)

    GPU 实例化允许将多个相同的网格数据一次性传递给 GPU,由 GPU 进行变换计算。这种技术减少了 CPU 的工作量,特别是对于大量相同物体的场景,如草地或树木等。GPU 实例化不支持 SkinnedMeshRenderer(蒙皮网格渲染器),因此衍生出了 GPUSkinning 技术。

  • GPUSkinning 技术

    GPUSkinning 将顶点数据和骨骼数据传递给 GPU,让 GPU 在顶点着色器中通过权重计算顶点的变换坐标。这种技术显著减轻了 CPU 的负担,特别是在处理复杂的动画时,如角色动画。

  • 计算着色器(Compute Shader)

    计算着色器是一种用于执行并行计算任务的 GPU 程序。它不仅可以用于渲染计算,还可以进行一般计算任务,如骨骼变换和粒子系统模拟。使用计算着色器可以进一步减轻 CPU 的负担,并提高计算效率,因为它可以将大量的计算任务并行处理在 GPU 上。

  • 材质的使用:

    在 Unity 中,通常情况下,当一个模型被实例化成多个模型时,这些实例化的模型会共享同一个材质球。这意味着,如果你修改了材质球的属性(例如颜色、纹理等),所有实例化的模型都会发生相同的变化。然而,在许多实际场景中,我们希望这些实例化的模型能够有不同的材质表现。

    一种实现不同材质表现的方法是实例化多个材质球,然后将这些材质球分别赋值给不同的模型,再分别修改材质球的属性。然而,这种方法并不可取。因为一个材质球通常意味着一次 Draw Call(如上文所述),实例化多个材质球会导致多次 Draw Call,从而增加渲染开销,影响性能。

    为了在多个实例化模型之间共享材质球的同时实现不同的材质表现,Unity 提供了 MaterialPropertyBlock。使用 MaterialPropertyBlock,我们可以为每个实例化的模型设置不同的属性,而不会实际修改共享的材质球,从而避免产生额外的 Draw Call

    MaterialPropertyBlock 的原理:

    MaterialPropertyBlock 的原理是它不会直接修改材质球的属性,而是通过将要修改的属性传递给渲染管线,在渲染的过程中动态应用这些属性。具体来说,MaterialPropertyBlock 会获取材质球中 Shader 绑定的纹理单元和属性缓存(UniformBuffers:存储常规属性),并在渲染过程中对这些缓存进行临时修改,从而实现每个模型不同的材质表现。

    总结:

  • 共享材质球的优势:共享材质球可以减少 Draw Call,提高渲染性能。但直接修改共享材质球会影响所有实例化的模型。

  • 实例化多个材质球的缺点:实例化多个材质球可以实现不同的材质表现,但会增加 Draw Call,对性能造成负面影响。

  • MaterialPropertyBlock 的使用MaterialPropertyBlock 是一种高效的解决方案,允许在不修改材质球本身的情况下,为每个实例化模型动态设置不同的属性,从而减少 Draw Call,提高渲染效率。

4.从cpu角度

1. 物理碰撞优化

  • 减少触发器和刚体的使用

    • 触发器:触发器用于检测物体之间的重叠而不产生物理响应。减少触发器的使用,尤其是在大量物体的场景中,可以减轻CPU的负担。仅在必要时使用触发器,并确保它们的用途明确。
    • 刚体(Rigidbody):动态物体需要Rigidbody来进行物理计算。对于静态物体或不需要物理响应的物体,使用Collider而不是Rigidbody,以减少物理计算开销。
  • 减少物理计算

    • 优化Rigidbody使用:对于不需要频繁更新的物体,将其设置为isKinematic,这样它们将不参与物理计算。对于不需要实时更新的物体,可以调整Collision Detection模式为Discrete而不是Continuous
  • 优化碰撞检测

    • 简化碰撞体:尽量使用简单的碰撞体(如BoxCollider、SphereCollider、CapsuleCollider)代替复杂的MeshCollider。复杂的碰撞体会增加物理计算的负担。
    • 减少碰撞体数量:合并小物体的碰撞体到一个较大的碰撞体中,避免大量小的碰撞体影响性能。
  • 分层碰撞检测

    • Layer Collision Matrix:使用Unity的Layer Collision Matrix来控制哪些层之间进行碰撞检测。对于不需要碰撞检测的层(例如UI层、背景层),可以取消碰撞设置,减少计算开销。

2. 减少模型顶点

  • LOD技术

    • 使用不同的细节级别:对于远离摄像机的物体使用低细节的LOD模型,减少渲染和计算负担。确保在场景中设置合适的LOD级别,并根据实际需求调整LOD切换的距离。
  • 遮挡剔除技术

    • 利用Unity的遮挡剔除(Occlusion Culling):确保只渲染在摄像机视野内的物体,减少不必要的渲染和计算。通过Occlusion Culling设置可以提高性能。
  • 模型减面

    • 简化模型几何:减少模型的顶点数量和多边形数目,优化模型的复杂性。使用3D建模工具中的减面功能来减少模型的面数。

3. 静态合批

  •  静态合批也是一个有效减少cpu计算的方法

    •  通过静态合批,将合批运算在编辑器里预先将模型合并计算好。然后将合批的大网格数据和相关信息存储起来。运行时直接使用。

4. 动画优化

  • 减少关键帧

    • 优化动画数据:减少动画中的关键帧数量,通过合理设置关键帧间隔来减轻动画计算的负担。对于重复的动画动作,可以使用动画曲线来减少关键帧数量。
  • 优化Animator Controller

    • 简化Animator状态机:减少Animator Controller中的状态和过渡,避免复杂的动画状态机影响性能。将频繁使用的动画进行简化处理。

5. 布料物理模拟

  • 减少布料物理的使用
    • 优化布料模拟:布料模拟会消耗大量计算资源。在不需要动态布料模拟的情况下,考虑使用静态布料贴图或简化布料模拟的设置来减轻CPU负担。

6. 减少代码对象的实例化

  • 对象缓存池(Object Pooling)
    • 实现对象池:对于需要频繁创建和销毁的对象(如子弹、敌人),使用对象池技术来复用对象,避免频繁的内存分配和垃圾回收。这可以显著减少性能开销。

7. 导航网格优化

  • 降低精度
    • 调整NavMesh精度:根据场景的复杂度和需求,降低NavMesh的精度。减少NavMesh的细节可以降低寻径计算的复杂性,从而提高性能。
    • 使用NavMesh区域:将NavMesh划分为多个区域,减少单一区域的计算量,特别是在大型场景中。这有助于减少更新和计算开销。

8.灯光阴影优化

1. 使用烘焙灯光 (Baked Lighting)
  • 烘焙静态光照:对于静态场景或物体,可以使用烘焙光照来生成光照贴图。烘焙光照会预先计算光照效果,减少实时计算的开销。
  • 减少实时灯光的数量:实时灯光计算开销较大,尽量减少使用,或者将其设置为混合灯光 (Mixed Lighting),让静态物体使用烘焙光照,动态物体使用实时光照。
2. 减少阴影贴图的分辨率 (Shadow Map Resolution)
  • 降低阴影贴图分辨率:高分辨率的阴影贴图会增加 GPU 负担。在不明显影响画质的前提下,适当降低阴影贴图的分辨率。
  • 使用 Cascade Shadows:对于远处的阴影,可以使用级联阴影 (Cascade Shadows),分段设置阴影的质量和分辨率,降低不重要区域的阴影质量。
3. 调整阴影距离 (Shadow Distance)
  • 缩短阴影距离:通过调整阴影距离,只在玩家视野范围内渲染阴影,减少远处不重要区域的阴影计算。
  • 使用灯光阴影裁剪 (Light Shadow Culling):可以通过代码动态调整某些灯光的阴影距离或关闭远离摄像机的灯光阴影。
4. 限制灯光的范围和数量
  • 控制灯光范围:减少灯光影响的范围,可以避免不必要的光照计算。Unity 的 Light 组件中可以调整光照范围 (Range)。
  • 减少动态光源的数量:尽量避免同时存在过多的动态光源,特别是在同一个区域内。可以使用脚本在不同区域动态激活/禁用灯光。
5. 优化实时阴影设置
  • 使用软阴影和硬阴影的混合:软阴影的计算开销较大,可以根据需要在场景中混合使用软阴影和硬阴影。
  • 减少实时阴影的更新频率:通过脚本控制,降低实时阴影的刷新频率。例如,只有当物体或光源移动时才更新阴影。
6. 使用基于距离的光照衰减 (Light Attenuation)
  • 启用距离衰减:确保光源使用适当的距离衰减 (Attenuation),以减少光照影响范围外的计算。
  • 调整光衰减曲线:通过调整衰减曲线,使得光照效果在远距离处逐渐减弱,降低对远距离物体的光照计算。

总结

通过从多个角度进行优化,可以显著提高Unity游戏的性能。以下是一个综合优化的策略:

  • 物理计算:减少触发器和刚体使用、优化碰撞体的数量和复杂性、利用Layer Collision Matrix进行分层碰撞检测。
  • 模型和渲染:使用LOD技术、遮挡剔除、简化模型顶点。
  • 动画:减少关键帧、简化Animator状态机。
  • 布料模拟:减少布料物理模拟的使用。
  • 对象管理:实现对象缓存池来减少对象实例化。
  • 导航网格:调整NavMesh的精度和分区来优化寻径计算。

内存优化

内存优化的核心在于合理设置加载资源、去重资源以及优化代码。以下是针对各个方面的详细说明:

图片优化
  • 使用合适的通道和压缩格式:根据需要选择图片的通道(如RGB、RGBA、单通道),并尽量使用压缩格式(如ETC2、ASTC、DXT等),这些格式能显著减少内存使用。

  • 关闭不必要的读写选项:在Unity中,如果不需要在运行时对图片进行读写操作,应关闭Read/Write Enabled选项,防止纹理在内存中被重复存储。

  • 根据需求关闭MipMap:对于UI元素或仅在特定尺寸显示的纹理,关闭MipMap生成可以节省内存。

  • 调整图片大小:根据设备性能和实际需求合理调整纹理大小,避免使用过大的纹理导致内存浪费。

  • 使用图集(Texture Atlas):将多个小纹理合并成一个大纹理,可以减少纹理切换(Texture Swaps),节省内存并提高性能。

模型网格优化
  • 关闭不必要的读写选项:与图片优化类似,除非需要在运行时修改模型网格,否则应关闭Read/Write Enabled选项以节省内存。

  • 优化模型质量:在模型导入阶段减少多边形数量,提高模型的优化程度,这些工作应在美术设计阶段完成。

  • 控制骨骼数量:减少模型的骨骼数量,尤其是在性能要求高的设备上,这可以显著降低内存使用。

  • 分离模型与材质:关闭模型材质的自动导入,将模型与材质分开处理。使用共享材质并通过打包资源实现按需加载,减少内存占用。

  • 分离模型与动作:关闭动作的自动导入,利用Unity的状态机机制进行动作复用。在打包时将动作资源单独分离,按需加载,而不是一次性加载所有动作数据。

  • 减少动画关键帧:通过减少关键帧数量来优化动画数据,降低内存使用。

音频优化
  • 使用压缩音频格式:选择压缩的音频格式(如OGG、MP3)代替未压缩的格式(如WAV),可以显著降低内存占用。Unity支持多种音频压缩格式,应选择适合目标平台的格式。

  • 合理设置音频加载类型

    • Decompress on Load:适用于小型音效,加载后解压缩存储在内存中,占用较多内存但无播放延迟。
    • Compressed in Memory:适用于中型音效,压缩存储在内存中,减少内存占用但可能会有解压缩开销。
    • Streaming:适用于大型背景音乐,直接从磁盘读取,内存占用最小但会有播放延迟。
  • 优化音频剪辑长度:将长音频剪辑分割成较短片段,便于在不需要时轻松卸载或切换,减少内存占用。

资源去重

资源包依赖去重

资源去重的关键在于合理的资源打包逻辑。通常,游戏开发过程中使用资源包(如Unity的AssetBundle或Addressables)加载模式来管理资源。因此,资源去重的工作应该在资源打包阶段完成,而不是在加载时做判断。

网上说是在加载时做判断,这纯正时扯蛋。说这话的根本不明白打包加载原理。这里不赘述原理,会单独写文章。因为讲清楚篇幅会很大。要做到打包不重复资源的极致还是很麻烦的。加载也是。即使最新的Addressables原生插件也是要自己做依赖分析,控制那些资源要打包,这也是扯蛋。当你通读Addressables文档后会发现确实是这样。

如何进行资源去重

  1. 资源依赖分析:在打包之前,对所有资源进行依赖分析,找出哪些资源被多个资源依赖。

  2. 共享资源单独打包:根据依赖分析的结果,将所有被两个或多个其他资源依赖的共享资源单独打成一个包。这可以确保这些共享资源只被加载一次,完全去掉重复资源,避免内存浪费。即一个资源被两个或两个以上的其他资源依赖,那么这个资源一定单独打成一个包,就会完全去掉重复资源

  3. 资源本身去重:对于项目中在不同路径下存在的相同资源,可以通过获取资源的哈希值进行比对,找出并删除重复的资源文件,确保每个资源只在一个位置存在。

  • 需要注意的是,即使使用Unity的Addressables插件,去重工作依然需要手动进行依赖分析和打包控制。Addressables主要简化了资源加载过程,但并不能自动解决资源的去重问题。需要开发者在打包时,通过详细的资源依赖分析,确定哪些资源需要单独打包,以避免资源重复。

烘焙场景灯光优化

  • 优化光照贴图的大小和分辨率:在进行烘焙场景灯光时,应控制光照贴图的分辨率在合理范围内,避免不必要的高分辨率,从而节省内存。

代码优化

  • 减少对象的频繁创建与销毁:通过使用对象池(Object Pooling)技术来重复利用对象,减少垃圾回收(GC)的频繁调用,优化内存使用。

  • 内存管理:定期检查代码中的内存使用情况,清理不再使用的对象引用,防止内存泄漏。

通过合理设置加载资源、去重资源以及优化代码,可以有效地进行内存优化,减少游戏运行时的内存消耗,提高游戏性能。

UGUI 优化的核心原理

UI优化的核心是优化动态批处理(Dynamic Batching)。在Unity的UI系统(UGUI)中,优化UI性能的关键在于减少Draw Call的数量。Draw Call的数量通常取决于Canvas内的动态批处理效率,因此,优化Canvas的批处理是提升UI性能的关键。

理想的Canvas状态

在理想状态下,Canvas内的所有UI元素可以形成一个单独的动态批处理,从而只产生一个Draw Call。要实现这种理想状态,需要满足以下几个条件:

  1. 简单的网格结构:UI元素通常使用简单的矩形网格(Quad Mesh),这样有助于批处理的合并。

  2. 同一图集(Texture Atlas):通常,一个界面上的UI元素会被打包到同一个图集中,以减少纹理切换,从而最大化动态批处理的效率。

  3. 同一材质(Material):所有UI元素使用同一种材质,可以进一步减少Draw Call的数量。

当以上条件得到满足,并且UI元素不发生变化(例如文本未更新、图片未移动),Canvas内的批处理就不需要重新计算,此时,一个Canvas只会有一个Draw Call。

动态批处理的挑战

然而,实际情况通常更复杂。一旦UI元素发生变化,例如文本内容改变或图像位置移动,Canvas就会触发重绘操作。这种情况会导致所有的动态批处理重新计算,从而增加Draw Call的数量。为了应对这个问题,提出了“动静分离”的策略。

动静分离

“动静分离”的策略是将动态变化的对象和静态不变的对象分开,使用不同的Canvas来管理:

  • 动态Canvas:包含那些会经常更新或移动的UI元素。
  • 静态Canvas:包含那些不会频繁变化的UI元素。

通过动静分离,可以减少Canvas的重绘次数,从而减少Draw Call。然而,这种方法在实际操作中存在一些问题:

  1. UI层级错乱:动静分离会导致多个Canvas的创建,这些Canvas之间的层级关系容易错乱,导致UI元素显示顺序出错。

  2. 复杂的Canvas管理:当多个Canvas被使用时,管理这些Canvas之间的交互和渲染顺序会变得非常复杂,可能会引起UI元素穿插和显示异常等问题。

  3. 局限性:动静分离在复杂UI场景下的应用非常有限,不容易做到准确分离。尤其是在不同的UI页面加载时,原本正常的UI层级关系可能会发生变化,导致显示错误。

因此,动静分离虽然在理论上能够优化Canvas的批处理,但在实际操作中,其应用局限性较大,需要谨慎使用。

多个图集的批处理挑战

在实际应用中,UI界面通常涉及多个图集,例如,UI元素的图像和字体通常属于不同的图集,这为动态批处理带来了额外的挑战。

图集和字体图集的穿插

假设有两个图集,一个是UI图集A,另一个是字体图集。以下是几个典型场景:

  • UI图集和字体图集的顺序排列

    • 当所有引用字体图集的元素(例如Text组件)都在引用UI图集A元素的上面或下面时,会形成两个独立的动态批处理,即两个Draw Call。
  • UI图集和字体图集的交叉排列

    • 如果字体图集中的元素(Text组件)与UI图集A中的元素在Hierarchy面板的UI节点顺序中交叉排列,可能会导致两种情况:

      • 不打断批处理:如果图集A的UI元素没有像素遮挡或与字体图集的元素像素重叠,动态批处理不会被打断,Draw Call数量保持不变。

      • 打断批处理:如果图集A中的UI元素在字体图集元素的上下之间,并且形成像素遮挡或重叠,批处理会被打断。这会形成三个Draw Call:

        1. 底层的字体图集元素形成一个Draw Call。
        2. 中间的UI图集A的元素形成一个Draw Call。
        3. 上层的字体图集元素形成另一个Draw Call。
复杂的图集场景

在更复杂的场景中,同一个UI图集可能会生成多张纹理(多个子图集)。可以将一张图集生成的多张纹理,理解为图集的多张子图集。在这种情况下,优化的原理仍然相同:尽量减少不同图集之间的穿插和遮挡,减少Canvas的重绘次数,最大化动态批处理的效率。

要优化这些复杂场景,可以采用以下方法:

  • 减少图集间的穿插:避免不同图集之间的UI元素在Hierarchy中的交叉排列,尽量将同一图集的元素放在一起,以减少Draw Call的数量。

  • 优化图集布局:合理安排图集的内容,尽量将相关性高的元素放入同一个图集中,减少不同图集的切换。

  • 控制Canvas的更新频率:将静态和动态元素分开到不同的Canvas中,减少不必要的Canvas重绘。

通过这些策略,可以有效提升UGUI的渲染效率,减少Draw Call的数量,从而提升整体UI性能。

Top