Skip to content

Stage03|Runtime Objects & Assets ​

Lesson033|Texture、SpriteFrame、Atlas 为什么不是同一个对象? ​

Tags: #Creator2.x #Stage03 #Texture #SpriteFrame #Atlas #MemoryDifficulty: ⭐⭐⭐☆☆


真实问题:换掉一个图标,为什么整张图集都还在内存 ​

背包有 100 个图标,全部打进 items.plist。页面只显示其中 10 个,开发者认为“只用了 10 张小图”。Profiler 却显示一张大 Texture 常驻;把 10 个 Sprite 的 spriteFrame 清空后,显存也没有立刻下降。

要解释它,必须分清三层对象:

text
Atlas:多张小图的组织与区域描述
→ SpriteFrame:其中一个矩形区域
→ Texture2D:真正承载像素并上传 GPU 的纹理

Sprite 显示一枚图标,并不等于 GPU 只保留那一小块像素。只要某个 SpriteFrame 依赖整张 Atlas Texture,纹理层通常以整张纹理为内存单位。

一、本课目标 ​

图片资源常被统称为“图片”,但在 Creator 运行时至少要区分:

text
Texture
SpriteFrame
Atlas
Sprite 组件

本课帮助你理解它们的职责、引用关系和内存影响。


二、Texture 是什么 ​

Texture 更接近 GPU 可采样的图像数据:

text
像素数据
尺寸
格式
采样信息
GPU 纹理对象

它关心的是“整张图像如何被存储和采样”。一张 Texture 可以被多个 SpriteFrame 使用。


三、SpriteFrame 是什么 ​

SpriteFrame 更像“从 Texture 中取哪一块并如何显示”:

text
SpriteFrame
├── Texture 引用
├── 矩形区域
├── 偏移和原始尺寸
└── 旋转 / 图集信息

例如图集中有 100 个图标:

text
icons.png → 一个 Texture
icon_001 → 一个 SpriteFrame
icon_002 → 一个 SpriteFrame

SpriteFrame 不一定拥有独立完整像素数据,它可能只引用 Texture 的一块区域。


四、Atlas 是什么 ​

Atlas 通常表示把多个小图合并到一张大纹理及其区域描述中:

text
Atlas
├── Texture
├── icon_001 SpriteFrame
├── icon_002 SpriteFrame
└── icon_003 SpriteFrame

它可以减少纹理切换和帮助合批,但也可能增加:

  • 单张纹理尺寸。
  • 内存常驻范围。
  • 某个小图导致整张大图加载。

五、Sprite 组件使用什么 ​

ts
this.sprite.spriteFrame = frame;

Sprite 组件通常使用 SpriteFrame,而 SpriteFrame 再引用 Texture:

text
Sprite Component
    ↓
SpriteFrame
    ↓
Texture / Atlas

因此替换 SpriteFrame 引用和替换底层 Texture 是不同操作。


六、为什么 SpriteFrame 共享有价值 ​

text
100 个 Sprite
    ↓ 使用
同一个 SpriteFrame
    ↓ 引用
同一张 Texture

可以减少重复资源加载和纹理切换,有助于批处理。但如果 100 个图标只需要其中一个,整张大 Atlas 仍可能被加载到内存。

资源组织需要在复用、合批和内存粒度之间平衡。


七、纹理内存不等于文件大小 ​

磁盘上的 PNG 可能压缩得很小,运行时上传 GPU 后通常按纹理格式和尺寸占用内存:

text
磁盘压缩大小 ≠ 解码后内存 ≠ GPU 显存占用

例如透明 RGBA 纹理可能按每像素 4 字节估算,再考虑 mipmap、对齐和平台格式。

所以性能分析不能只看资源管理器中的文件大小。


八、案例:按钮图标替换 ​

ts
this.icon.spriteFrame = this.selectedFrame;

这里通常只改变 Sprite 组件指向的 SpriteFrame,不需要重新创建 Node,也不需要重新加载同一张 Texture(如果已在缓存中)。

但如果 selectedFrame 来自另一张大图集,可能触发:

  • 新纹理加载。
  • 材质或纹理状态变化。
  • 合批变化。
  • GPU 内存峰值。

