UGUI 性能优化系列:第一篇——基础优化与资源管理

发布于:2025-07-18 ⋅ 阅读:(19) ⋅ 点赞:(0)

UGUI 性能优化系列:第一篇——基础优化与资源管理

UGUI 性能优化系列:第二篇——Canvas 与 UI 元素管理

在 Unity 游戏中,用户界面(UI)是玩家与游戏交互的核心。然而,不当的 UGUI 使用常常成为游戏性能的瓶颈,尤其是在移动设备上。理解 UGUI 的工作原理并掌握其优化技巧,是每个 Unity 开发者必备的技能。本系列的第一篇文章,我们将从最基础的层面出发,深入探讨 UGUI 的渲染管线,以及如何从 资源管理 的角度进行优化,为后续更深入的优化打下坚实基础。


一、UGUI 渲染管线简介

要优化 UGUI,首先需要了解它是如何将 UI 元素绘制到屏幕上的。UGUI 的渲染过程可以简化为以下几个核心步骤:

1. UI 元素层级与事件系统

当我们在 Hierarchy 窗口中创建 UI 元素时,它们会形成一个父子层级结构。UGUI 会根据这个层级结构以及它们的 Rect Transform 属性来计算每个 UI 元素的最终位置和大小。同时,UGUI 的事件系统 (EventSystem) 负责处理用户的输入(如点击、拖拽),并将其分发给相应的 UI 元素。

2. Mesh 生成与更新

UGUI 中的每个可渲染的 UI 元素(如 Image, Text, RawImage 等)最终都需要被转换为 Mesh(网格)才能被 GPU 渲染。这个 Mesh 包含了顶点的坐标、UV 坐标(用于纹理映射)和颜色信息。

  • 脏标记(Dirty Flag)与重建(Rebuild):
    当 UI 元素的某些属性发生改变时(例如 Text 内容变化、ImageSprite 变化、Rect Transform 大小位置改变),UGUI 会给这个元素打上一个“脏标记”。在一个 Canvas 上,当任何子元素的“脏标记”被激活时,UGUI 会触发该 Canvas 的 重建 过程。
    重建过程会重新计算受影响 UI 元素的 Mesh,并将新的 Mesh 数据提交给 GPU。这个过程是性能开销的主要来源之一,因为 Mesh 的生成涉及到 CPU 计算,并且数据传输到 GPU 也会消耗带宽。
3. 合批(Batching)

为了提高渲染效率,GPU 喜欢一次性接收大量数据进行处理,而不是零散地接收小批数据。因此,Unity 会尝试将多个可以共用相同材质(Material)和纹理(Texture)的 UI 元素的 Mesh 合并成一个更大的 Mesh,然后一次性提交给 GPU 进行渲染。这个过程就是 合批(Batching)

  • Draw Call:
    每一次 GPU 接收到渲染指令(渲染一批 Mesh)并进行渲染的过程,被称为一个 Draw Call。Draw Call 的数量是衡量渲染性能的关键指标之一。Draw Call 越多,CPU 和 GPU 之间的通信开销就越大,性能也就越低。
    理想情况下,我们希望尽可能减少 Draw Call 的数量。UGUI 的合批机制正是为了达到这个目的。
4. 裁剪(Culling)与遮挡(Occlusion)
  • 裁剪: UGUI 会自动裁剪超出 Canvas 范围的 UI 元素,这意味着那些完全在 Canvas 外部的 UI 元素不会被渲染。
  • 遮挡: 尽管 UGUI 并没有像 3D 场景那样的完整遮挡剔除机制,但它会根据 UI 元素的层级和 Rect Transform 的布局来确定哪些元素在 Z 轴上被其他元素完全遮挡,从而避免绘制那些完全被遮挡的像素。
5. 像素填充(Overdraw)

即使一个 UI 元素没有被完全遮挡,它也可能与其他 UI 元素重叠。在重叠区域,GPU 可能需要多次绘制同一个像素,这个现象称为 Overdraw(过度绘制)。Overdraw 发生在 GPU 层面,它会增加 GPU 的像素填充率(Fill Rate)压力,尤其是在移动设备上,过高的 Overdraw 会显著影响性能。

