ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cocos 纹理格式与压缩纹理:选型、配置与内存优化实战

Cocos 纹理格式与压缩纹理:选型、配置与内存优化实战 纹理格式和压缩纹理这两个词在很多 Cocos 项目里都是那种平时没人管、出事全来找的东西。项目顺风顺水的时候美术丢什么图进去都能显示谁也不会去翻图片导入设置等到包体从 30M 涨到 200M、低端机一进战斗就闪退、或者某个模型贴图在 Blender 里好端端的到了引擎里直接报错大家才回头翻这些参数。我这些年接过的调优需求里纹理相关的占比一直不低包体瘦身、内存压降、加载提速、透明边缘发黑、图集花屏几乎都绕不开纹理格式和压缩纹理这两块。这篇就把我踩过的坑、算过的账、配过的参数一次性摊开讲清楚纹理格式到底是什么、未压缩和压缩各自适合什么场景、Cocos Creator 里从导入到构建该怎么配、出问题从哪查。不管你是刚接触资源管线的新手还是想给项目做一次系统性瘦身的老手都能顺着往下抄作业。1. 从一次包体超标说起纹理到底吃掉了多少内存1.1 一个真实的内存账本先说个我印象很深的案例。之前接手的休闲项目玩法不复杂就是几个 3D 模型加一堆 UI结果打出来的安卓包接近 180M冷启动进主界面要七八秒低端机上只要开个带粒子的大招就有概率闪退。美术那边信誓旦旦说模型才十几兆程序也查了代码没毛病。最后用引擎自带的资源分析工具一扒发现大头全在纹理上——尤其是那些 1024 甚至 2048 的 UI 大图和模型贴图导入时是默认的 RGBA8888一张没压缩。这就是问题的核心模型网格本身很轻顶点和索引加起来可能就几百 KB真正重的是它身上那张贴图。一张 2048×2048 的贴图如果按 RGBA8888 存每个像素 4 字节算下来是 2048×2048×4 16,777,216 字节也就是 16M。十几张贴图叠加显存和内存双双爆掉闪退一点都不冤枉。所以调优纹理的第一步从来不是去调参数而是先把账算明白知道钱花在哪了。1.2 不压缩纹理的代价为什么必须做纹理压缩有人会说现在手机内存这么大16M 而已。这里有个容易被忽略的点纹理在运行时要上传到 GPU 显存而且 mipmap 会再叠加约三分之一的内存占用图集又是把多张小图拼成一张大图拼完的尺寸可能是 4096×4096。真正在设备上占用的显存往往是你以为的两三倍。中低端安卓机的显存本来就紧张纹理一块超了最先扛不住的就是 GPU表现出来就是掉帧、黑屏、闪退。纹理压缩要解决的正是这个矛盾。它做的事情说白了就是把每个像素占的字节数压下来让同样的画质占用更少的内存带宽。这里要特别强调一句纹理压缩不是把 PNG 压小那么简单。PNG、JPG 是磁盘存储格式到了运行时引擎会把它们解码成原始像素而 ETC、ASTC 这类压缩纹理格式是 GPU 能直接读取、不需要完整解码的硬件压缩格式。前者省的是包体后者省的是内存和带宽两者是两件事这也是为什么单独配了 PNG 压缩运行内存却纹丝不动的原因。1.3 为什么这篇值得从头看一遍我做优化有个习惯先把什么是格式、什么是容器、什么是压缩算法这三层概念分清楚因为很多撞墙的问题都源于概念混淆。比如有人把 KTX 当成一种压缩格式其实它只是个容器里面装的是 ETC 还是 ASTC 要看具体配置有人以为选了 ASTC 就一定比 ETC2 好结果低端机直接不支持。这些坑我都会在后面拆开讲先把地基打牢后面的配置和排查才不会变成玄学。2. 先看懂基础未压缩纹理格式和它们的取舍2.1 RGBA8888、RGB888、RGBA4444、RGB565 差在哪未压缩纹理格式的选择本质上是在色彩精度和内存占用之间做交易。最常见的几种我按占用从大到小排一下。RGBA8888 是四通道各 8 位每像素 4 字节画质最保真带完整 alpha是引擎导入图片的默认格式也是内存消耗最大的。RGB888 去掉了 alpha 通道每像素 3 字节适合不需要透明的场景比如一些不带透明信息的背景图。再往下是 RGBA4444 和 RGB565都是每像素 2 字节。RGBA4444 四个通道各 4 位能保留 alpha 但色彩过渡会出现比较明显的色带尤其渐变背景上肉眼可见。RGB565 把 5 位给红色、6 位给绿色、5 位给蓝色没有 alpha绿色精度高一点是因为人眼对绿色更敏感。这两种格式在早期的 UI 小图、色块类资源上很常见胜在省内存但绝不能用在有柔和渐变的图上。2.2 位深、通道和 alpha 精度选型真正要看的东西很多人选格式只看名字其实要拆成三个维度来想。第一个是位深也就是每个通道用多少位表示8 位是常态4 位就会开始出现可感知的精度损失。第二个是通道数要不要 alpha、alpha 是不是只有 0 和 1 两种状态这决定了能不能砍掉通道。第三个是 alpha 精度像 UI 图标这种边缘需要平滑过渡的4 位 alpha 会看到锯齿而剪影类只需要透明和不透明两种状态的图其实可以走专门的单通道 alpha方案。我一般的判断逻辑是这样有半透明渐变或者柔和阴影的图老老实实用 RGBA8888 或者对应的带 alpha 压缩格式纯色块、按钮底图这种没有渐变又不需要透明的可以考虑 RGB565只有硬边透明比如怪物剪影的alpha 只有 0/1理论上可以单独抽成一张位图来用不少成熟项目就是靠这招把内存再压一截的。2.3 内存占用计算公式顺手就能估公式很简单单张纹理内存 宽 × 高 × 每像素字节数。开启 mipmap 后再乘以约 1.33。图集的话先看最终的图集尺寸再套公式。举个例子一张 1024×1024 的 RGBA8888 纹理是 1024×1024×4 4M转成 RGB565 就是 2M换成 ETC1 这种每像素 0.5 字节的压缩格式直接降到 512K。差距有多大一算就知道。这里有个实操心得做优化之前先用资源分析工具导出一张纹理内存排行表按占用从高到低排。你会发现往往前 10 张图占了 70% 的内存优先处理这几张效果立竿见影比一股脑全改配置稳妥得多。改配置这种全局操作风险高容易牵连到不该动的资源定点爆破才是正确姿势。3. 压缩纹理的家族谱ETC、PVRTC、ASTC、DXT 怎么选3.1 各平台 GPU 支持的格式矩阵压缩纹理最麻烦的地方在于它跟 GPU 硬件强绑定不同平台支持的格式不一样。Android 阵营里ETC1 是 OpenGL ES 2.0 时就强制支持的误差小、兼容好但只有 RGB 没有 alphaETC2 是 ES 3.0 引入的支持 RGBA但老设备不一定认ASTC 是较新的格式压缩率灵活中高端机型支持得好老机型翻车。iOS 那边老一点的时候主打 PVRTC后来苹果设备从 A8 之后也开始支持 ASTC。桌面端和一部分 PC 平台则是 DXT也叫 S3TC/BC 系列的天下BC1、BC3、BC7 各有分工。这张矩阵必须记在脑子里因为选错格式的后果不是画质差一点而是直接读不出来、显示成花屏或者纯黑。一般项目为了兼容性会在 Android 上准备 ETC1/ETC2 两套iOS 上准备 PVRTC 和 ASTC构建时按设备能力选择。具体怎么在 Cocos 里配这套多平台方案第 4 节会详细展开。格式典型平台每像素位深是否带 Alpha备注ETC1Android 低端4 bpp否ES 2.0 强制支持兼容性最好ETC2Android 中高端4/8 bpp是ES 3.0 起老机需降级PVRTCiOS 旧设备2/4 bpp是4bpp 版苹果生态传统格式ASTCiOS/Android 新机0.89~8 bpp 可调是块大小可调压缩灵活BC1/BC3PC/桌面4/8 bppBC3 带DXT 系桌面 GPU 主流3.2 ETC1 和 ETC2 的 alpha 怎么处理ETC1 不支持 alpha这是它最经典的痛点。早年的做法是ETC1 单独一张 alpha 图就是把 RGB 存一张 ETC1把透明信息单独抽成一张灰度图也存 ETC1运行时在 shader 里把两张合起来。这套方案省内存但资源数量翻倍、shader 也要改管线复杂度上去了新项目基本不建议这么干。ETC2 的 RGBA 版本就省心多了一个文件搞定代价是每像素 8 位内存是 ETC1 的两倍。我个人的建议是如果项目最低适配到 ES 3.0 以上直接用 ETC2 RGBA别折腾双图方案如果还要守 ES 2.0 的老设备那就只能回到 ETC1 加 alpha 图的老路或者用 PVRTC 兜底。这里没有银弹取决于你的最低适配机型这个决策一定要在项目初期定下来后期再改会牵动整条资源管线。3.3 ASTC 为什么越来越香但它也有代价ASTC 最大的优势是块大小可调从 4×4 到 12×12 随你选块越大压缩率越高、画质越糊。4×4 是每像素 8 位6×6 大概是 3.56 位8×8 降到 2 位。你可以针对不同用途的图选不同块大小UI 图和人脸贴图用 4×4 或 6×6 保画质远景、地面这种大面积重复纹理用 8×8 甚至更大。这种粒度控制是 ETC、PVRTC 给不了的也是 ASTC 现在被力推的原因。但代价也实在。第一老设备不支持有些低端安卓机直接读不了 ASTC 纹理得准备降级方案。第二ASTC 的压缩计算量大导入和构建时处理慢资源多的项目构包时间会明显变长。第三块大小选大了画质下降会集中在边缘和文字上UI 上的小字特别容易糊成一团。所以我的做法是把 ASTC 留给中高端机型和 3D 场景资源UI 文字类还是慎用大块或者干脆单独用图集加未压缩格式。3.4 块压缩原理4×4 到底压了些什么不理解块压缩你永远想不通为什么压缩纹理会糊、会在边缘出现诡异的色块。像 ETC、ASTC、BC 这类都属于块压缩思路是把图像切成固定大小的小块比如 4×4 个像素每个块只存一份基色和几个端点块内所有像素用这些端点做插值还原。好处是解压快、GPU 硬件直接支持坏处是同一个块内只能表达有限种颜色一旦块里颜色变化剧烈比如文字边缘、细线条、噪点插值就还原不出来糊掉或者出现杂色。这也是为什么选块大小要跟内容挂钩。颜色平坦、渐变柔和的区域大块也还原得不错细节密集的区域块越小越好。贴图在 Blender 里看着没问题到了引擎里糊成一片很多时候就是因为这类细节丰富的贴图被套上了压缩率过高的格式或者尺寸处理不当。这个坑第 5 节还会细说。4. Cocos Creator 里配置压缩纹理的完整流程4.1 图片导入设置逐项拆解在 Cocos Creator 里点中一张图片资源Inspector 面板会列出它的导入属性这里几个参数直接决定后续内存占用。Type决定它是普通纹理、精灵帧还是图集的一部分Premultiply Alpha关系到透明边缘的处理后面会单独讲。真正关键的是压缩相关的那组设置通常挂在资源自己的配置里可以在这里针对单张图覆盖项目级别的默认预设。我一般优先在这里处理那些占用排行前列的大图因为覆盖面小、风险可控。这里有个操作习惯值得养成给资源定一套命名和目录规范比如ui_前缀的走 UI 通道、model_前缀的走 3D 通道然后利用构建时的通配规则批量套不同的压缩预设。这样新增资源的时候只要丢对目录、起对名字压缩格式自动就对了不会出现某个人漏配一张大图的情况。规范带来的收益在几十上百张图的规模上非常明显。4.2 构建面板里的纹理压缩配置项目级的压缩纹理策略在 Cocos Creator 3.x 里是通过项目设置里的纹理压缩预设preset来管理的。一个 preset 里可以针对不同平台配置不同的格式组合比如 Android 配一组 ETC1 加 ETC2iOS 配一组 PVRTC 加 ASTC。构建发布面板里再指定当前这次构建用哪个 preset或者按平台细分。配置时我建议至少准备两档一档走兼容优先格式保守、覆盖老设备一档走效果优先用 ASTC 把画质和内存都压下来。发布前根据目标渠道和用户机型分布来选。别小看这一步很多项目上线后收到部分机型黑屏的反馈追根溯源就是压缩格式没做多档降级一台老设备读不了新格式就直接白屏了。{ name: custom-preset, android: { default: etc2, etc1: true }, ios: { default: astc_6x6, pvrtc_4bit: true } }上面这种配置的大意是给两个平台分别指定主格式和备选格式实际字段以你使用的引擎版本为准不同版本命名会有出入配的时候对着官方说明核对一遍别照抄。4.3 自定义压缩纹理配置怎么落地预设配好之后还需要让资源跟预设挂上钩。常见做法是在图片资源上指定使用哪个压缩预设没指定的就落到项目默认。这一步容易出问题的地方是同一张图被不同 preset 引用了不同格式构建时到底听谁的一定要理清楚优先级——一般是资源自身设置 目录规则 项目默认。理不顺优先级就会出现我明明改了预设构建出来还是老格式这种抓狂情况。我踩过最典型的一个坑是图集。图集里的散图如果各自配了不同格式图集打包后到底用哪个格式是有讲究的往往以图集整体的配置为准散图的设置被忽略。所以图集资源我一般统一在图集层面配散图不单独设省得互相打架。这个细节很多教程不会提但实际项目里图集又是重灾区值得单独记一笔。4.4 用代码动态控制纹理的过滤和环绕除了构建期的格式运行期的采样方式也影响观感。Cocos 里可以拿到 Texture2D用它提供的方法去设过滤方式和环绕模式。过滤方式上NEAREST是最近邻采样像素风游戏常用放大不糊但边缘硬LINEAR是线性插值普通贴图用过渡柔和。如果做的是像素风用默认的线性过滤会把锐利的像素边缘糊掉必须手动切到最近邻。import { Texture2D } from cc; const tex imageAsset.getGFXTexture(); tex.setFilters(Texture2D.Filter.NEAREST, Texture2D.Filter.NEAREST); tex.setWrapMode(Texture2D.WrapMode.CLAMP_TO_EDGE, Texture2D.WrapMode.CLAMP_TO_EDGE);环绕模式上平铺背景要用REPEAT普通 UI 和模型贴图用CLAMP_TO_EDGE防止边缘出现接缝。这些设置跟压缩格式是不冲突的两件事但新手容易把它们混为一谈结果排查方向都找错了。记住格式管的是数据怎么存过滤管的是数据怎么采样。5. 踩坑实录从 Blender 正常到引擎里报错的那些事5.1 常见问题速查表先上一张我整理的速查表遇到问题先对号入座再往下看详细分析。现象可能原因排查方向贴图在 Blender 正常引擎里报错尺寸非 2 的幂、含 ICC 色彩描述、格式不支持导出时转成 2 的幂、去掉色彩描述透明边缘出现黑边/白边Premultiply Alpha 处理不一致统一预乘设置或改 shader 混合压缩后明显糊、色块块大小过大、细节区域被强压该图换小尺寸块或走未压缩部分机型黑屏/花屏压缩格式不被该 GPU 支持增加格式降级多档兼容图集显示错位、串图图集尺寸图集配置冲突检查图集整体压缩设置平铺背景出现缝Clamp 与 Repeat 用反平铺资源改 REPEAT5.2 尺寸不是 2 的幂是报错的高发区从 Blender 导出贴图到引擎里报错排第一名的原因往往是贴图尺寸不是 2 的幂。很多 GPU 对纹理尺寸是有偏好的非 2 的幂NPOT纹理在部分设备或部分格式下会采样异常压缩格式更是几乎都要求尺寸对齐到块的边界。Blender 里导出默认可能是按模型 UV 自动算的尺寸或者中途被裁剪成了 1000×1000 这种不规整的数到了引擎这边压缩处理时就会出问题。解决办法很直接导出前把贴图统一转成 2 的幂尺寸比如 512、1024、2048或者直接在引擎的导入设置里勾选自动转换。我的经验是资源管线里加一道尺寸规整的检查比事后一个个排查高效得多。这也呼应了前面说的规范问题管线里的一道自动检查能省下无数次加班。5.3 Premultiply Alpha 和透明边缘的那点事透明边缘发黑或者发白是我被问得最多的问题之一。根源在于 alpha 的预乘处理。简单说像素的 RGB 和 alpha 在混合时有一个先乘还是先合的顺序问题做 3D 时模型贴图一般用直通 alpha做 UI 和粒子时为了混合正确往往用预乘 alpha。如果一张图导出时预乘了导入引擎时又说没预乘边缘就会算错出现黑边或者白边。排查思路是先确认这张图是给 3D 用还是给 UI 用然后统一管线的预乘约定要么全部预乘、shader 里按预乘处理要么全部不预乘。最忌讳的是一半预乘一半不预乘那样怎么调都调不对。这个坑最隐蔽的地方在于它在编辑器预览时可能不明显等到真机、或者放到不同背景色上才暴露所以透明边缘的验证一定要放到真实场景里做。5.4 色彩描述文件和格式转换的隐形雷另一个容易忽略的点是图片自带的色彩描述文件ICC profile。有些设计软件导出的图带了色彩描述引擎那边的解析器不一定认轻则颜色偏差重则直接解析失败报错。这个坑的排查成本很高因为图在本地看图软件里一切正常只有引擎打不开。遇到图没问题但引擎报错可以先用工具把图的色彩描述去掉、转成普通 sRGB 再试。还有一类是格式本身不被支持比如你直接把一张 KTX 或别的引擎专属格式丢进 Cocos它认不出来。引擎能读的是它能解析的源格式压缩要在导入或构建阶段由引擎去生成不是你把已经压好的文件放进去就行。搞清楚源文件格式和目标压缩格式这两个概念的区别能避开一大半莫名其妙的报错。5.5 图集、九宫格和压缩的相互干扰图集是个好东西能减少 DrawCall但它和压缩纹理放一起时容易互相添乱。图集是把很多小图拼成一张大图拼完之后整张图用统一的压缩格式。如果里面混了画质敏感的小图标和可以狠压的背景块就只能迁就最敏感的那个压缩收益打折扣。而且图集尺寸往往很大一旦压缩格式选大了块图集里的小字和小图标会糊得最明显。九宫格图更特殊它需要图像边缘的像素精确压缩带来的边缘插值误差可能让拉伸后的边框出现色差或断续。我的做法是把九宫格图、文字相关的图集单独拆出来走未压缩或者轻压缩其他不敏感的大图该狠狠压就压。分类处理比一刀切强太多这也是资源管线需要一定复杂度的原因——没有一套配置能通吃所有资源。6. 效果验证与性能度量别拍脑袋说优化好了6.1 怎么确认纹理真的被压缩了配完压缩不等于生效一定要验证。方法有几种一是构建后的产物目录里看生成的纹理文件是不是变成了对应压缩格式的产物比如生成了 ktx 之类的中间文件二是在真机上用引擎的渲染调试工具查看当前纹理的实际内存占用和格式三是做个对比工程同场景分别在压缩前后跑一遍对比内存曲线。我最推荐第一种配合真机查看因为构建产物骗不了人真机数据才是最终结论。有个细节要提醒编辑器里预览时有些压缩格式是不生效的或者用的是简化处理所以你必须在真机上、用构建后的包来验证。只盯着编辑器看很容易得到改了没效果或者明明生效了的错误结论。这条经验是我早期反复吃亏换来的编辑器环境和真机环境在纹理处理上真的两码事。6.2 内存和包体的对比实测拿一组我实操过的数据来说明收益。同一个 3D 场景模型贴图大约 20 张平均尺寸 1024。优化前全部 RGBA8888光这些贴图的显存占用大约 80M加上 mipmap 接近 107M。把其中不需要透明的大图改成 ETC1、需要透明的改成 ETC2、少数画质敏感的保留原格式之后这部分显存降到约 30M 上下包体也从接近 180M 缩到 90M 出头。画质上只有个别渐变色块在仔细对比时能看出轻微色带正常游玩几乎无感。但我要诚实地说压缩不是没有代价。文字、细线、深色渐变这几类资源最容易在压缩后暴露问题所以我的策略是分层处理而不是全量压。所谓优化本质是在各项指标之间找平衡点而不是把某个指标压到极限。一味贪图内存小最后画质崩了、返工反而更亏。6.3 画质和体积的平衡点怎么找平衡点没有标准答案得实测。我的流程是这样先选一批有代表性的场景和机型定好画质底线比如用户能明显看出糊就算不合格然后从最激进的压缩配置开始逐步放宽到画质可接受为止最后拿用户机型分布做加权看整体收益。别只在高配机上测低配机才是压缩格式兼容问题的高发地。另外一个实用技巧是给不同分辨率档位准备不同的资源策略。高端机用高质量格式、低端机用高压缩格式按设备能力动态加载。这套做下来管线会复杂一些但收益明显——既照顾了低端机的流畅度又没牺牲高端机的观感。值不值得做取决于你的用户机型分布如果低端机占比很高那这套分层方案几乎是必须的。7. 几个容易被忽略的进阶细节7.1 纹理和渲染效果的联动聊到纹理就不得不提渲染。像做游戏迷雾这种效果很多时候要在 shader 里采样一张噪声纹理或者遮罩纹理来驱动可见性。这类纹理对采样方式特别敏感如果遮罩被强压缩边缘会出现块状伪影迷雾边界就会一坨一坨的过滤方式如果用了最近邻边缘又会有明显的锯齿。所以做效果类纹理时格式选择要和效果本身一起考虑不能等效果做完了再回头配格式。我一般的做法是给效果类纹理单独建一个目录用低压缩或未压缩格式尺寸也未必需要很大256 或 512 就够用因为这类纹理通常是模糊的、规律性的。把它和普通贴图区分开既保证了效果稳定又不会因为一张小图拖累整体内存预算。7.2 动态图、序列帧的纹理陷阱序列帧动画很容易把纹理内存吃光因为它动辄几十上百张图。如果每张都是 RGBA8888内存分分钟爆炸。这类资源合适的做法是先拼图集再对图集整体压缩同时控制单帧尺寸。序列帧里如果有透明背景还要注意前面说的 alpha 处理。我见过有项目一段几秒的特效序列帧光这一块就占了上百兆显存最后是靠控制帧尺寸加压缩才救回来。7.3 构建时间和压缩格式的权衡最后提一个经常被忽略的点压缩格式越复杂、块大小越小、要生成的平台变体越多构建时间就越长。ASTC 加上多平台降级构包时间翻倍是常事。如果你的项目迭代频繁、每天要出好几个包构包慢是很影响效率的。我的建议是把压缩配置和构建流程解耦日常开发迭代用轻量配置快速出包只有发布候选版本才启用完整的多平台压缩。这样既不影响开发效率又能保证上线包的质量。8. 我在实际项目里沉淀下来的几条经验纹理格式和压缩纹理这件事做到最后拼的不是技术门槛而是规范和执行。技术原理就那么些难的是让整条资源管线稳定运行让每个人都按规范来让每一张图都有明确的归属和格式策略。我见过效果最好的团队往往不是用了多高级的格式而是把基础的尺寸规范、命名规范、分类压缩做得很扎实。还有一点心得是关于全局改配置这件事的。新手特别容易一上来就把项目默认格式全改掉期待一劳永逸。但渲染资源千差万别一刀切必然出问题。正确的姿势是先用数据找到大头定点处理控制影响范围每改一批就真机验证一批。慢一点但稳。踩过这么多坑之后我给自己定了一条铁律任何纹理相关的改动都必须经过真机验证编辑器里说了不算。编辑器环境太温柔了很多格式问题在真机上才现原形。这条规矩帮我挡掉了无数次上线事故。你如果刚开始做资源优化也建议把这条刻进工作流里能省很多事。
返回列表