本文基于资料收集,概括了几种纹理压缩格式的基本思想,希望对于学习有所帮助。
为什么我们需要纹理压缩格式?
例如R5G6B5、A4R4G4B4、A1R5G5B5、R8G8B8或A8R8G8B8等未经压缩的图片格式,是能够被GPU直接读取的原生纹理格式。但在低端硬件设备或者说移动平台下,有两个问题需要解决。
一个是内存,例如A8R8G8B8格式中一个像素占4字节,如果是512x512分辨率内存就占用512x512x4 B=1048576 B=1 MB,这种内存消耗在低端设备上根本无法接受。
另一个重要的是数据传输时的带宽,带宽是发热的元凶,在渲染3D场景时,会有大量的贴图被传输到GPU,若不限制,总线带宽很快就会成为瓶颈,手机秒变暖手宝,严重的还会影响渲染性能。
因此我们需要一种内存占用既小又能被GPU读取的格式——压缩纹理格式。纹理压缩对应的算法是以某种形式的固定速率有损向量量化(Lossy Vector Quantization)将固定大小的像素块编码进固定大小的字节块中。
有损表示对于渲染来说,有损压缩是可以接受的,一般选择压缩格式时需要在纹理质量和文件大小上寻求一个平衡。
固定速率压缩指的是什么呢?因为GPU需要能够高效的随机访问一个像素,这意味着对任意像素,解码速度不该有太大的变化。因此,我们常见的贴图压缩算法都是有损压缩。相反的例如zip则是一种可变速率压缩。
向量量化(Vector Quantization,VQ)是一种量化技术,将一组大量的点(向量)分成具有近似相同数量的最接近它们的点的组。每个组用它的质心点表示,因此存在数据误差,适用于有损压缩。放到纹理压缩中来理解,就例如将4x4块像素的颜色以2个基色来表示。
编码和解码速度:一般来说编码速度慢没关系,因为通常纹理压缩只需要在游戏打包时进行一次,对于用户运行时体验完全没有影响。但解码速度必须足够快,而且基本上不能影响到渲染性能。
压缩比:通常以比特率或每像素的平均比特数(bits per pixel,bpp)表示,常见的为2~8bpp。一般RGB原生纹理的像素指24位,4bpp表示每像素占4位,所以也可以认为4bpp表示压缩比为6:1。
顺便一提,在Unity中,任何图片文件格式都存在一个导入过程,导入后的文件格式都是Texture2D,在Texture2D的导入设置选项中需要针对不同平台设置纹理压缩格式。
为什么我们不使用png、jpg这类常见的压缩格式?
尽管像jpg、png的压缩率很高,但并不适合纹理,主要问题是不支持像素的随机访问,这对GPU相当不友好,GPU渲染时只使用需要的纹理部分,我们总不可能为了访问某个像素去解码整张纹理吧?不知道顺序,也不确定相邻的三角形是否在纹理上采样也相邻,很难有优化。这类格式更适合下载传输以及减少磁盘空间而使用。
常见纹理压缩格式
ETC
ETC(Ericsson Texture Compression)最初为移动设备开发,如今它是安卓的标准压缩方案,ETC1在OpenGL和OpenGL ES中都有支持。
其原理简单来说,是将4x4的像素块编码为2x4或4x2像素的两个块的方法,每个块指定一个基色,每个像素的颜色通过一个编码为相对于这些基色偏移的灰度值确定。
具体来说,ETC1每4x4像素块编码为64位的字节数据,每一个像素块又分为两个2x4子块(由一个“flip”位控制水平或竖直划分),每个子块包含一个3位的修饰表索引(modifier table index)和一个基本颜色值,这两个颜色值要么是2*R4G4B4要么是R5G5B5+R3G3B3(由一个“ diff”位控制是哪一种)。
这样一个子块由1个基本颜色值和4个修饰值可以确定出4种新的颜色值:
color0 = base_color + RGB(modifier0, modifier0, modifier0)
color1 = base_color + RGB(modifier1, modifier1, modifier1)
color2 = base_color + RGB(modifier2, modifier2, modifier2)
color3 = base_color + RGB(modifier3, modifier3, modifier3)
最终的颜色就是从这4个颜色值中选出。其原理就是另外32位数据中包含16个2位选择器数据,每个像素的颜色都根据2位选择器的值从这4种中选出。
Unity的几种ETC纹理压缩格式:
RGB ETC1 4 bit:4 bits/pixel,对RGB压缩比6:1,不支持Alpha,绝大部分安卓设备都支持。
RGB ETC2 4 bit:4 bits/pixel,对RGB压缩比6:1。不支持Alpha,ETC2兼容ETC1,压缩质量可能更高,但对于色度变化大的块误差也更大,需要在OpenGL ES 3.0和OpenGL 4.3以上版本。
RGBA ETC2 8bit:8 bits/pixel,对RGBA压缩比4:1。支持完全的透明通道,版本要求同上。
RGB +1bit Alpha ETC2 4bit:4 bits/pixel。支持1bit的Alpha通道,也就是只支持镂空图,图片只有透明和不透明部分,没有中间的透明度。
EAC:核心原理与ETC相同,但它只用于单通道或双通道数据,OpenGL ES 3.0和OpenGL 4.3后的设备大部分支持,但由于安卓平台五花八门的兼容性,一般不建议用单双通道贴图。
DXT
原名S3TC(S3 Texture Compression ),由S3 Graphics开发的一种与组块有关的有损压缩算法,也叫DXTn或DXTC(DirectX Texture),是块截断编码(Block Truncation Coding)的一种改进。由于版权专利一般用于Windows平台。
其原理简单来说,是由一对低精度的“基色”来描述一个4x4的RGB像素块,并允许每个像素在这些基色之间指定一个插值。S3TC有多种变体,每种都是为特定类型的图像数据设计,但它们都是将4x4的像素块转换为64位或128位的数据。
BC1(Block Compression)是最小的一种变体,也是转换比最高的一种,在不需要高精度也不需要a值时可以使用。它将4x4个像素作为一个块(block)存储64位数据,其中包括两个16位RGB值(R5G6B5,人眼对绿色更敏感)$color_0$和$color_1$,和16个2位选择器值,一个像素最终的颜色值由$color_0$、$color_1$和对应的2位选择器值决定。混合公式如下:
PS:BC1的信息丢失主要集中在比较细的边界上,可提高分辨率来解决,高宽放大41%(1.41*1.41=1.9881),这样整个纹理差不多是以前的2倍,但总的压缩率还能保持为3:1。另一个损失是24位RGB转为了16位颜色,看上去差别不大,但对于渐变色带来说就有一些细微差别了。
PPS:在Unity 2017中新增了基于Crunch库的改进版DXT算法,占用磁盘空间更小,解压速度更快,但是相应的图像质量不如DXT那么好。
BC2在BC1的基础上支持了a值。它将4x4共16个像素存储为128位数据,其中包括64位a通道(每像素4位)和64位颜色通道,颜色和DXT1一致,即两个16位RGB值和16个2位索引表x。它的透明度只有4位共16种数值,相比DXT5有些功能基本没人用。DXT2和DXT3的差别在于是否是预乘过的颜色。
BC3在BC2的基础上改进了a值算法。BC3将4x4共16个像素存储为128位数据,其中包括64位a通道(两个8位a值和一个4x4的3位索引表)和64位颜色通道,颜色和DXT1一样,即两个16位RGB值和16个2位索引表x。由于a值采用了和颜色一样类似的块压缩算法,所以数值范围更广,BC是比较常见的。
BC4和BC5在D3D10中可用,只能存储一个/两个颜色通道。
BC6H和BC7在D3D11中可用。BC6H是一个不带a值的HDR格式,它将4x4共16个像素存储为128位数据,其中包括两个48位的RGB值(16:16:16),每个颜色分量都是带符号浮点值(1个符号位 + 5个指数位 + 10个尾数位),以及16个2位索引表。
BC7相比其它有点特殊,虽然它也是将4x4共16个像素存储为128位数据,但它的最低有效位为Mode位,(最低有效位即最低的非0位)根据不同模式,颜色值的存储格式不同,是否有a值或a值的存储格式也不尽相同,是一种比较灵活的存储格式,但这也意味着解码所带来更多的消耗。
PVRTC
PVRTC(PowerVR Texture Compression)由Imagination公司专为PowerVR显卡核心设计,由于专利原因一般它只被用于苹果的设备,仅iPhone、iPad和部分PowerVR的安卓机支持。这可能是这几种压缩格式中最不公开的技术。
PVRTC不同于DXT和ETC这类基于块的算法,而将整张纹理分为了高频信号和低频信号,低频信号由两张低分辨率的图像A和B表示,这两张图在两个维度上都缩小了4倍,高频信号则是全分辨率但低精度的调制图像M,M记录了每个像素混合的权重。要解码时,A和B图像经过双线性插值(Bilinearly)宽高放大4倍,然后与M图上的权重进行混合。
PVRTC 4-bpp模式下,每4x4像素占一个64位数据块,2-bpp模式下每8x4像素会有一个64位数据块。两者大同小异,我们仅说4-bpp模式。
4-bpp模式下,A和B图缩小后都只保存一个颜色值,如下图所示,A图比B图少1位,但两张图都可以选择以RGB或ARGB的方式存储(最高位决定为哪种),A色可以用RGB554或ARGB3443格式编码,B色可以用RGB555或ARGB3444格式编码。
在解码时,为了解码任意像素,必须读取4个相邻的PVRTC块,使用这4个块来解码一个5x5块。
使用双线性过滤来对A和B图进行扩大,然后A和B图根据M图与“Mode”位进行混合,这里的"Mode"位为1时,M图中10值像素被看作是开启了“punch-through alpha”,Alpha通道会被强制清零,这种神奇的操作是为了兼容旧应用程序,具体就不说了。
单从4-bpp模式来看,PVRTC和BC、ETC非常相似,都有两个颜色值,但基本思想却是不同的。
Unity的几种PVRTC的纹理压缩格式:
苹果的所有移动设备都支持PVRTC。
PVRTC2质量比PVRTC更高,而且支持NPOT(非2次幂纹理),是PVRTC的升级版。
PVRTC和PVRTC2都支持4-bpp和2-bpp的ARGB格式。
RGB PVRTC 4 bit:4 bits/pixel,对RGB压缩比6:1,安卓设备需要PowerVR Series 5以上。
RGBA PVRTC 4 bit:4 bits/pixel,对RGBA压缩比8:1,3位Alpha值,设备同上。
RGB PVRTC 2 bit:2 bits/pixel,对RGB压缩比6:1,安卓和iOS设备需要PowerVR Series 5X以上。
RGBA PVRTC 2 bit:2 bits/pixel,对RGBA压缩比8:1,3位Alpha值,设备同上。
ASTC
ASTC(Adaptive Scalable Texture Compression),由ARM和AMD联合开发,2012年发布,是较新的一种压缩格式,唯一一个不受专利权影响的压缩格式。ASTC在压缩率、图像质量、种类上都挺不错的,也正在逐步代替前三种,最大的缺点可能就是兼容性还不够完善和解码时间较长,但以现在移动端的发展趋势来看,GPU计算能力越来越难成为瓶颈,因此非常有希望在以后能成为统一的压缩格式。
ASTC也是一种基于块的有损压缩算法,它很像BC7,不同的是块中像素数量可变,它的特点有很多:
- 较高的灵活性:支持1-4分量的贴图
- 压缩率/质量灵活可变:根据不同图片会选择不同压缩率级别的算法
- 支持2D/3D贴图
- 跨平台:iOS、安卓、PC
- 同时支持LDR和HDR:BC6H虽然支持HDR但不支持Alpha通道
ASTC格式的块为固定大小的128位。2D纹理编码中,它从4x4到12x12像素都有,对应的压缩比从3:1到27:1。所有支持的块和比特率如下:
Increment列表示压缩比的增量,这也表示ASTC的比特率可以在小数级变化,这种技术称为BISE(Bounded Integer Sequence Encoding)。
ASTC的压缩非常复杂,分了很多可变的配置数据,也不打算再深入研究了。
但要注意的是尽管纹理可以被编码为1-4通道图像,但是解码后的值总是以RGBA格式输出。在LDR sRGB模式下,颜色值以8位整数的形式返回,而如果是HDR则将以16位浮点数的形式返回。
《ASTC纹理压缩格式》对ASTC做了详细的测试,非常建议去看看。
总结
整理了下网上的资料,以2020年市场上的移动设备支持来看:
- 安卓:用ETC2没有什么问题;至于ASTC在Android 5.0/OpenGL ES 3.1后支持,市场大部分机型都支持(98.5%),可以考虑选择,但毕竟是安卓,要做好处理兼容性的心里准备。
- iOS:在iPhone6以上(包含)都支持ASTC,6以下可以选择PVRTC2。
最后,所有压缩格式的简要描述如下,灰色框表示已经过时了或基本不用了。
参考来源:
TEXTURE COMPRESSION TECHNIQUES
Recommended, default, and supported texture compression formats, by platform
Another Milestone for ASTC Texture Compression
Crunch compression of ETC textures
ETC1 Compressed Texture Image Formats
Texture compression on mobile demystified
Texture compression using low-frequency signal modulation
这是侑虎科技第1187篇文章,感谢作者蕾芙丽Reverie供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)
作者主页:https://www.zhihu.com/people/...
再次感谢蕾芙丽Reverie的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)