了解了这些基本原理,我们就可以有针对性地进行优化了。


二、Sprite Atlas(图集)优化

图集(Sprite Atlas) 是 UGUI 性能优化中最重要的手段之一。它的核心思想是将多个小图片打包到一个大图片中,从而减少 Draw Call 数量,提高渲染效率。

1. 为什么要使用图集?

在 UGUI 中,每个 Image 组件通常引用一个 Sprite。如果每个 Sprite 都对应一张独立的纹理图片,那么在渲染时,每个 Image 都可能产生一个独立的 Draw Call。想象一下一个复杂的 UI 界面,有几十甚至上百个 Image 组件,这将导致 Draw Call 数量飙升,严重拖累性能。

使用图集后,所有打包到同一个图集中的 Sprite 共享一张大的纹理图片。当这些 Sprite 被渲染时,只要它们使用相同的材质,Unity 就可以将它们的 Mesh 合并,一次性渲染,从而显著减少 Draw Call。

优势总结:

  • 减少 Draw Call: 这是最核心的优势,直接降低 CPU 和 GPU 的通信开销。
  • 减少内存占用(可能): 某些情况下,打包后的图集可能比散图的总和占用更少的内存,因为它避免了每个图片单独加载的额外开销。
  • 提高加载速度: 加载一张大图通常比加载多张小图更快。
2. Unity 中的图集制作与使用

Unity 提供了两种主要的方式来创建和使用图集:Sprite Packer手动图集

a. Sprite Packer(推荐)

Sprite Packer 是 Unity 内置的自动图集打包工具,它能自动检测项目中符合条件的 Sprite 并将其打包成图集。

使用步骤:

  1. 启用 Sprite Packer:
    打开 Unity 编辑器,选择 Edit > Project Settings > Editor。在 Sprite Packer 部分,将 Mode 设置为 EnabledEnabled For Builds

    • Enabled: 在编辑器和打包时都启用 Sprite Packer。这会让你在编辑器中也能看到打包后的效果,便于调试。
    • Enabled For Builds: 只在打包时启用 Sprite Packer。在编辑器中会保持散图状态,性能消耗可能稍高,但对于一些不希望编辑器实时打包的场景可能更合适。通常建议设置为 Enabled
  2. 设置 Sprite 的 Packing Tag:
    选择你想要打包的 Sprite 纹理(通常是 Texture Type 设置为 Sprite (2D and UI) 的图片)。在 Inspector 窗口中,找到 Packing Tag 属性。

    • 输入一个相同的字符串作为 Packing Tag,所有拥有相同 Packing TagSprite 将会被打包到同一个图集中。
    • 命名规范建议: Packing Tag 应该反映图集的内容或用途,例如 UI_Common, UI_Icons, UI_Battle 等。
    • 你也可以在 Packing Tag 前面加上 [TIGHT][RECTANGLE] 来控制打包方式,但通常默认行为就足够了。
  3. 打包图集:

    • Sprite Packer 启用后,Unity 会在特定时机(例如保存项目、进入 Play 模式、构建游戏)自动进行图集打包。
    • 你也可以手动触发打包:在菜单栏选择 Window > 2D > Sprite Packer,然后点击 Pack 按钮。
    • Sprite Packer 窗口中,你可以预览打包后的图集,以及各个 Sprite 在图集中的位置。
  4. 在 UI 中使用:

    • 一旦 Sprite 被打包成图集,你在 Image 组件中引用这些 Sprite 时,它们会自动使用打包后的图集。你无需做额外的修改。

Sprite Packer 的优缺点:

  • 优点:
    • 自动化: 大大简化了图集管理工作,无需手动调整布局。
    • 高效: Unity 内部算法会自动优化图集尺寸和排布,最大化空间利用率。
    • 易于维护: 添加、删除或修改 Sprite 后,Unity 会自动重新打包。
  • 缺点:
    • 不可控性: 开发者对最终图集的大小和布局控制较少,可能不符合特定需求(例如,要求某个图集固定尺寸)。
    • 构建时间: 项目中 Sprite 数量众多时,每次打包都可能增加构建时间。
