Skip to content

Lesson 051|Dynamic Atlas 如何降低 DrawCall? ​

Tags: #Creator2.x #Stage04 #DynamicAtlas #TextureAtlas #DrawCallDifficulty: ⭐⭐⭐⭐☆


真实问题:50 张零散小图开启动态图集后,DrawCall 为什么没降 ​

商城从服务器配置中加载 50 张本地小图,每张都是独立 Texture。团队开启 Dynamic Atlas,预期全部合批,结果指标几乎不变。

可能原因包括:

  • 图片尺寸/格式不符合动态图集条件;
  • 已经属于静态 Atlas 或不允许打入;
  • 材质、Mask 和顺序仍不兼容;
  • 图集页满而产生多页;
  • 资源在测量前还未进入预期路径;
  • 真正瓶颈不是 Texture 切换。

Dynamic Atlas 只尝试统一 Texture,不会消除其他状态边界。

🎯 本课目标 ​

本课理解 Dynamic Atlas 解决的问题、适用条件和代价:

运行时把多个小纹理整理到共享纹理中,减少纹理切换和拆批机会。


二、静态图集和动态图集 ​

text
静态图集:编辑器或构建阶段提前打包
动态图集:运行时根据资源加入共享 Atlas

静态图集可预测、稳定;动态图集更灵活,但运行时需要额外管理和复制像素。


三、动态图集的高层流程 ​

text
SpriteFrame 第一次使用
    ↓
检查是否适合加入 Dynamic Atlas
    ↓
分配 Atlas 区域
    ↓
复制或上传纹理像素
    ↓
生成新的纹理区域引用
    ↓
后续对象共享 Atlas Texture

加入后,SpriteFrame 的 UV 可能指向动态图集中的新区域。

假设 A、B 原来各自占一张 64×64 Texture:

text
A UV 0~1 → Texture A
B UV 0~1 → Texture B

动态图集分配一张更大的 Atlas 后:

text
A 像素复制到 Atlas 左上区域
B 像素复制到 Atlas 另一块区域
A、B 的采样 UV 改为各自在 Atlas 中的子矩形

显示内容没变,但两者现在绑定同一张 Atlas Texture,于是“纹理不同”这个批次边界可能消失。


四、它为什么可能降低 DrawCall ​

text
原来:A、B、C 使用三张 Texture
    → 多次纹理绑定

加入动态图集后:
A、B、C 共享 Atlas Texture
    → 更容易合批

但最终仍受 Material、Render State 和排序影响。


五、动态图集的代价 ​

  • 首次加入有 CPU 和纹理复制成本。
  • Atlas 有容量限制。
  • 纹理尺寸和显存可能增加。
  • 资源生命周期被共享纹理绑定。
  • 某些纹理格式、尺寸或组件不适合加入。
  • 运行时结果可能受加载顺序影响。

因此 Dynamic Atlas 是运行时策略,不是无条件打开就一定更快。

收益和成本发生在不同时间:

text
首次出现
→ 资格判断、区域分配、像素复制、UV 更新

稳定绘制
→ 共享 Texture,可能减少切换和 DrawCall

资源退出
→ Frame 与图集页的生命周期、碎片和回收问题

如果页面只停留很短时间,首次合图成本可能还没来得及被稳定帧收益摊薄;如果页面长时间滚动,大量小图的持续合批收益可能更有价值。


六、适合和不适合的资源 ​

更适合:

  • 大量小尺寸 UI 图标。
  • 生命周期相近的 SpriteFrame。
  • 材质相同的普通 2D 图片。

需要谨慎:

  • 超大纹理。
  • 特殊压缩格式。
  • 需要独立释放的资源。
  • 高频变化或特殊 Shader 对象。
  • 本身已经在静态图集中的资源。

具体限制以 Creator 2.x 版本实现为准。


七、案例:动态列表图标 ​

商城列表中图标来自多个小资源:

text
初始加载:图标分散在多张纹理
滚动使用:运行时逐渐加入 Dynamic Atlas
后续绘制:更多图标共享 Atlas

需要测量:

  • 首次滚动是否出现尖峰。
  • Atlas 显存是否超预算。
  • 场景切换后是否正确释放。
  • DrawCall 是否真的下降。

还要区分“资源加载顺序”和“最终资源集合”。动态页可能按首次使用顺序分配空间,不同用户路径造成不同图集页布局与碎片。测试时应覆盖冷启动、常见滚动路径和页面反复进入,而不是只测一次已经预热的编辑器预览。


八、常见误区 ​

误区一:动态图集没有运行时成本 ​

首次加入需要分配、复制和更新纹理。

误区二:所有 Sprite 都能进入动态图集 ​

尺寸、格式、组件和引擎限制都会影响结果。

误区三:加入动态图集后一定只有一个 DrawCall ​

材质、状态和顺序仍可能拆批。

误区四:动态图集越大越好 ​

过大的 Atlas 会增加显存和生命周期耦合。



九、深入推导:动态合图的运行时过程 ​

高层模型:

text
零散 SpriteFrame 首次参与渲染
→ 判断尺寸、格式与配置是否允许
→ 在动态图集页寻找空间
→ 复制像素到 Atlas Texture
→ 为 Frame 建立图集区域/UV
→ 后续 Sprite 采样 Atlas
→ 相邻兼容对象获得合批机会

这会带来运行时复制、图集页内存和管理成本。它是以运行时工作换取后续提交优化。

进入动态图集后,Sprite 看到的内容没有变,但采样位置变了:

