十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Unity游戏优化必知:纹理压缩原理、ASTC/ETC2选型与批处理实践

Unity游戏优化必知:纹理压缩原理、ASTC/ETC2选型与批处理实践 做游戏开发的朋友应该都有类似体验项目做了一两个月包体悄悄变大游戏启动时加载越来越慢到了真机上显存告急、掉帧频发。检查来检查去DrawCall 压了、Shader 优化了、LOD 也做了最后才发现罪魁祸首往往是纹理资源。纹理是游戏画面里体积最大、数量最多的资源之一。一张 2048×2048 的 RGBA 32 位贴图原始数据就有 16MB一个场景里几十张贴图光显存占用就是几百 MB。纹理压缩就是在这样的背景下必须掌握的一项基本功。本文会围绕纹理压缩的原理、主流格式、Unity 工程里的实战配置和批处理脚本展开帮助你理清“什么时候该用哪种压缩格式”“为什么内存会不降反升”“ASTC 和 ETC2 到底怎么选”这些问题。适合做 Unity 客户端、移动端游戏、或者刚接触资源优化的开发者阅读。1. 为什么要关注纹理压缩1.1 一个容易被忽视的性能瓶颈很多初学 Unity 的开发者会把注意力集中在 Shader 和脚本逻辑上很少关注纹理本身的存储形态。但纹理在游戏运行时的影响是系统性的包体大小纹理资源是安装包体积的主要来源之一。未压缩的 2048 纹理一张就是十几 MB稍微多一点美术资源包体立刻膨胀。加载速度纹理数据越大从磁盘读取、解压、上传到 GPU 的时间就越长。首次进入场景的卡顿很大概率是纹理加载引起的。内存和显存占用运行时 RGBA 原始纹理需要占用大量内存。移动端 GPU 和内存本身就很紧张纹理一旦失控掉帧和闪退几乎是必然的。带宽压力即使纹理已经上传到 GPU绘制时 GPU 仍需要从显存中取数据。未压缩纹理的带宽占用远高于压缩纹理会影响填充率和整体帧率。所以纹理压缩并不只是一个“省包体”的操作它同时影响 CPU、内存、显存、带宽和加载速度五个维度。1.2 纹理压缩解决什么问题纹理压缩解决的问题非常直接把纹理数据在存储和传输阶段压缩同时在显存里保持 GPU 可以直接采样的形式。它的目标不是“让图片看起来更小”而是减少包体中的资源体积。减少运行时显存占用。减少 GPU 采样时的带宽消耗。保证 GPU 可以在绘制时直接读取压缩后的数据并完成实时解码。这里的关键点是“GPU 直接采样”。普通的图片压缩格式比如 PNG、JPG在 GPU 中是不能直接使用的。它们需要先被 CPU 解压还原成 RGBA 像素再上传到显存。而 GPU 纹理压缩格式例如 BC 系列、ETC、ASTC可以直接放在显存中由 GPU 的专用硬件解码单元在采样时实时解压。这正是纹理压缩和普通图片压缩最本质的区别。1.3 纹理压缩不是“把图片压成 JPG”这里需要先做一个概念区分存储压缩PNG 无损耗、JPG 有损耗都是为了减小文件体积。它们适合 UI、图集、美术原图这些“需要再编辑”的场景。GPU 纹理压缩BC1、BC3、BC7、ETC2、ASTC 等格式除了减小文件体积更重要的是让 GPU 能在渲染时直接采样不需要先解压成完整 RGBA。在 Unity 中Texture import 面板里的 Compression 设置就是 GPU 纹理压缩。它与我们平时用 PhotoShop 导出 Web 格式不是一回事。2. 纹理压缩的核心原理2.1 分块压缩与调色板思想GPU 纹理压缩的主流方案基本都是基于“分块”的思想。简单理解把一张纹理切分成固定大小的像素块例如 4×4 像素为一个块。每个块内部记录一个基准颜色、一个颜色范围然后块内的每个像素根据基准颜色和范围插值出近似颜色。这样就不需要为每个像素单独存储 RGBA 值。这种思路和调色板有些相似一块区域里颜色变化通常不会太大用少量颜色信息加上插值规则就能表达大部分像素。比如 BC1 格式一个 4×4 的块只占 64 位8 字节数据。如果没有压缩这 16 个像素用 RGBA 32 位表示是 64 字节压缩比达到 8:1。2.2 压缩格式的固定块大小不同纹理压缩格式的区别主要在于块大小、颜色精度、是否支持 Alpha、是否有四分量独立压缩等。常见的格式块大小格式块大小压缩比RGBA8 基准质量特点BC14×464 bit8:1不支持高质量 Alpha适合不透明贴图BC34×4128 bit4:1支持 Alpha适合法线、透贴BC74×4128 bit4:1高质量支持高动态范围适合高质量贴图ETC24×4128 bit4:1Android 兼容性强支持 AlphaASTC 4x44×4128 bit4:1高质量移动端最强ASTC 8x88×8128 bit16:1压缩比大适合背景、远景这里的压缩比是相对的具体视觉效果还需要结合纹理内容判断。ASTC 的灵活性在于块可以从 4×4 到 12×12 任意选择开发者能在质量和体积之间做非常细的调节。2.3 GPU 硬件解码与 CPU 解压的区别前面提到GPU 纹理压缩可以直接被硬件采样。这里再稍微展开一下使用 PNG、JPG 这类图片格式时Unity 会在加载阶段通过 CPU 解压把图片转换成 RGBA 原始数据然后通过 API 上传到显存。这个过程既费 CPU又占额外的临时内存。使用 DXT、ETC2、ASTC 等格式时纹理在磁盘上就是 GPU 压缩后的数据。加载到显存后GPU 在 Fragment Shader 采样时会通过纹理单元的硬件解码器实时解压不占用 CPU 计算资源也不需要维持一份完整的 RGBA 转存。所以纹理压缩不仅仅是节省存储空间它真正改变了纹理资源在运行时的生命周期。3. 主流纹理压缩格式对比与选型3.1 DXT/BC 系列PC 老牌压缩格式DXT 系列是 DirectX 时代的纹理压缩格式后来在 GPU 业界统一扩展为 BCBlock Compression命名。BC1对应 DXT1适合不透明的颜色贴图。BC3对应 DXT5具备独立的 Alpha 通道适合普通贴图和透贴。BC5适合法线贴图两个通道精度更高。BC7高质量格式用于高质量纹理PC 和 Xbox 平台支持度高。在 PC 端开发时BC 系列是默认选择。Unity 的 Standalone 平台默认纹理格式通常是 BC1/BC3BC7 则在高压缩质量项目中很常见。3.2 ETC 系列Android 高清纹理的主流ETC1 是安卓平台早年常用的格式但它不支持 Alpha 通道所以当时开发者常常把一张纹理拆成颜色贴图和 Alpha 贴图两张来处理带来不少麻烦。ETC2 是 OpenGL ES 3.0 之后强制支持的格式兼容性和表现都比 ETC1 好很多支持 Alpha压缩质量也更高。Unity 打包 Android 时如果设备支持 OpenGL ES 3.0默认就会使用 ETC2。但 ETC2 也有局限压缩质量相对粗糙在颜色渐变、高对比度区域容易出现色块和噪点。对于要求比较高的贴图ETC2 不一定够用。3.3 ASTC移动端全能型选手ASTCAdaptive Scalable Texture Compression是 ARM 推出的纹理压缩格式支持从 4×4 到 12×12 的块尺寸兼容度极高。它的优势非常明显单格式适配 Android 和 iOS无需分别导出两套资源。块尺寸可调质量与体积之间能灵活平衡。支持 RGBA、HDR 甚至 3D 纹理。在相同压缩比下画质通常优于 ETC2 和 BC 系列。现在主流的移动设备基本都支持 ASTC。Unity 在 Android 平台推荐的纹理压缩格式就是 ASTC。3.4 格式选择速查表目标平台推荐格式说明Android 主流设备ASTC 6x6 / ASTC 8x8平衡画质与体积Android 老设备ETC2兼容性兜底iOSASTC 4x4 / 6x6支持良好PC StandaloneBC7高质量/ BC1普通根据项目画质选择UI 图集RGBA32 / RGBA16 也可必要时使用 ETC2/ASTCUI 对画质较敏感谨慎压缩需要说明的是这里的格式选择要结合项目实际设备池决定。如果主打 iOS 平台ASTC 是首选如果面向低端安卓机可能需要用 ETC2 或者同时保留多套资源。4. 实战环境准备4.1 使用 Unity 作为示例纹理压缩在不同的引擎中配置方式不同但最直观、最常见的还是在 Unity 中配置。所以本文以 Unity 为例讲解纹理导入、平台覆盖和脚本批处理。需要说明的是Unity 版本和 API 在持续变化。本文示例代码基于 Unity 2021 LTS / 2022 LTS 时代的常用 API新版本如果遇到 API 调整请以项目实际版本的帮助文档为准。4.2 准备测试纹理建议准备一张 2048×2048 的 RGBA 贴图和一张带有透明通道的表情贴图放入项目Assets/Textures/目录下。测试纹理可以来自美术资源也可以自己创建一张带渐变色、文字、网格线的测试图。有文字和网格线更容易发现压缩后的锯齿和色块问题。4.3 项目目录结构为了配合后面的批处理脚本建议目录结构如下Assets/ ├── Editor/ │ └── TextureCompressionBatchTool.cs └── Textures/ ├── character_color.png └── item_icon.pngEditor目录下的脚本只会影响编辑器不会打包进最终项目。5. Unity 纹理导入与平台设置5.1 单张纹理的设置路径在 Unity 中选中一张纹理后可以在 Inspector 面板看到 Texture Import 设置Texture Type纹理类型选择 Texture、Sprite、Normal Map 等。Max Size最大导入尺寸例如 2048、1024。Format平台纹理格式例如 RGBA32、Compressed、ASTC 等。CompressionFaster / Normal / Best Quality 的滑块影响压缩速度和质量。Use Crunch Compression是否使用 Crunch 压缩适合优化包体大小但加载时会解压到其他 GPU 格式。对单张纹理做平台设置时最直观的方法是切换到平台覆盖面板在右边选择 Standalone、iOS、Android 等平台分别设置。5.2 按平台覆盖Standalone、iOS、Android默认情况下一张纹理在 PC、Android、iOS 三个平台可能使用不同的格式Standalone 可以选 BC7 或 BC1。Android 选 ETC2 或 ASTC。iOS 选 ASTC 4x4 或 6x6。在 Inspector 中点击“Override for Android”可以单独设置安卓平台的 Max Size 和压缩格式。这个能力很关键因为美术资源往往以 PC 画质为标准制作移动端需要降规格。5.3 Mipmap、图集与贴图尺寸Mipmap用于场景中随距离变化而逐渐降低分辨率的纹理。如果不开启远处的纹理会出现闪烁和摩尔纹但如果用不到又不关会额外增加约三分之一的显存占用。图集小纹理尽量合并到图集减少 DrawCall。Max Size根据贴图实际用途设置。UI 大图一般 2048 够用普通场景贴图建议不超过 1024。这里需要提醒的是Mipmap 和纹理压缩是两件事不要混为一谈。Mipmap 解决的是采样细节问题纹理压缩解决的是数据体积问题。6. 脚本批处理纹理压缩格式6.1 为什么需要批处理脚本项目美术资源通常有成百上千张纹理如果靠手动在 Inspector 里逐张点击修改效率太低也容易漏。更合理的做法是写一个 Editor 脚本在导入后统一设置平台覆盖并在 CI 或提交前批量校验。下面我们写一个TextureCompressionBatchTool编辑器工具它会扫描Assets/Textures目录下的所有纹理并给 Android 平台设置 ASTC 6x6。6.2 创建 Editor 工具脚本新建文件Assets/Editor/TextureCompressionBatchTool.cs粘贴以下代码using UnityEditor; using UnityEngine; public static class TextureCompressionBatchTool { private const string TextureRoot Assets/Textures; private const string AndroidPlatform Android; private const string IOSPlatform iPhone; [MenuItem(Tools/Texture/Apply Android ASTC)] public static void ApplyAndroidASTC() { string[] guids AssetDatabase.FindAssets(t:Texture, new[] { TextureRoot }); int changedCount 0; foreach (string guid in guids) { string path AssetDatabase.GUIDToAssetPath(guid); TextureImporter importer AssetImporter.GetAtPath(path) as TextureImporter; if (importer null) { continue; } bool dirty false; TextureImporterPlatformSettings androidSettings importer.GetPlatformTextureSettings(AndroidPlatform); if (androidSettings.format ! TextureImporterFormat.ASTC_6x6) { androidSettings.overridden true; androidSettings.format TextureImporterFormat.ASTC_6x6; importer.SetPlatformTextureSettings(androidSettings); dirty true; } if (dirty) { importer.SaveAndReimport(); changedCount; } } Debug.Log($[TextureCompressionBatchTool] 扫描纹理 {guids.Length} 张修改 {changedCount} 张。); } [MenuItem(Tools/Texture/Print Texture Info)] public static void PrintTextureInfo() { string[] guids AssetDatabase.FindAssets(t:Texture, new[] { TextureRoot }); foreach (string guid in guids) { string path AssetDatabase.GUIDToAssetPath(guid); TextureImporter importer AssetImporter.GetAtPath(path) as TextureImporter; if (importer null) { continue; } TextureImporterPlatformSettings androidSettings importer.GetPlatformTextureSettings(AndroidPlatform); Debug.Log(${path} - Android Format: {androidSettings.format}, Overridden: {androidSettings.overridden}); } } }6.3 代码说明AssetDatabase.FindAssets(t:Texture, ...)用于按类型查找指定目录下的纹理资源。GetPlatformTextureSettings(Android)获取安卓平台的覆盖设置。如果之前没有覆盖Unity 也会返回一个默认对象。overridden true表示显式覆盖该平台的格式。format TextureImporterFormat.ASTC_6x6指定压缩格式。SaveAndReimport()会应用修改并重新导入资源。注意这里是基于 Unity 的公共编辑器 API在绝大多数 LTS 版本中均可使用。个别旧版本对TextureImporterPlatformSettings的支持不同如果编译报错建议优先升级项目版本或查阅对应版本的 Scripting API。6.4 运行与验证在 Unity 编辑器菜单栏中点击Tools Texture Apply Android ASTC执行完毕后在 Console 中查看输出。再点击Tools Texture Print Texture Info检查各纹理的 Android 平台格式是否变成 ASTC 6x6。如果需要给 iOS 设置 ASTC 4x4只要把ApplyAndroidASTC方法中的平台名和格式换成TextureImporterPlatformSettings iosSettings importer.GetPlatformTextureSettings(IOSPlatform); if (iosSettings.format ! TextureImporterFormat.ASTC_4x4) { iosSettings.overridden true; iosSettings.format TextureImporterFormat.ASTC_4x4; importer.SetPlatformTextureSettings(iosSettings); }这样就能在多个平台之间保持清晰的覆盖逻辑。7. 纹理压缩后如何验证效果7.1 在 Editor 里查看内存与包体Unity 的 Build Report 窗口Window Analysis Build Report可以查看打包后各资源的大小。在编辑器里也可以通过选中纹理 resource 文件在 Inspector 顶部查看导入后的存储大小。需要注意的是Inspector 显示的是资源源文件大小和运行时内存占用并不完全一样但能够反映包体中的资源占比。另一个实用方法是在Build Settings里打包一次然后在Build Report中按“Asset”列表查看哪些纹理占用了最多空间再针对性地调整 Max Size 或压缩格式。7.2 真机 Profile 注意事项真机上的显存占用和编辑器里看到的不一样。建议使用 Unity Profiler 的 Memory 模块并连接真机运行在 Profiler 中查看纹理资源。重点关注哪些纹理占用显存超过 10MB。场景贴图是否存在“同贴图多份拷贝”的情况。是否存在未压缩的 RGBA 纹理被大体积加载。如果某张纹理显示为 RGBA32 而不是预期的 ASTC 或 ETC2说明它的导入设置没有覆盖成功或者该项目使用的打包流程没有应用平台覆盖。7.3 视觉质量对比方式压缩后一定要做视觉对比不能只看数据。推荐方法把压缩前后的纹理截图并排对比。在 Scene 视图或者通过临时 Material 查看纹理贴到模型上的效果。重点检查渐变天空、文字边缘、法线贴图细节、透明边缘。ASTC 8x8 虽然压缩比很高但遇到渐变天空和 UI 描边会出现明显色斑。遇到这类资源时可以单独把格式调成 ASTC 6x6 或 ASTC 4x4。8. 常见问题与排查8.1 压缩后发白、变模糊问题现象常见原因解决思路纹理压缩后整体发灰偏白使用了不支持 RGB 的格式例如把带透明通道的图压成 BC1换成 BC3 / ETC2 / ASTC 并确认 Alpha纹理变糊、细节丢失压缩比过大例如 ASTC 10x10改用更高精度的块尺寸颜色边缘出现色斑压缩格式精度不足针对渐变贴图单独提高格式精度这里尤其需要注意带 Alpha 的 UI 和多边形贴图。BC1 只有 1 bit 的 Alpha基本等于不支持透明很容易把半透明区域变成锯齿状。8.2 内存没降反增问题现象常见原因解决思路压缩后包体变小内存却变大开启了 Crunch Compression加载时解压到普通 RGBA/ETC 格式检查 Crunch 只在打包阶段使用还是运行时也解压场景进入后内存峰值仍然高没有正确启用 mipmap 或图集上有重复纹理开启 mipmap 或检查是否有两张同图不同尺寸的拷贝动态加载的纹理成为驻留资源资源加载后没有释放检查 AssetBundle 或 Addressables 的释放流程Crunch Compression 是非常典型的“包体小但内存大”的场景。Crunch 格式在磁盘上非常小但它在加载时会被后台转换为其他 GPU 格式因此运行时占用的显存可能和普通格式没有本质区别。如果目标是降低内存重点看 format 而不是磁盘上的 Crunch 大小。8.3 部分安卓设备不支持 ASTC问题现象常见原因解决思路低端安卓设备上一部分贴图花屏设备不支持 ASTC但导入时强制设为 ASTC增加 ETC2 降级或维护两套纹理资源老机型显示异常OpenGL ES 版本过低升级游戏最低 API Level或对老设备单独打包 ETC2ASTC 是现在的主流但确实还存在一部分老旧设备不完全支持。稳妥的做法是在打包流程中判断目标设备的 GPU 能力在低端设备上使用 ETC2 资源包在主流设备上使用 ASTC 资源包。8.4 图集与压缩格式冲突问题现象常见原因解决思路图集打包后单个贴图内存翻倍图集引入后改变了纹理尺寸导致平台格式失效确认图集最终尺寸是否仍满足压缩格式的块对齐要求UI 图集压缩后文字变模糊UI 图集压缩比太高UI 图集单独设置高精度格式图集是常见的联合使用方案但它会导致纹理尺寸变成多张子图加边距后的总和。如果一张 1024×1024 的 UI 图集被设置成 ASTC 8x8文字清晰度会受影响。UI 贴图建议单独使用较低压缩比的格式甚至使用 RGBA16/RGBA32。9. 最佳实践与工程建议9.1 纹理规范与目录约定在项目中尽早制定纹理规范场景贴图统一放在Assets/Textures/Scenes/下。UI 图片统一放在Assets/Textures/UI/下。角色、特效贴图单独建目录。这样做的最大好处是批处理脚本可以直接按目录设置默认格式不需要维护一份庞大的“纹理清单”。比如场景目录统一用 ASTC 8x8角色贴图用 ASTC 6x6UI 贴图用 ETC2 或 RGBA16脚本逻辑会更清晰。9.2 按纹理用途分流同一种纹理格式不能覆盖所有使用场景建议按用途分流纹理用途建议格式角色皮肤、脸部贴图ASTC 4x4 / BC7场景地表、墙面ASTC 8x8 / BC1法线贴图BC5 / ASTC 6x6UI 图标、文字相关RGBA16 / RGBA32透明特效ASTC 4x4 / ETC2这个表格不是硬性标准但它反映了一个重要原则质量敏感型贴图多花一点体积普通背景贴图尽可能压缩。9.3 自动化 CI 检查有条件的情况下可以把纹理压缩检查接入打包前的 CI 流程中。可以在打包脚本里扫描所有纹理资源检查是否存在以下问题是否仍有未压缩的 RGBA32 大纹理。是否所有纹理都配置了 Android、iOS 平台覆盖。是否存在尺寸超过 4096 的纹理。是否在 UI 目录中误使用了高压缩比格式。一旦发现异常直接让构建失败并输出具体资源路径。这样比靠美术同学人肉检查可靠得多。我建议在项目里保留一份类似的检查脚本至少确保新提交的资源符合规范[MenuItem(Tools/Texture/Validate Texture Compression)] public static void ValidateTextureCompression() { string[] guids AssetDatabase.FindAssets(t:Texture, new[] { TextureRoot }); int errorCount 0; foreach (string guid in guids) { string path AssetDatabase.GUIDToAssetPath(guid); TextureImporter importer AssetImporter.GetAtPath(path) as TextureImporter; if (importer null) { continue; } TextureImporterPlatformSettings androidSettings importer.GetPlatformTextureSettings(AndroidPlatform); if (!androidSettings.overridden) { Debug.LogError($[TextureValidate] 未设置 Android 平台覆盖{path}); errorCount; } } if (errorCount 0) { Debug.Log([TextureValidate] 所有纹理检查通过。); } }9.4 性能预算判断纹理压缩并没有“绝对正确”的方案关键是建立性能预算。在项目初期就明确以下数字单场景纹理显存预算例如 150MB。角色贴图预算例如一个角色全部贴图不超过 8MB。UI 图集预算例如整个 UI 不超过 50MB。然后针对实际项目调整格式和尺寸。预算一旦确定就可以在 Review 时快速否决不合规的资源而不是反复争论“这张图能不能再压缩一点”。10. 总结与下一步学习建议纹理压缩看起来只是 Unity 导入面板里的一个下拉选项但真正用好的时候它涉及压缩原理、平台兼容、资源规范、打包流程多个层面。如果你是在项目中期才开始关注纹理体积建议先做三件事用 Build Report 统计当前包体里纹理占比找出前 20 张大纹理。给这些纹理设置合理的 Max Size 和压缩格式。用真机 Profile 对比优化前后的内存和加载时间。关于下一步学习可以继续研究图集合并策略、AssetBundle 的纹理分离与变体、Addressables 的资源生命周期以及不同平台的 Graphics API 能力。纹理优化只是一部分和资产加载策略、渲染管线配合起来才是一套完整的游戏性能优化体系。如果本文对你有帮助建议收藏备用。实际项目中遇到纹理压缩相关的奇怪问题也欢迎在评论区把现象发出来大家一起交流排查思路。
返回列表