b. 手动图集(旧版本或特定需求)

在较老的 Unity 版本或某些需要精确控制图集内容的特殊情况下,开发者会选择手动制作图集。

使用步骤:

  1. 创建大图: 使用 Photoshop、GIMP 等图像编辑软件,将多个小图片拼接成一张大图。
  2. 切割 Sprite: 将这张大图导入 Unity,将其 Texture Type 设置为 Sprite (2D and UI)。在 Inspector 窗口中,将 Sprite Mode 设置为 Multiple。然后点击 Sprite Editor 按钮,手动或自动(通过 Slice 功能)将大图切割成多个 Sprite
  3. 在 UI 中使用:Image 组件中,直接引用这些手动切割出来的 Sprite

手动图集的优缺点:

  • 优点:
    • 完全可控: 开发者可以精确控制图集的内容、尺寸和布局。
    • 适用于复杂场景: 当需要将一些并非 Sprite 类型(如 RawImage 或 3D 模型的贴图)的图片手动打包到一起时,这是一种灵活的方式。
  • 缺点:
    • 繁琐: 手动拼接和切割工作量大,尤其是在项目后期需要频繁修改时。
    • 低效: 人工排布通常不如自动算法高效,可能造成空间浪费。
    • 维护困难: 增删改 Sprite 需要重新编辑大图并重新切割,容易出错。

结论: 除非有非常特殊的理由,否则强烈建议使用 Sprite Packer 来管理图集。

3. 图集大小、格式与压缩

图集的尺寸和格式直接影响内存占用和加载速度。

a. 图集尺寸
  • 推荐尺寸: 大多数情况下,图集尺寸应为 2 的幂次方(例如 256x256, 512x512, 1024x1024, 2048x2048, 4096x4096)。这是因为 GPU 在处理 2 的幂次方的纹理时效率最高。
  • 最大尺寸: 检查目标平台的 GPU 支持的最大纹理尺寸。通常移动设备支持 2048x2048 或 4096x4096。超过这个尺寸的纹理可能无法加载或被迫降采样,反而浪费资源。
  • 合理控制: 避免创建过大或过小的图集。过大的图集会增加内存占用和加载时间,过小的图集可能导致无法有效合批。
b. 图片格式与压缩

图集的格式和压缩方式直接决定了其在内存中的大小和渲染性能。

  • RGBA 32-bit: 默认格式,每个像素 32 位(R, G, B, A 各 8 位),提供最高的图像质量,但内存占用最大。适用于需要高质量透明度的 UI 元素。
  • RGBA 16-bit: 每个像素 16 位,质量略有下降但内存占用减半。在对画质要求不是特别高的情况下,可以考虑使用。
  • RGB 24-bit: 没有透明度通道,内存占用低于 RGBA。适用于不透明的 UI 元素,但 UGUI 中带透明度的 UI 元素占多数。
  • ETC2 (Android & OpenGL ES 3.0+):
    • ETC2 RGB4: 无透明度,适用于不带 Alpha 的图片,压缩比高。
    • ETC2 RGB4 A1: 1 位 Alpha 通道,适用于只有完全透明或完全不透明的图片。
    • ETC2 RGBA8: 8 位 Alpha 通道,支持高质量透明度,压缩比适中。
    • 优势: 硬件解压,GPU 直接读取,无需 CPU 解压,效率高,内存占用小。
    • 缺点: 仅支持 Android 和部分 iOS 设备(OpenGL ES 3.0+),兼容性不如 ASTC。
  • ASTC (Android & iOS):
    • 自适应可伸缩纹理压缩: 更先进的纹理压缩格式,提供更高的压缩比和更好的图像质量。
    • 块大小: 可以选择不同的块大小(如 4x4, 6x6, 8x8, 12x12),块越小质量越好,内存越大;块越大质量越差,内存越小。
    • 优势: 兼容性更好(Android 和 iOS 广泛支持),压缩质量和效率通常优于 ETC2。
    • 缺点: 压缩时间可能较长,对硬件支持有一定要求。
  • PVRTC (iOS Only):
    • PowerVR 纹理压缩: 针对 PowerVR GPU 优化的压缩格式(早期 iOS 设备主要使用),有 2-bit 和 4-bit 两种。
    • 优势: 在特定 iOS 设备上表现优秀。
    • 缺点: 质量相对较差,且仅限 iOS 平台。

