# 前言
在今年 10 月份这次评审的时候,有问到图片设置相关的内容,当时感觉也是没回答多好。
例如,ETC1 与 ETC2 相比有什么差别?
回答的是 ETC1 不带 Alpha 通道,ETC2 带 Alpha 通道,其它就差不多 (实际上压缩质量也会高一点)
有问到如果 ETC 质量不佳怎么办?回答的是用 RGBA 设置看效果,而且还被追问除此之外呢?是否直接就用 RGBA 了?
下来后才想明白,意思应该是想问 ASTC 的,然而当时不知是觉得这个格式项目没有实际普及还是什么,根本没回答这个点... 甚至回答上都没提到 ASTC,虽然 ASTC 这格式肯定是知道,ios、android 新机型通用的一种图片压缩格式,估计也跟只在理论上了解、却没自己实践对比有关 —— 就很难反应过来。
因此这里就对这些格式挨个进行尝试、对比,整理一下自己了解的差异,以期得到更好的理解。
# 图片设置
sRGB 选项,本身处于 gamma 颜色空间内时,勾选与否都不影响。
处于线性颜色空间内时,勾选后才会做校正。
Mipmap:UI 肯定不能勾,3D 模型勾不勾取决于有没有相机远近操作及内存平衡。
# 图片压缩格式
为什么需要压缩?
减少内存、减少数据传输带宽需求,压缩是有损的。
# 压缩格式
对此官方文档也有列表说明:
class-TextureImporterOverride
# 一、RGBA
RGBA 格式主要分为:
RGBA64:每个像素 64 位,每个通道 16 位
RGBA32:每个像素 32 位,每个通道 8 位
RGBA16:每个像素 16 位,每个通道 4 位
其中 RGBA64、RGBA32 均为无压缩,RGBA16 官方文档说是『量化』模式,可能是靠直接量化缩放减少像素精度以减少内存占用,不过这种模式质量损失其实感觉有点大,对比了下还不如 ASTC4x4 压缩。
分辨率 128x128,RGBA32->ASTC4x4,大小为 32kb->16kb (质量和大小都不占优)
RGBA64 高精度模式,图片本身不支持的情况下,设置了也只是浪费内存,所以基本上可以把 RGBA32 当做『原图』看待。
# 二、ECT1
安卓,不带 Alpha 通道,4bits/pixel
强行要求图片分辨率为 2N 次方。
# 三、ETC2
安卓,带 Alpha 通道 8bits/pixel,不带 Alpha 通道 4bits/pixel,质量比 ETC1 好。
强行要求图片分辨率为 2N 次方。
ETC1 与 ETC2 对比(128x128&10.7kb):
(字体边缘变化模糊度更低的就是 ETC2)
ETC2 还有一种支持镂空图 (无 Alpha 过度) 的模式 (RGB+1bit Alpha),也是 4 bits/pixel,这个格式网上没有解释如何处理的,猜测是将 Alpha 0.5 为界限,完全透明或不透明。
我用 PS 大概画了一张渐变图进行测试,设置后对比效果如下:
例如:
左边为 ETC2 RGB+1bit Alpha 4Bit (8KB),右边为 ETC2 RGBA 8Bit (16KB)
整体质量相对会差一点,不过相比常规 ETC2 RGBA 省一半内存。感觉这种适合效果要求不是很高、小一些的无渐变的纯透明图标,不过边缘像素必须处理好,不然边缘可能会比较明显,或者存在锯齿。
注:ETC 和 ETC2 在 Unity 还有种 Crunched 模式,可以自己选择压缩比 (不过 ETC2 只有 RAGB Crunched),这种模式会增加一定解压消耗,因为它会在 CPU 上将该纹理解压缩为 ETC 传给 GPU (虽然官方称解压过程『非常快』),更多见 官方文档 说明。
# 四、ASTC
文档表示同等压缩率下,效果比 ETC2 更好,其 blockSize 越大压缩率越高,压缩分块从 4x4 到 12x12 最终可以压缩到每个像素占用 1bit 以下
其原理是将图片分块,每块颜色固定为 16bytes,然后通过控制每块存放多少像素 (4x4、6x6) 来控制压缩率,所以存放颜色越多压缩率越高,效果就越差。
不像 ETC、PVRTC 强行要求分辨率 2N 次方,当然符合这个规则的图片最好。
参考:https://www.zhihu.com/question/376921536/answer/1063272336
与 RGBA 格式相比:
计算公式为:压缩后像素大小 = 16bytes/(分块宽高) x8bit
尝试计算一张 1024x1024 图片在 ASTC 10x10 下的大小:
压缩后每像素颜色占用:16/(10x10) x8=1.28bit
原本大小:1024x1024x32=4MB
4MB/(32/1.28)=1024x1024x1.28=1342177.28bit=163.84KB
ASTC 的 Alpha 通道是自动识别的 (特指安卓平台,ios 倒是可以单独选择是否带 Alpha 通道格式,不过并不影响压缩大小)。如果带 Alpha 通道,可以设置更低的 block size,否则可以设置更高的 size。
例如,根据压缩率的对比,若将 4x4 作为带 Alpha 透明通道图片标准:
- 4x4=8bit=RGBA (32bit)= 压缩率 4 倍
- 不带 Alpha 通道 压缩率 4 倍 = RGB (24bit)/4=6bit~5x5
当然,换算也不绝对,还是要看实际图片效果进行确定,不过感觉一般通用可以按照这种规则来,若某些特定图片出现可见的损失则手动处理。
对比了一下:
分辨率 128x128,ETC2->ASTC4x4,大小为 16kb->16kb
分辨率 128x128,ETC2->ASTC5x5,大小为 16kb->10.6kb
分辨率 128x128,ETC2->ASTC6x6,大小为 16kb->7.6kb
分辨率 128x128,ETC2->ASTC8x8,大小为 16kb->4kb
同一张带 Alpha 通道的 Icon 图片,直到 ASTC8x8 的时候才与 ETC2 有了比较明显的差距 (比之更糊点),但是大小可是小了 4 倍!那怕 ASTC6x6 也小了一倍以上,且效果更好。
最后,再分别使用 ASTC4x4、ETC2 与 RGBA32 (也就是原图) 进行对比:
分辨率 128x128,RGBA32->ASTC4x4,大小为 64kb->16kb ↑
分辨率 128x128,RGBA32->ETC2,大小为 64kb->16kb ↑
其中,RGBA32->ASTC4x4 损失的质量很少,RGBA32->ETC2 质量损失就比较明显了。
后续还测试了 RGBA32->ASTC5x5,虽然噪点更多,不过在宏观上来看,对于小图来说基本跟原图差别不是很大。
虽然按照文档所说,ASTC 效果会比 ETC2 更好,不过设置更高的 block 获得更高压缩率后效果肯定还是会降低,这是压缩率跟质量必然的取舍。不过大的 ASTC block 压缩率是真的高,而且 ios 和 android 都支持 (除了非常非常老的设备 ——iPhone6 以前、android OpenGLES3.0 部分及所有 OpenGLES3.1 GPU)。
# 五、PVRTC
IOS (GPU) 专用,强行要求图片分辨率为 2N 次方的方形。
从左自右分别是:ASTC4x4 (16kb)->ASTC6x6 (7.6kb)->ASTC8x8 (4kb)->PVRTC_4bit (8kb)->PVRTC_2bit (4kb)
就实际结果而言,PVRTC 2bit 糊得基本没法看,而 PVRTC 4bit 实际效果,基本也就跟 ASTC8x8 差不多,细节上甚至还差一点 —— 而 ASTC8x8 容量却比它低了一半。
PVRTC 4bit 只能说比 ASTC10x10 好些 —— 但 ASTC10x10 都直接压缩到 2.6kb 了
所以 ASTC 在 ios 上的质量 / 压缩比优势比在 android 平台更大,如果目标机型不是 iPhone6 (iPhone6 支持) 以前的设备,最好就使用 ASTC 了。
# 六、其它
除此之外,还有部分『其它』格式,例如:
- R8:未压缩单通道 (R)
- Alpha 8:未压缩单通道 (A)
- EAC:与 ETC 类似,不过只有单通道或双通道。
- R EAC 4 bit :压缩单通道 (R)
- RG EAC 8 bit :压缩双通道 (RG)
- 等
详细解释在官方文档 class-TextureImporterOverride 也都有说明,不过这些诸如拆分通道的格式,基本上不常用 (或者说没见过用),或许某些情况下,写 Shader 只需要单通道之类的可能会用到?
# 压缩后对 AssetBundle 影响
在对图片格进行一堆测试之后,心里就冒出一个想法:
既然图片本体进行了压缩,那么对最终资源大小有影响吗?如果图片本身压缩后,AssetBundle 打包无法减少更小,是否可以选择对图片采取不压缩,或者 LZ4 压缩模式?
于是找了一张图进行测试,由于这张图片不是 POT 分辨率,因此以 ASTC 与 RGBA32 (原图) 为例。
简单的测试代码如下:
[MenuItem("AssetBundleTest/PackSelect(LZMA)")] | |
public static void PackSelectLZMA() | |
{ | |
PackSelect(BuildAssetBundleOptions.DeterministicAssetBundle); | |
} | |
[MenuItem("AssetBundleTest/PackSelect(LZ4)")] | |
public static void PackSelectLZ4() | |
{ | |
PackSelect(BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.ChunkBasedCompression); | |
} | |
[MenuItem("AssetBundleTest/PackSelect(Uncompress)")] | |
public static void PackSelectUncompress() | |
{ | |
PackSelect(BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.UncompressedAssetBundle); | |
} | |
private static void PackSelect(BuildAssetBundleOptions opt) | |
{ | |
Object o = Selection.activeObject; | |
string path = AssetDatabase.GetAssetPath(o); | |
AssetImporter importer = AssetImporter.GetAtPath(path); | |
if (importer == null) return; | |
AssetBundleBuild build = new AssetBundleBuild(); | |
build.assetBundleName = importer.assetBundleName; | |
build.assetNames = new string[] { path }; | |
BuildPipeline.BuildAssetBundles(Path.Combine(Application.dataPath, "MyBundles"), new AssetBundleBuild[] { build }, opt, EditorUserBuildSettings.activeBuildTarget); | |
AssetDatabase.Refresh(); | |
} |
对比结果:
RGBA (1.8M)->LZMA (449kb [25%])->LZ4 (755kb [41%])-> 不压缩 (1.85M [102%])
ASTC4x4 (462.2kb)->LZMA (273kb [59%])->LZ4 (303kb [65%])-> 不压缩 (467kb [101%])
ASTC12x12 (52.1kb)->LZMA (33kb [63%])->LZ4 (37kb [71%])-> 不压缩 (57kb [109%])
ETC2 压缩模式对于 NPOT 的图片,勾选 ToNearest (正常被压缩) 以及不勾选 (无法正常被压缩为 ETC2) 的大小:
根据结果可以得出结论:
被压缩过的图片,最终打出来的 AssetBundle 包压缩率也会更低 —— 当然无论如何,图片本身进行过压缩格式压缩后,最终资源包体会更小。
ETC 一类如果图片是 NPOT 分辨率,则压缩将会无效
所以从空间占用来考虑,打包时『通常压缩』格式依然还是需要选择压缩模式的。
为什么『通常压缩』要打重点?
因为后面我在测试 ETC Crunched 压缩模式时,确认这种高压模式 AssetBundle 无法再作压缩了。
依然以上述图片为例,设置为 ETC Crunched 100% 质量:
打包的 AssetBundle 大小基本跟 Inspector 预览差异不大 (多了一点 AssetBundle 数据)
所以要是选用了 ETC Crunched 模式,打包 AssetBundle 倒是可以采用『不压缩』或者『LZ4』来进一步节省加载时间。
# 注意
就算是 ASTC 压缩,如果选择开启了 Mipmap,则同样要求必须是 POT 的图,否则无法压缩 —— 对此项 Unity 会有 Warning 提示。
# 总结
根据上述各种对比,可以得出结论:
质量 (由高到低):RGBA32 (64kb)->ASTC4x4 (16kb)->ASTC5x5 (10.6kb)->ASTC6x6 (7.6kb)->ETC2 (16kb)->ASTC8x8 (4kb)~PVRTC4bit (8kb)->ASTC10x10 (2.6kb)->PVRTC2bit (4kb)
大小 (由高到低):RGBA32 (64kb)->ASTC4x4 (16kb)->ETC2 (16kb)->ASTC5x5 (10.6kb)->PVRTC4bit (8kb)->ASTC6x6 (7.6kb)->ASTC8x8 (4kb)->PVRTC2bit (4kb)->ASTC10x10 (2.6kb)
所以,除非对非常老的机型也想支持,否则默认就可以使用 ASTC6x6 ,再视情况而定对 Block 进行增加或减少。
否则:
- 安卓:不带 Alpha 使用 ETC2 RGB,带 Alpha 使用 ETC2 RGBA
- IOS:不带 Alpha 使用 PVRTC RGB 4bit,带 Alpha 使用 PVRTC RGBA 4bit
注 1:ETC 要求图片为 2N 次方 (长宽不强行要求一致)
注 2:PVRTC 要求图片为 2N 次方的方形 (长宽强行要求一致)
注 3:IOS PVRTC 勾选 ToNearest 同样会被强行压缩为正方形,比安卓平台变形会更大 (当长宽差距过大 ToNearest 也会失败)
注 4:勾选 ToNearest 时,缩放算法可以选 Bilinear ,个人对比后觉得这个细节明显更好点,默认的 Mitchell 更糊。
另外,在实在无法启用 ASTC,但是又想减少包体大小的情况,可以考虑使用 ETC Crunched (有损压缩) 模式,该模式增加部分解压 CPU 消耗,以时间换存储空间,且对于运行时内存使用量没有影响 (在加载时被解压缩,官方称 解压缩速度非常快)。
(由于压缩率很高,文件大小减少,变相减少了 IO,对整体加载时间的影响或许不会太大?)
注 1:ETC Crunched 无法再被 AssetBundle 进一步压缩
注 2:ETC Crunched 压缩比非常高,经过测试可能比 ASTC12x12 都小
注 3:实测 ETC2 Crunched 100% 质量也比普通 ETC2 更糊一些
- 部分无法压缩的 NPOT 图片,勾选 ToNearest 会导致效果出现问题,或者使用的不规则图片然后调用了 SetNativeSize 的情况 (一般出现于老项目,比如说我们汉室复兴),也可以采取使用 RGB16 或 RGBA16 的格式,比 32 位减少一半大小。
注:优化老项目时 (例如我们的项目),对于设置 ETC2 和 RGBA16 时可以评估考虑两者具体占用大小、效果。例如带透明通道 RGBA16 某些大图可能会会产生『条纹』。部分情况 (RGBA) 下 ToNearest ETC2 (8bit) 占用比 RGBA16 还要高 (512x512x8>300x300x16),强行要求方形的 PVRTC (4bit) 极端情况下可能也有类似问题 (512x512x4>400x100x16)。可以写个工具计算两者哪个更划算再进行设置。
其它还需要注意的是官方对平台不支持纹理的说明:
当 Unity 加载的纹理具有设备不支持的压缩格式时,它会将纹理解压缩为该平台的默认未压缩格式,并将未压缩副本与原始压缩纹理一起存储在内存中。这会增加纹理加载时间并使用额外的内存。当 Unity 加载的纹理具有设备支持的压缩格式时,GPU 可以使用该数据而无需任何转换。
最后:
因为 ETC 不支持 Alpha,一些老项目会出现单独分离透明通道的做法。
虽然 ASTC 已经不强行要求 2N 次方了,不过 POT 最好也让美术遵守一下,对性能有一定好处,参考。
注:对于图片大小,最终打包成 AssetBundle 后跟 Inspector 预览的大小可能会有差异 —— 如果像素属于『空白』像素,会产生更大的压缩率 (资源包更小)
# 疑问
既然 ASTC 会根据 Alpha 自动计算压缩效果,Unity (2021.3.6f1) 为安卓平台提供的 ASTC 选项也是合并在一块的,为什么 IOS 平台又是分开设置的了?
注 1:Unity2018.4.36f1 安卓也是分开的,分为 ASTC RGB 和 ASTC RGBA,推测高版本 Unity 安卓平台增加了 ASTC HDR 模式后,因为设置量太多而合并?
注 2:实测 IOS 设置RGB ASTC
或RGBA ASTC
大小并不会发生变化 (选 RGB 透明通道确实会被丢掉)
参考文章:
- ASTC 纹理压缩格式详解
- texture-compression-formats
- class-TextureImporterOverride