外观
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 → 一个 SpriteFrameSpriteFrame 不一定拥有独立完整像素数据,它可能只引用 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 组件的引用,不会改变其他组件的引用。
十、练习与答案
练习
- Texture 和 SpriteFrame 的职责有什么不同?
- 为什么 Atlas 可以帮助合批?
- 为什么大 Atlas 可能造成内存浪费?
- 文件大小和 GPU 内存为什么不是一回事?
参考答案
- Texture 保存可采样图像数据,SpriteFrame 描述 Texture 中的显示区域和相关信息。
- 多个 Sprite 可以在同一纹理上取不同区域,减少纹理切换。
- 使用一个小图标可能需要加载整张大纹理。
- 文件可能压缩,运行时还要解码、上传并按 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或生成目录。
实验准备
- 在
assets/resources/texture-lab/导入两张小 PNG。 - 在资源管理器中展开图片,观察 Texture 与 SpriteFrame 入口。
- 创建两个 Sprite,分别静态绑定图片。
- 创建
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 使用者的分层模型仍成立。
- 512×512 RGBA8888 未压缩基础级约多少内存?
512 × 512 × 4 = 1,048,576字节,约 1 MiB;mipmap、对齐和平台副本另算。 - 为什么同图集 20 个 Frame 不等于 20 份纹理? Frame 主要保存区域信息并共同引用 Texture。
- 为什么只显示一个 Frame 仍可能占整张 Atlas 的纹理内存? GPU 分配单位是底层 Texture,不会只上传某个 Frame 的矩形。
- 图集分组首要依据是什么? 同屏使用和生命周期相近,其次再考虑合批、更新频率、尺寸限制和采样设置。
十一、本课总结
text
Texture 是图像数据。
SpriteFrame 是区域和显示描述。
Atlas 组织多个 SpriteFrame 和底层纹理。
Sprite 组件通常持有 SpriteFrame 引用。
资源复用、合批和内存粒度需要综合权衡。十二、下一课预告
text
Lesson034|Asset Bundle、远程资源、CDN 与版本管理