选择建议:

  1. 优先考虑平台专属压缩格式:
    • Android: 优先使用 ASTC,其次是 ETC2
    • iOS: 优先使用 ASTC,其次是 PVRTC(如果目标设备较旧且对内存极致敏感)。
    • PC/Standalone: 通常可以使用 DXT1 (RGB)DXT5 (RGBA)
  2. 根据图片内容选择:
    • 对于不需要透明度的图片:选择 RGB 格式或对应平台的无 Alpha 压缩格式(如 ETC2 RGB4, ASTC NxB 无 Alpha 块)。
    • 对于需要透明度的图片:选择 RGBA 格式或对应平台的带 Alpha 压缩格式(如 ETC2 RGBA8, ASTC NxB 带 Alpha 块)。
  3. 权衡质量与内存: 在保证视觉效果的前提下,尽量选择压缩比最高的格式。

在 Unity 中,你可以通过选择纹理图片,在 Inspector 窗口中设置 Texture TypeSprite (2D and UI),然后在 Platform Specific Overrides 中针对不同平台设置不同的压缩格式。

4. 动态图集与静态图集

尽管 Sprite Packer 是自动打包,但我们仍然可以从逻辑上区分动态图集和静态图集。

  • 静态图集:
    • 定义: 指那些在游戏运行过程中内容不会改变,或者变化非常少的图集。例如,主界面的通用图标、按钮背景、HUD 元素等。
    • 优势: 一次加载,永久使用,内存开销稳定。
    • 管理: 将所有相关的静态 Sprite 都放在一个或几个大的图集中,通过 Packing Tag 来区分。
  • 动态图集:
    • 定义: 指那些内容会根据游戏进度、玩家选择等动态加载和卸载的图集。例如,某个特定副本的怪物头像、装备图标、特定任务的 UI 元素等。
    • 优势: 按需加载,减少初次加载时间,节省内存。
    • 管理: 为不同模块、不同场景的动态 UI 元素创建独立的图集。当某个模块不再使用时,可以卸载对应的图集资源。
    • 注意事项: 频繁加载和卸载图集本身也会有性能开销,需要权衡。可以考虑使用 AssetBundleAddressables 来管理动态图集的加载和卸载。

规划建议:

  • 将所有在游戏中频繁出现、通用性强的 UI 元素(如通用按钮、通用图标、背景、通用字体)打包到一个或几个大的 “公共图集” 中。
  • 针对特定模块或场景(如战斗界面、背包界面、商店界面),将只在该模块或场景中使用的 UI 元素打包成独立的 “模块图集”
  • 避免将无关的 Sprite 打包到同一个图集中,这可能导致图集过大或无法有效卸载。

三、字体优化

字体在 UGUI 中也扮演着重要角色,其渲染方式也会影响性能。

1. 字体的渲染原理

当你在 UGUI 中使用 TextTextMeshPro 组件时,字体字符实际上也是以纹理和 Mesh 的形式被渲染的。

  • 字体纹理(Font Atlas): 每个字符都会被渲染到一张纹理上,这张纹理就是字体图集(Font Atlas)。当需要显示某个字符时,UGUI 会从这张字体图集中获取对应字符的 UV 信息,并将其绘制到屏幕上。
  • Mesh 生成: 每个字符都会被转换成四边形 Mesh,这些 Mesh 包含了字符的形状信息。当文本内容发生变化时,对应的 Mesh 需要重新生成。