九、常见误区 ​

误区一:SpriteFrame 就是一张独立图片 ​

它可能只是 Texture 的一个区域描述。

误区二:PNG 文件小,运行时内存就小 ​

解码和 GPU 格式可能远大于磁盘压缩大小。

误区三:Atlas 越大越好 ​

大图集有利于复用和合批,但会扩大加载和内存粒度。

误区四:修改 SpriteFrame 就会修改所有 Sprite ​

通常修改某个 Sprite 组件的引用,不会改变其他组件的引用。


十、练习与答案 ​

练习 ​

  1. Texture 和 SpriteFrame 的职责有什么不同?
  2. 为什么 Atlas 可以帮助合批?
  3. 为什么大 Atlas 可能造成内存浪费?
  4. 文件大小和 GPU 内存为什么不是一回事?

参考答案 ​

  1. Texture 保存可采样图像数据,SpriteFrame 描述 Texture 中的显示区域和相关信息。
  2. 多个 Sprite 可以在同一纹理上取不同区域,减少纹理切换。
  3. 使用一个小图标可能需要加载整张大纹理。
  4. 文件可能压缩,运行时还要解码、上传并按 GPU 格式占用内存。

从源文件到 Sprite 的逐层数据 ​

假设图集纹理是 2048×2048 RGBA8888:

text
磁盘 PNG:可能只有 1.2 MB
解码像素:2048 × 2048 × 4
理论基础级:约 16 MB
加上 mipmap:约再增加 1/3
GPU 对齐、缓存和副本:依平台而异

SpriteFrame 自己主要描述:

  • 在 Texture 中的矩形区域;
  • 是否旋转;
  • 原始尺寸、裁剪偏移;
  • 边框等切片信息;
  • 对底层 Texture 的引用。

它通常不会复制一份完整像素。因此多个 SpriteFrame 共用 Texture 可以减少重复纹理,但任何一个 Frame 活跃都可能让整张 Texture 保持。

Creator 2.4.x 编辑器实验:观察对象层次 ​

所有图片导入、Atlas 配置和组件绑定通过 Creator 编辑器完成。不要手工修改 .meta、.plist、.prefab 或生成目录。

实验准备 ​

  1. 在 assets/resources/texture-lab/ 导入两张小 PNG。
  2. 在资源管理器中展开图片,观察 Texture 与 SpriteFrame 入口。
  3. 创建两个 Sprite,分别静态绑定图片。
  4. 创建 TextureLayerProbe.ts。
ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class TextureLayerProbe extends cc.Component {
    @property(cc.Sprite)
    target: cc.Sprite = null;

    loadFrame() {
        cc.resources.load(
            'texture-lab/icon-a',
            cc.SpriteFrame,
            (err, frame: cc.SpriteFrame) => {
                if (err) {
                    cc.error(err);
                    return;
                }

                const texture = frame.getTexture();
                cc.log(
                    '[texture-lab]',
                    'frame=', frame.name,
                    'texture=', texture && texture.name,
                    'size=', texture && texture.width,
                    texture && texture.height
                );
                this.target.spriteFrame = frame;
            }
        );
    }
}

实验 A:从 Frame 找到底层 Texture ​

运行 loadFrame。预期 Frame 和 Texture 是不同对象,Frame 能通过 getTexture() 找到它依赖的纹理。

实验 B:两个 Sprite 共用 Frame ​

把同一个 Frame 赋给两个 Sprite,输出:

ts
cc.log(
    spriteA.spriteFrame === spriteB.spriteFrame,
    spriteA.spriteFrame.getTexture()
        === spriteB.spriteFrame.getTexture()
);

预期二者可以共享同一个 Frame 和 Texture,不会因为有两个 Sprite 自动复制两份像素。

实验 C:Auto Atlas ​

通过 Creator 编辑器为实验目录配置 Auto Atlas,预览或构建后观察合图效果、SpriteFrame 区域与 DrawCall。具体生效阶段和预览方式以 2.4.x 当前编辑器为准。