text
原来 UV:指向独立 Texture 的 0~1 区域
加入图集后:指向 Atlas Texture 中分配到的小矩形

运行时通常不需要每帧重新复制图片。主要复制和区域建立发生在进入图集时,后续绘制复用新的 Atlas 区域;但图集页容量、Frame 生命周期、重置和平台实现仍由具体 Creator 版本决定。

这也解释了它为什么只解决纹理绑定问题:UV 可以改到同一 Atlas,Material、Blend、Mask、Camera 和透明顺序却不会因此自动相同。

十、Creator 2.4.x 实验:开关前后做控制变量 ​

通过 Creator 编辑器的项目设置配置 Dynamic Atlas。不要直接修改引擎缓存、导入产物或私有图集数据。

准备 ​

  1. 导入 30 张尺寸较小的独立 PNG,不做静态 Atlas。
  2. 创建 30 个相邻 Sprite,确保默认材质和无 Mask。
  3. 创建另一组相同 Sprite,但交替插入 Label/Mask,作为对照。
  4. 在 Project Settings 中确认 Dynamic Atlas 设置。

DynamicAtlasProbe.ts ​

ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class DynamicAtlasProbe extends cc.Component {
    @property(cc.Node)
    compatibleGroup: cc.Node = null;

    @property(cc.Node)
    interruptedGroup: cc.Node = null;

    showCompatible() {
        this.compatibleGroup.active = true;
        this.interruptedGroup.active = false;
    }

    showInterrupted() {
        this.compatibleGroup.active = false;
        this.interruptedGroup.active = true;
    }
}

实验步骤 ​

  1. 关闭 Dynamic Atlas,重启预览并记录两组 DrawCall。
  2. 开启后重启预览,等待资源首次显示,再记录。
  3. 比较第一次出现帧与稳定帧,观察运行时合图是否有首帧成本。
  4. 增大图片到不适合合图的尺寸,再比较。
  5. 保持动态图集开启,但插入不同材质/Mask,验证其他边界仍存在。

修改项目设置后应按 Creator 工作流重新预览/构建,不能假设运行中热切换完全生效。

十一、静态 Atlas、Dynamic Atlas 与独立纹理怎么选 ​

静态 Atlas ​

适合内容稳定、同场景使用、可在构建前规划的 UI。优点是结果可控、无运行时复制;代价是更新粒度和生命周期绑定。

Dynamic Atlas ​

适合许多小而零散、运行时才组合的 Sprite。优点是减少手工规划;代价是首帧复制、页管理、条件限制和可观测性。

保持独立 ​

大图、频繁更新的纹理、特殊采样、RenderTexture 或生命周期完全不同的内容,可能不适合合图。

十二、真实项目故障:打开背包首帧卡,之后很流畅 ​

现象 ​

开启 Dynamic Atlas 后稳定 DrawCall 显著下降,但首次打开背包卡顿增加。

根因 ​

text
大量小图同帧首次显示
→ 同帧发生纹理解码/上传
→ 动态图集分配与像素复制
→ RenderData/UV 更新
→ 首帧尖峰

优化 ​

  • 分批创建可见 Item;
  • 在合适加载阶段预热必要图标;
  • 常用稳定资源改为静态 Atlas;
  • 限制动态图集候选和页面尺寸;
  • 避免为了稳定帧牺牲不可接受的首帧;
  • 同时记录首次与后续帧。

十三、图集页为什么也会浪费 ​

若小图尺寸差异大或生命周期杂乱:

text
Atlas Page 产生碎片
→ 一些 Frame 不再使用
→ Page 因其他 Frame 仍活跃不能释放
→ 运行时纹理占用高于预期

Dynamic Atlas 不是资源释放器。它甚至可能把不同业务资源绑定到同一运行时页,需要结合版本行为理解回收策略。

十四、故障诊断 ​

开启后 DrawCall 不变 ​

先验证候选是否进入图集,再检查材质、状态、顺序和 Camera。不要仅凭开关状态断言生效。

图片边缘异常 ​

检查 padding、Filter、纹理格式、动态图集复制条件和缩放。

内存上升 ​

统计原 Texture、Atlas Page、CPU 副本和页面生命周期。减少 DrawCall 可能用更多图集内存换来。

十五、练习、推导答案与版本边界 ​

Dynamic Atlas 的资格、页面尺寸、调试方式和平台行为高度依赖 Creator 2.4.x 小版本。配置以当前编辑器为准,业务不访问私有 manager 数据结构。

  1. Dynamic Atlas 降低 DrawCall 的必要但不充分条件是什么? 多个 Sprite 最终共享 Atlas Texture;材质、状态、顺序等也要兼容。
  2. 为什么首帧可能更慢? 首次需要分配图集页、复制像素并更新 UV/渲染数据。
  3. 大背景为何通常不适合? 占据巨大页空间、复制成本高,且本身往往没有与大量小图合批的收益。
  4. 如何选择静态与动态 Atlas? 根据资源稳定性、同屏关系、生命周期、首帧预算、更新发布粒度和实测指标。

能力验收 ​

你应该能画出零散纹理进入 Dynamic Atlas 的运行时过程,解释它为什么只解决纹理兼容而不解决其他批次边界,并能同时比较首次合图成本、稳定帧收益、图集页内存和资源生命周期。

本课总结 ​

text
Dynamic Atlas 通过共享纹理提高合批机会。
它不是免费优化,首次加入和显存管理都有成本。
最终收益必须用 DrawCall、帧时间和内存实测。

十一、下一课预告 ​

text
Lesson052|Mask、Graphics、Spine、Particle 的特殊渲染成本