2. 动态字体与静态字体(TextMeshPro 的优势)
a. 动态字体(Dynamic Font)
  • 原理: Unity 默认的 Text 组件通常使用动态字体。当你导入一个 .ttf.otf 字体文件时,Unity 会在运行时根据需要动态生成字符纹理和 Mesh。这意味着只有当某个字符被用到时,它才会被加入到字体图集中。
  • 优势: 初始包体较小,因为不需要预生成所有字符纹理。
  • 缺点:
    • 运行时开销: 第一次使用某个字符时,需要实时渲染并生成其纹理,这会产生一定的 CPU 开销。如果文本内容频繁变化且包含大量新字符,这种开销会累积。
    • 字体图集扩展: 随着使用的字符越来越多,字体图集会不断扩展,如果扩展次数过多,可能导致 Draw Call 增加或内存碎片。
    • 渲染质量: 默认 Text 组件的渲染质量通常不如 TextMeshPro
b. 静态字体(Pre-generated Font Atlas / SDF Font)
  • 原理: TextMeshPro(简称 TMP)是 Unity 推荐的文本解决方案,它采用 SDF(Signed Distance Field,有符号距离场) 技术。在使用 TMP 时,我们通常会预先生成一个包含所有常用字符的字体图集(Font Atlas)。
  • 优势:
    • 高质量渲染: SDF 字体在放大或缩小时依然保持清晰,没有锯齿感,渲染效果远优于传统动态字体。
    • 性能稳定: 字体图集在游戏启动时一次性加载,运行时无需动态生成字符纹理,避免了额外的 CPU 开销和字体图集扩展问题。
    • 更少的 Draw Call: TMP 会尝试将所有使用相同字体和材质的文本合并成一个 Draw Call。
    • 丰富的文本效果: TMP 内置了描边、阴影、渐变等多种文本效果,且性能开销小。
  • 缺点:
    • 包体增大: 预生成的字体图集会增加游戏包体大小。
    • 初次加载: 字体图集越大,初次加载时间越长。

建议: 强烈推荐使用 TextMeshPro 来处理所有文本显示。它的优点远远超过缺点。对于一些极端需要控制包体大小的场景,可以考虑只打包常用的字符集。

3. 字体 Atlas 的生成与管理 (TextMeshPro)

当你使用 TextMeshPro 时,字体 Atlas 的管理变得尤为重要。

a. 生成字体 Atlas:
  1. 导入字体: 将你的字体文件(.ttf 或 .otf)导入 Unity 项目。
  2. 创建字体 Asset: 选中字体文件,右键 Create > TextMeshPro > Font Asset
  3. 配置字体 Asset:
    • 在生成的 Font Asset 文件上,点击 Open Font Asset Creator 按钮。
    • Source Font: 你的字体文件。
    • Font Size: 用于生成字体图集时采样的字体大小,越大生成的图集质量越高,但图集占用空间越大。通常 90-128 足够。
    • Padding: 字符之间的填充距离,用于防止字符边缘锯齿和裁剪。
    • Atlas Resolution: 字体图集的尺寸,通常选择 2048x2048 或 4096x4096。
    • Character Set: 选择要包含的字符集。
      • ASCII: 仅包含基本英文字符。
      • Extended ASCII: 包含更多欧洲语言字符。
      • Unicode Hex Range: 自定义 Unicode 范围,适用于特定语言字符。
      • Custom Characters: 手动输入字符。
      • Characters From File: 从文本文件加载字符列表。
      • 最常用的是 Characters From File 准备一个包含游戏中所有可能用到的中文字符的文本文件,然后导入。这能最大程度地压缩字体图集大小,同时保证所有字符可用。
    • Render Mode: 通常选择 Distance Field (SDF) 以获得最佳效果。
    • 生成: 点击 Generate Font Atlas 按钮,然后保存生成的 Font Asset。