记录:

  • 两个 Frame 是否指向同一 Atlas Texture;
  • Atlas 尺寸;
  • 合批是否改善;
  • 只显示一个 Frame 时整张 Texture 的估算内存。

UV 为什么属于 SpriteFrame ​

Texture 是一张完整二维像素表。SpriteFrame 的矩形区域会转成 UV 范围:

text
像素矩形 x/y/width/height
→ 除以 Texture 宽高
→ 得到 0~1 范围 UV
→ Sprite 顶点通过 UV 采样图集局部

所以“换 SpriteFrame”可能只改变 UV 与尺寸数据,而不更换 Texture;这种情况下更容易保持合批。若换到另一张 Texture,即使材质相同,也可能打断批次。

Atlas 不是越大越好 ​

大图集的收益:

  • 更多 Sprite 共享 Texture;
  • 减少纹理切换;
  • 提高合批机会;
  • 减少零散请求。

代价:

  • 只用一个小图也可能加载整张大纹理;
  • 更新一个资源可能使整个图集版本变化;
  • 超过设备纹理限制会失败或降级;
  • 大纹理解码和上传造成峰值;
  • 不同生命周期资源绑在一起难释放;
  • 空白和打包布局可能浪费面积。

合理分组更看重生命周期和使用场景:

text
同一页面同时出现
生命周期相近
材质/采样设置兼容
更新频率相近
平台尺寸限制允许

真实项目故障:低端机打开背包直接闪退 ​

现象 ​

资源包中的 PNG 总大小只有十几 MB,但打开背包时进程内存快速上升并被系统终止。

根因 ​

text
多张压缩 PNG 同时读取
→ 解码为 RGBA 像素
→ Auto Atlas 大纹理创建
→ CPU 解码缓冲与 GPU 纹理短时并存
→ 峰值远大于磁盘文件大小

修复方向 ​

  • 按页面和生命周期拆分 Atlas;
  • 使用平台支持的合适压缩纹理格式;
  • 控制最大纹理尺寸;
  • 分阶段加载,避免同时解码;
  • 关闭页面后按所有权释放;
  • 用真机峰值验证,不用 PNG 文件大小估算内存。

实验失败诊断 ​

Frame.getTexture() 为空 ​

确认加载成功、类型确为 SpriteFrame、资源导入完成,并检查 Frame 是否已初始化。输出完整 err,不能用类型断言掩盖返回对象。

Atlas 没有减少 DrawCall ​

图集只统一 Texture;材质、混合状态、Mask、渲染顺序、组件类型等仍可能打断合批。L049~L051 会系统分析。

图片变模糊或边缘渗色 ​

检查 Filter、Wrap、压缩格式、Atlas 间距、旋转/裁剪和缩放,不要直接把问题归因于 SpriteFrame。

版本边界与练习答案 ​

Creator 2.4.x 中图片导入、Auto Atlas 和 Asset Manager 都参与这条链。Creator 3.x 的模块 API 和渲染数据结构不同,但 Texture、区域描述和 Sprite 使用者的分层模型仍成立。

  1. 512×512 RGBA8888 未压缩基础级约多少内存? 512 × 512 × 4 = 1,048,576 字节,约 1 MiB;mipmap、对齐和平台副本另算。
  2. 为什么同图集 20 个 Frame 不等于 20 份纹理? Frame 主要保存区域信息并共同引用 Texture。
  3. 为什么只显示一个 Frame 仍可能占整张 Atlas 的纹理内存? GPU 分配单位是底层 Texture,不会只上传某个 Frame 的矩形。
  4. 图集分组首要依据是什么? 同屏使用和生命周期相近,其次再考虑合批、更新频率、尺寸限制和采样设置。

十一、本课总结 ​

text
Texture 是图像数据。
SpriteFrame 是区域和显示描述。
Atlas 组织多个 SpriteFrame 和底层纹理。
Sprite 组件通常持有 SpriteFrame 引用。
资源复用、合批和内存粒度需要综合权衡。

十二、下一课预告 ​

text
Lesson034|Asset Bundle、远程资源、CDN 与版本管理