b. 字体 Atlas 的管理:
  • 字符集管理: 最重要的优化是控制字体 Atlas 中的字符数量。只包含游戏中实际会用到的字符,而不是全部字符。
    • 对于中文游戏,需要收集所有文本内容,提取出唯一的字符,然后生成一个字符列表文件。
    • 对于多语言游戏,为每种语言或语言组生成独立的 Font Asset,按需加载。
  • 复用 Font Asset: 确保所有使用相同字体的 TextMeshPro 组件都引用同一个 Font Asset,这样才能最大程度地实现合批。
  • 优化图集尺寸: 在保证清晰度的情况下,选择最小的 Atlas Resolution
  • 多个 Font Asset: 如果游戏中有多种风格差异很大的字体,或者某种字体只在特定场景使用,可以创建多个 Font Asset,并按需加载。例如,标题字体一个 Asset,正文字体一个 Asset。

四、图片资源优化

除了图集,单个图片资源的优化也至关重要,它们是构建 UI 的基本块。

1. 图片格式与压缩

这部分与图集优化中的图片格式和压缩原理相同,但针对的是那些不适合打包成图集或作为 RawImage 使用的独立图片。

  • 纹理类型 (Texture Type):
    • Sprite (2D and UI): 用于 UI Image 组件中的 Sprite
    • Texture: 用于 RawImage 组件或 3D 模型的纹理。
  • Read/Write Enabled:
    • 默认情况下,Read/Write Enabled 是关闭的。这意味着 CPU 无法直接访问纹理数据,从而节省内存。
    • 除非你需要在运行时通过脚本读写纹理像素(例如生成截图、进行像素级操作),否则务必保持 Read/Write Enabled 为关闭状态。 开启它会使纹理在内存中保留一份 CPU 可读副本,导致内存占用翻倍。
  • Generate Mip Maps:
    • Mip Maps 是纹理的不同分辨率副本,用于在物体距离摄像机较远时使用低分辨率的纹理,从而提高渲染效率和消除摩尔纹。
    • 对于 UGUI 纹理,通常不需要 Mip Maps UI 元素通常是 2D 的,且不会因为距离变化而显著缩小。开启 Mip Maps 会增加 33% 的内存占用。因此,在 Sprite 和 UI 纹理的 Import Settings 中,请 禁用 Generate Mip Maps
  • Filter Mode:
    • Point (No Filter):最近邻采样,像素化效果,用于像素艺术。
    • Bilinear:双线性过滤,平滑过渡,用于大多数 UI。
    • Trilinear:三线性过滤,在 Mip Maps 之间平滑过渡,但 UI 不开 Mip Maps,所以选择 Bilinear 即可。
2. 图片尺寸与分辨率的合理设置

图片尺寸是影响内存占用和渲染性能的另一个关键因素。

  • 最小化尺寸: 图片尺寸应该 刚好满足 UI 元素在屏幕上显示的最高分辨率要求。不要使用过大的图片,然后让 Unity 缩放。例如,如果一个按钮图标在 UI 中最大显示为 64x64 像素,那么其原始图片尺寸就应该是 64x64,而不是 256x256。
    • 计算方式: 考虑 UI 在不同分辨率设备上的缩放。如果你使用的是 Canvas ScalerScale With Screen Size 模式,你需要根据你设置的 Reference Resolution 和目标分辨率来计算实际渲染尺寸。
  • 避免非 2 的幂次方: 尽管现代 GPU 对非 2 的幂次方纹理支持良好,但对于一些旧设备或特定压缩格式,使用 2 的幂次方尺寸(如 128x128, 256x256)仍然是更安全的做法,并且可能在内部处理上更高效。
  • 统一分辨率: 尽量在美术资源导出时就统一好图片的分辨率。例如,如果你的基准分辨率是 1920x1080,那么所有 UI 元素都应该根据这个分辨率来设计其最佳显示尺寸。
  • LOD(Level of Detail)for UI? 尽管 Unity 有 LOD 系统,但它主要用于 3D 模型。对于 UGUI,通过控制图片尺寸和图集来达到类似的目的更为实际。例如,对于需要放大的 UI,提供更高分辨率的图集;对于缩小或背景元素,可以使用较低分辨率的图集。
3. 避免使用未经优化的图片资源
  • 美术规范: 与美术团队建立良好的沟通,让他们了解性能优化的要求。
    • 导出格式: 优先导出 PNG(带 Alpha)或 JPG(无 Alpha)。
    • 裁剪透明像素: 确保图片边缘没有多余的透明像素,这会增加不必要的内存占用和 Draw Call。在 Photoshop 中使用 TrimCrop 功能。
    • 统一尺寸: 如果是系列图标或按钮,尽量保持其导出尺寸统一,便于图集打包。
  • 检查图片冗余: 项目中是否存在多余的、未使用的图片资源?使用 Unity 的 Editor 扩展或插件来检测并删除它们。
  • 利用 .psd 导入: Unity 可以直接导入 .psd 文件,并将其切割为 Sprite。这对于美术迭代非常方便,但要确保最终导出到游戏中的图片是经过优化的。在导入 .psd 文件后,通常需要调整其 Import Settings 以应用合适的压缩。

五、Batching(合批)原理与优化

合批是 UGUI 渲染优化的核心,直接影响 Draw Call 数量。

1. 合批条件

Unity 的 UGUI 合批机制主要依赖于以下几个条件:

  • 相同 Canvas: 只有在同一个 Canvas 下的 UI 元素才可能进行合批。
  • 相同 Material: 这是最核心的条件。所有参与合批的 UI 元素必须使用 完全相同的 Material 实例
    • 如果 UI 元素的 Material 属性不同,或者即使材质文件相同但参数被修改导致生成了不同的材质实例,都无法合批。
    • Image 组件的 Color 属性通常不会破坏合批,因为颜色是通过顶点颜色传递给 Shader 的。
  • 相同 Texture: 如果 Material 中引用了纹理,那么这些纹理也必须是相同的。
    • 这就是为什么图集如此重要的原因:图集中的所有 Sprite 都共享同一个大纹理,从而满足这个条件。
  • 渲染顺序: UI 元素的渲染顺序也至关重要。如果两个可以合批的元素之间插入了一个无法合批的元素,那么合批就会被中断。
    • Z 轴顺序: UGUI 的渲染是基于 Z 轴(Order in Layer, Rect Transform 的 Z 坐标)和 Hierarchy 中的顺序。越靠后的 UI 元素越靠前渲染。
    • 透明与不透明: 透明元素和不透明元素的渲染批次是分开的。通常不透明元素先渲染,透明元素后渲染。将透明度高的 UI 元素(如半透明背景)放在不透明元素之后,可以提高合批效率。
2. 合批的种类
a. Dynamic Batching(动态合批)
  • 原理: Unity 会在 CPU 上将满足合批条件的小型 Mesh 合并成一个更大的 Mesh,然后一次性提交给 GPU。
  • UGLI 中的表现: UGUI 的合批机制就是 Dynamic Batching 的一种特殊形式。
  • 限制:
    • 合并的顶点数量限制(通常为 300-900 左右,具体取决于 Unity 版本和平台)。如果合并后的 Mesh 顶点数量超过这个限制,就会分成多个批次。
    • Mesh 属性:如果 Mesh 的法线、切线、UV0 以外的 UV 通道、顶点颜色等属性不同,也可能无法合批。但 UGUI 的 Mesh 通常比较简单,很少会遇到这些限制。
b. Static Batching(静态合批)
  • 原理: 在构建游戏时,将标记为静态的对象合并成一个或几个大 Mesh。
  • UGUI 中适用性: 不适用于 UGUI。 UGUI 元素通常是动态的(需要响应交互、动画等),不适合标记为 Static。将 UI 元素标记为 Static 可能导致意外行为或无法进行合批。
3. 如何通过合理组织 UI 元素来促进合批

减少 Draw Call 的关键在于尽可能让更多的 UI 元素满足合批条件。

  • 统一 Material:

    • 确保所有需要合批的 UI 元素使用相同的 Material。默认的 UGUI ImageText 组件都使用 UI/Default Shader 和 Material。
    • 如果你自定义了 UI Shader,确保使用该 Shader 的所有 UI 元素都使用同一个 Material 实例。
  • 使用 Sprite Atlas: 这是最关键的一步,保证所有 Image 组件引用来自同一个图集的 Sprite

  • 调整 UI 层级与渲染顺序:

    • 将能合批的元素放在一起: 在 Hierarchy 窗口中,将那些可以合批的 UI 元素(例如,同一张图集的不同图标)尽量放在同一个 Canvas 下,并且在层级上尽可能靠近。
    • 避免交叉: 如果 A、B、C 三个 UI 元素,A 和 C 可以合批,B 无法合批。如果层级是 A -> B -> C,那么 A 和 C 就无法合批,会产生两个 Draw Call。理想的层级应该是 A -> C -> B,这样 A 和 C 就可以合批,只产生一个 Draw Call。
    • 透明度:
      • 避免半透明与不透明 UI 元素交错: 通常,不透明的 UI 元素先渲染,半透明的 UI 元素后渲染。如果它们交错排列,会导致 Draw Call 频繁切换,从而打断合批。
      • 最佳实践: 将所有不透明的 UI 元素放在一个层级或 Canvas 下,然后将所有半透明的 UI 元素放在另一个层级或 Canvas 下。
  • Canvas 的切割与分层(下篇会详细讲解):

    • 将一个大 Canvas 切割成多个小 Canvas,可以更精细地控制 UI 元素的重建范围。
    • 同时,分层后的 Canvas 也更容易进行 Draw Call 的优化,因为每个 Canvas 都可以独立地进行合批。
  • 减少 Mask 组件的使用:

    • Mask 组件(包括 Rect Mask 2D)会打断合批。因为 Mask 会修改渲染状态(裁剪范围),导致其内部和外部的元素无法合批。
    • 尽量减少 Mask 的使用,或者只在必要的地方使用。对于简单的裁剪需求,可以考虑使用 ImageTypeFilledSliced 来实现。
  • 善用 Unity Profiler 和 Frame Debugger:

    • Profiler: 在 Profiler 的 CPU UsageGPU Usage 模块中,你可以看到 UI.Render 的开销,以及 Draw Call 的数量。
    • Frame Debugger: 这是分析 Draw Call 和合批情况的利器。
      • 打开 Window > Analysis > Frame Debugger
      • 在 Frame Debugger 中,你可以一步步查看每个 Draw Call 渲染了哪些对象,以及 Draw Call 为什么被中断(例如 Material changed, Shader changed, Texture changed 等)。通过分析 Frame Debugger,你可以准确找出导致 Draw Call 增加的原因,并有针对性地进行优化。

六、总结与展望

本篇文章我们深入探讨了 UGUI 渲染的基础原理,并详细讲解了如何从 资源管理 的角度进行优化,包括:

  • 理解 UGUI 的 渲染管线Draw Call 的概念。
  • 通过 Sprite Atlas (图集) 大幅减少 Draw Call,并学会选择合适的打包方式(推荐 Sprite Packer)和纹理压缩格式。
  • 强调 TextMeshPro 在字体渲染上的巨大优势,以及如何优化其 字体 Atlas
  • 学会优化 图片资源 的尺寸、格式和导入设置,避免不必要的内存开销。
  • 深入理解 合批(Batching) 的条件,并掌握通过合理组织 UI 元素来促进合批的方法。
  • 学会使用 Unity ProfilerFrame Debugger 来分析和定位 Draw Call 问题。

这些基础知识和优化策略是 UGUI 性能优化的基石。掌握它们,你就能有效地减少游戏在 UI 渲染上的性能开销。

在下一篇文章中,我们将进一步深入,聚焦于 Canvas 的重建机制,以及如何通过 Canvas 分层UI 元素管理 来实现更高级别的性能优化。

UGUI 性能优化系列:第一篇——基础优化与资源管理

UGUI 性能优化系列:第二篇——Canvas 与 UI 元素管理


网站公告

今日签到

点亮在社区的每一天
去签到