About Texture Compression Techniques

常见疑问
  1. 为什么要使用纹理压缩?
  2. 这些ETC,ASTC等纹理压缩格式与常见的JPG,PNG,TGA图片格式有什么区别?
  3. 那么有什么压缩格式呢?不同平台又如何选择合适的压缩格式呢?
  4. 对美术同学来说,为什么贴图常有二次幂,4的倍数这种要求?
  5. GPU是怎么读贴图的呢?
为什么要有纹理压缩?什么是纹理压缩?
  1. 从GPU如何使用纹理说起

左边是图片格式的读取流程,右边是纹理格式的读取流程。过程都是,CPU读取纹理到系统内存(图片会解压,纹理不会),然后传递到GPU,GPU再读取所需的具体内容。

在Unity中,当GPU无法直接读取压缩过的纹理时,CPU会将读取进来的纹理解压成未压缩的原生纹理(如RGB24,RGBA32等),再传输到GPU进行后面的内容。

  1. 为什么要有纹理压缩?

从上面的流程可以很明显的看到,虽然GPU可以直接读取R5G6B5,R8G8B8A8等未压缩的原生纹理,但是在低端硬件设备,特别是移动平台下,内存和带宽问题就显得尤为严重。

因此为了缓解移动平台最重要的带宽、功耗和内存问题,我们需要一种内存占用又小又能被GPU读取,同时还要保证速度和质量的格式——压缩纹理格式。

  1. 什么是纹理压缩?

纹理压缩对应的算法是以某种形式的固定速率有损向量量化(lossy vector quantization )将固定大小的像素块编码进固定大小的字节块中。

  • 固定速率:GPU需要高效的随机访问某个像素。
  • 有损:对于渲染来说,有损压缩是可以接受的,一般选择压缩格式时需要在纹理质量和文件大小上寻求一个平衡。
  • 向量量化:是一种量化技术,将一组大量的点(向量)分成具有近似相同数量的最接近它们的点的组。每个组用它的质心点表示,因此存在数据误差,适用于有损压缩。放到纹理压缩中来理解,就是例如将4×4块像素的颜色以2个基色来表示。
  • 解码速度:解码速度快,基本上不能影响到渲染性能。
常见的几种纹理压缩格式介绍
S3TC

历史:S3开发了一种称为S3纹理压缩(S3 Texture Compression)(S3TC)的方案,该方案被选作DirectX的标准,并称为DXTC。在DirectX 10中,它称为BC(用于块压缩(for Block Compression))。

共同属性:DXTC / BC压缩方案有七个变体,它们具有一些共同的属性。编码是在4×4的纹素块上完成的。每个块存储两个参考值颜色,并为该块中的16个纹素的每一个保存一个插值因子。在解码时,每个纹素的颜色等于存储两个参考值颜色或从两种颜色中插值。

PS. 由于版权专利一般用于Windows平台。

  1. BC1 Block (S3TC/DXT1)

    1. 两个16位参考RGB值(R5G6B5)+ 每个纹素2位插值因子 = 共64位。
    2. BC1有两种模式:一种不支持透明,其他两种颜色插值有1:2和3:5两种选择;另一种支持透明,其他两种颜色为全透明和1:1的颜色插值。
      1. 第一种模式中其他两种颜色插值选择1:2还是3:5是跟GPU硬件相关,大部分都是选择1:2,在nVidia的一些GPU中选择的是3:5。
      2. 另外,不同硬件对于RGB565到RGB888的转换实现不同,所以根据以上原因,相同的压缩纹理在不同的硬件上最终效果不同。
      3. 如何判断BC1格式是表示不透明还是具有1位alpha的贴图,是通过两个颜色值color_0和color_1来实现的,如果color_0的数值大于color_1则表示贴图是完全不透明的,反之则表示具有一位透明信息。因为只有一位Alpha信息,所以只能表示透明或不透明。
    3. 压缩率为6:1,即4bpp(bits per pixel)。
    4. 是转换比最高的一种,在不需要高精度也不需要alpha值时可以使用。
    5. PS.BC1的信息丢失主要集中在比较细的边界上,可提高分辨率来解决,高宽放大41%(1.41*1.41=1.9881),这样整个纹理差不多是以前的2倍,但总的压缩率还能保持为3:1。另一个损失是24位RGB转为了16位颜色,看上去差别不大,但对于渐变色带来说就有一些细微差别了。
  2. BC2 Block (DXT2/DXT3)

    1. 两个16位参考RGB值(R5G6B5)+ 每个纹素2位RGB插值因子 + 每个纹素4位Alpha值 = 共128位。
    2. 压缩率:8bpp。
    3. 支持了透明度,但是它的透明度只有4位共16种数值。
    4. PS.DXT2和DXT3的不同之处在于,DXT2中颜色是已经完成了Premultiplied by alpha操作(已完成颜色与alpha的混合,当透明度发生改变时,直接改变整体颜色值,不必再单独复合),DXT3的Alpha信息则是相对独立的,之所以要区分开了则是为了适应不同的需要,因为有些场合需要独立的Alpha信息。
  3. BC3 Block (DXT4/DXT5)

    1. 两个16位参考RGB值(R5G6B5)+ 每个纹素2位RGB插值因子 + 两个8位参考Alpha值 + 每个纹素3位Alpha插值因子 = 共128位。
    2. 压缩率:8bpp。
    3. 每个纹理像素可以选择参考Alpha值或六个中间值之一。
    4. PS.在这里插值的方法有两种,一种用于表示具有完全透明和完全不透明的状态,另一种则是仅在给出的极端值alpha_0和alpha_1中进行插值。区分的方法也是通过比较alpha_0和alpha_1的大小来实现的,如果alpha_0大于alpha_1,则通过插值计算剩下的6个中间alpha值;否则,只通过插值计算4个中间alpha值,alpha_6直接赋值0,alpha_7直接赋值255。
  4. BC4 Block (ATI1/3Dc+)
    1. 只有一个通道,像BC3中的Alpha一样编码。两个8位参考R值 + 每个纹素3位R插值因子 = 共64位。
    2. 压缩率:4bpp。
    3. 支持无符号[0,1]和有符号[-1,1]。
  5. BC5 Block (ATI2/3Dc)
    1. 两个通道,每个通道都像BC3中的Alpha一样编码。两个8位参考RG值 + 每个纹素3位RG插值因子 = 共128位.
    2. 压缩率:8bpp。
    3. 适合法线贴图。取值范围同BC4。
  6. BC6H 与 BC7 共同之处
    1. 首先讲讲BC1的缺陷
      1. 精度低。比如许多纯灰色在RGB565中表示起来就不够纯,比如RGB565(12,24,12)换算成RGB888(99,97,99)大概偏紫色的灰,但是RGB565(12,25,12)换算成RGB888(99,101,99)又显得灰的太绿了。
      2. 颜色少。一个block最多4个颜色。
      3. 所有的颜色都在一条线上(因为是线性插值嘛),这样会导致丢失一些特征颜色。
    2. BC6H 与 BC7 通过提高颜色精度和数量解决了上述问题。两个都是128位,8bpp的压缩率。
    3. 为了有多个颜色组,一个block会被分成多个subset,每个sebset都有自己的颜色。分成两个区一共有64种预设,三个区一共也有64中预设。通过partition ID 去辨别是哪种分区。
    4. BC1的颜色插值不同的硬件可能有不同的权重,但是在BC6H和BC7中是严格定义的。插值因子可以是2-4位,伪代码如下。
  7. BC6H Block
    1. BC6H被设计用于压缩HDR纹理,支持不带Alpha的RGB图,值用带或者不带符号位的16位浮点数表示。解码后,值会作为一个32位的浮点数返回到shader中。
    2. 如上图所示,左边是带符号的16位浮点数,右边是不带符号的16位浮点数表示。
    3. 上图是BC6H的14种block type。BC6H只支持一个或两个分区。Partion ID只有5位,即32种分区模式。
    4. 算法与BC7相似。
  8. BC7 Block

    1. 在Mode0中,PB只有4位,所以只允许使用前16种分区模式。其他都是6位,可以使用所有的64位分区模式。
    2. 新定义的P-bit,可以把颜色多扩充一位。RGB444->RGB555,使用P-bit作为新的RGB的最低位;与直接存储RGB555相比,这样每个颜色节省了2bit。
    3. Mode1中使用了P-shared,和P-bit概念是一样的,只是所有的颜色都使用这一位数值去扩充。
    4. 如上图,Mode0的构成为 1位mode + 4位partition ID + 3个分区*( 每个分区2个RGB444 + 2个P-bit)+ 45个插值因子 = 128位。
    5. 如上图,在Mode4和Mode5中,有两个独立的index tables,可以用来分别保存四个通道中的一个。Rotation字段用来表示哪个通道有独立的索引表。
ETC

历史:Ericsson Texture Compression,是由 Khronos 支持的开放标准,在移动平台中广泛采用。它是一种为感知质量设计的有损算法,其依据是人眼对亮度改变的反应要高于色度改变。类似于DXTC,ETC也是把4×4的像素块压缩成一个64或128位的数据块,也是有损压缩。

PS.人眼对亮度改变的反应要高于色度改变的科学依据如下:视细胞分为视锥细胞和视杆细胞。视锥细胞是感受强光和颜色的细胞,而视杆细胞对暗光敏感。在人的视网膜内约含有600万~800万个视锥细胞,12000万个视杆细胞,分布于视网膜的不同部位。

  1. ETC1

    1. 原理:将4×4的tile分成2块(2×4或4×2),每个块储存一个参考颜色,每个像素的颜色通过一个编码为相对于这些基色偏移的灰度值确定。
    2. 1位Flip值控制划分块的水平或竖直 + 1位Diff值控制参考颜色的模式(两个参考颜色是R4G4B4*2或者R5G5B5+R3G3B3) + 每个子块包含一个参考颜色和一个3位的修饰表索引 + 每个纹素的2位索引值 = 64位。
    3. 压缩率:4bpp。
    4. 如何确定Flip?ETC1的编码流程:对于每个4×4的block,尝试所有的编码可能性,取解码后和原来差距缩小的一种编码。包括是否flip,每个subblock用哪一列的插值表,每个像素取哪一列的差值。
    5. 如何确定Diff?两个subblock的RGB值相差在 [-4,3]区间(5位表示的情况下,对应常规的8位表示是[-32,24]),第一个subblock的RGB值用5位表示,第二个用3位差值表示。
  2. ETC2
    1. 首先看看ETC1的缺点:每个subblock中只有一个颜色,这样色度广的block会丢失很多信息;并且对于渐变的纹理支持很不好。
    2. ETC2通过扩展block modes来解决这些问题。当R5和dR3组合出现溢出的时候,这个数值是无效的,这个时候可以用这个作为新的编码模式。所有的R5和dR3组合如下图,一共16种。

    3. 59-bit mode (T-mode)
      1. 当R通道溢出时,还剩 总共64位-1位diff-8位R通道+(R5的最低两位和dR3的最低两位构成的4位编码)=59位 可以使用。
      2. 颜色构成为:A:RGB444 + B:RGB444 + C0:A-d + C1:A+d
      3. 所以是 2*RGB444 + 3位索引表 + 16个2bit的索引因子 = 59位。
    4. 58-bit mode (H-mode)
      1. 当G通道溢出时,还剩 总共64位-1位diff-8位R通道-8位G通道+(R通道不溢出的256-16种情况,就是7位)+ (G5的最低两位和dG3的最低两位构成的4位编码)= 58位 可以使用。
      2. 颜色构成为:C0:A-d + C1:A+d + C2:B-d + C3:B+d
      3. 所以是 2*RGB444 + 3位索引表 + 16个2bit的索引因子 = 59位。但是只有58位可以用,多出来的一位怎么办呢?通过比较A和B的大小,把0或者1补偿到颜色中。
    5. 57-bit mode (Planar mode)
      1. 当B通道溢出时,还剩 总共64位-1位diff-8位R通道-8位G通道-8位B通道+(R通道不溢出的256-16种情况,就是7位)+(G通道不溢出的256-16种情况,就是7位)+ (B5的最低两位和dB3的最低两位构成的4位编码)= 57位 可以使用。
      2. 定义三个角的颜色RGB676,然后通过差值公式去计算颜色。
    6. 压缩率:4bpp。
  3. ETC2 RGB+1Alpha
    1. 支持1bit的Alpha通道,也就是只支持镂空图,图片只有透明和不透明部分,没有中间的透明度。
    2. 压缩率:4bpp。
    3. 只有RGB555+dRGB333的模式,diff被用来当做别的标识,当diff为1时和以前一样,当diff为0时,索引中的“10”被认为是透明。索引表如下。
  4. EAC
    1. ETC2不支持完全的A通道,也对单个和两个通道的图,比如normal map支持不好。因此引出了EAC压缩格式。
    2. 8位的通道信息 + 4位乘数 + 4位索引表ID + 16*每个纹素3位索引因子 = 共64位。
    3. 解码公式:A = clamp255(baseColor + Mul*Luminance)
    4. 压缩比:4bpp。
PVRTC

历史:PVRTC (PowerVR Texture Compression)由Imagination公司专为PowerVR显卡核心设计,由于专利原因一般它只被用于苹果的设备,仅Iphone、Ipad和部分PowerVR的安卓机支持。这可能是这几种压缩格式中最不公开的技术。

原理:PVRTC不同于DXTC和ETC这类基于块的算法,而将整张纹理分为了高频信号和低频信号,低频信号由两张低分辨率的图像A和B表示,这两张图在两个维度上都缩小了4倍,高频信号则是全分辨率但低精度的调制图像M,M记录了每个像素混合的权重。要解码时,A和B图像经过双线性插值(bilinearly)宽高放大4倍,然后与M图上的权重进行混合。

  1. PVRTC 4bpp

    1. 16位ColorB + 15位ColorA + 1位Mode + 16*每纹素2位的插值因子 = 共64位。
    2. 颜色的最高位1代表RGB图,0代表ARGB图。
      1. 最高位为1时,A图:R5G5B4,B图:R5G5B5
      2. 最高位为0时,A图:A3R4G4B3,B图:A3R4G4B4
    3. Mode位记录如何混合颜色
  2. PVRTC 2bpp

    1. 思想和PVRTC 4bpp是一样的,只是A,B两张图在宽度上被再压缩了一半。相当于把8×4的block都打在原来的64位中。
    2. Mode也有了新的含义。当Mode是0时,每个纹素的插值因子只有1位,解码时颜色选择A或B的颜色。当Mode是1时,只有一半的纹素有2位插值因子(像棋盘格),这些有插值因子的纹素像PVRTC 4bpp一样插值颜色,剩下的没有插值因子的纹素,从周围已有颜色的2或4个纹素颜色插值。
  3. PVRTC2 4bpp
    1. PVRTC2可以支持NPOT,比PVRTC提升压缩质量。
    2. 在PVRTC中,A和B两个图都在最高位保存了自己是RGB还是ARGB图。但是大概率上A和B的选择是一样的,所以可以把其中一个最高位省出来,作为新的编码模式。
    3. Hard和Mode位可以构成4中编码。
      1. Standard bilinear interpolatin 和 Punch-through alpha和PVRTC一致。
      2. Non-interpolated和BC1类似。C0,C1,C0和C1按比例混合。
      3. Local palette:每个纹素都有自己对应的4个(因为索引因子只有2位)颜色可以选择。
  4. PVRTC2 2bpp
    1. 把A和B图再折一半的思想用到PVRTC2 4bpp上。
ASTC

历史:ASTC(Adaptive Scalable Texture Compression),由ARM和AMD联合开发,2012年发布,是较新的一种压缩格式,唯一一个不受专利权影响的压缩格式。ASTC在压缩率、图像质量、种类上都挺不错的,也正在逐步代替前三种,最大的缺点可能就是兼容性还不够完善和解码时间较长,但以现在移动端的发展趋势来看,GPU计算能力越来越难成为瓶颈,因此非常有希望在以后能成为统一的压缩格式。

特点:

  1. 可支持1-4个通道。
  2. 可接受的质量。对Normalmap和RGBA比较重要。
  3. 支持LDR和HDR。
  4. 跨平台。
  5. 可自主选择的压缩率。2D Texture的话从0.89bpp到8bpp都可以选择。
  6. 支持2D/3D Texture。

原理:
ASTC同样是基于block的压缩方式,但块的大小却较支持多种尺寸,比如从基本的4×4到12×12,而且块的宽高也不限于pot,比如6×5;每个块内的内容用128bits来进行存储,因而不同的块就对应着不同的压缩率。

全局信息:

  1. Dynamic range (LDR/HDR)
  2. Texture dimension (2D/3D)
  3. Tile size
  4. Output color space (sRGB/RGB)

每个Block的信息:

  1. Weight grid size
  2. Weight range (for BISE-decoding)
  3. Weight values
  4. Number of partitions
  5. Partition pattern ID
  6. Color endpoint modes
  7. Color endpoint data
  8. Number of planes (1 or 2)
  9. Plane-to-channel assignment

支持情况:

  • IOS:苹果从A8处理器开始支持 ASTC,iPhone6及iPad mini 4以上iOS设备支持,2014年的iPhone 5s及iPad mini 3以前的设备不支持。
  • 安卓:安卓主流压缩格式正在从ETC2转向ASTC。
  • 所有支持OpenGL ES 3.1和部分支持OpenGL ES 3.0的GPU。

建议:

  1. 无Alpha通道的贴图建议压缩格式为ASTC 8×8。如果贴图为法线贴图,建议压缩格式为ASTC 5×5。有更高要求的贴图(比如面部、场景地面),可以设置压缩格式为ASTC 6×6,法线贴图为ASTC 4×4。有Alpha通道的贴图建议压缩格式为ASTC 5×5。有更高要求的贴图(比如特效、UI),可以设置压缩格式为ASTC 4×4。
  2. 在同一压缩格式下,贴图容量不变,有无Alpha通道对压缩结果有很大影响,带Alpha通道的贴图压缩质量下降。
  3. ASTC压缩的算法比较智能,它会为变化更大的通道RGB或者A分配更高的权重,而且对于单色图,RGB通道内容一样时,使用较低的像素占用就可以达到很好的效果。对于单色图完全没有必要使用R8压缩格式,而是应该将RGB通道填充一样的信息,选择ASTC较低的像素占比,如ASTC8x8。
总结与比较
压缩格式 Block Size 压缩比 质量 要求 备注
BC1/DXT1 64 bit 4bpp medium 4的倍数 RGB or RGB + 1-bit Alpha
BC2/DXT2/DXT3 128 bit 8bpp medium RGBA (uncompressed 4-bit Alpha
BC3/DXT4/DXT5 128 bit 8bpp medium 4的倍数 RGBA (compressed Alpha
BC4 64 bit 4bpp medium+ 单通道
BC5 128 bit 8bpp medium+ 双通道,适合normalmap
BC6H 128 bit 8bpp very high HDR
BC7 128 bit 8bpp very high 用于替代BC1-BC5的高质量方案
ETC1 64 bit 4bpp medium+ 2的整数次幂,并且宽高相同
ETC2 64 bit 4bpp high 4的倍数 RGB or RGB + 1-bit Alpha
EAC 64 bit 4bpp high 单通道
PVRTC 64 bit 4×4=4bpp;8×4=2bpp medium+;low 2的整数次幂,并且宽高相同,最小为8
PVRTC2 64 bit 4×4=4bpp;8×4=2bpp high;medium/low
ASTC 128 bit 4×4=8bpp to 12×12=0.89bpp 1-4个通道,LDR/HDR

PS.使用目标平台不支持的纹理压缩格式时,纹理将解压缩为 RGBA 32 并与压缩纹理一起存储在内存中。发生这种情况时,解压缩纹理会损失时间,并因为存储两次而导致内存损失。此外,所有平台都有不同的硬件,并经优化后可最有效地处理特定压缩格式;因此,选择不兼容的格式会影响游戏的性能。

常见疑问解答
  1. 为什么要使用纹理压缩?
    1. 缓解内存、带宽和缓存问题。
  2. 这些ETC,ASTC等纹理压缩格式与常见的JPG,PNG,TGA图片格式有什么区别?
    1. 常见的JPG,PNG,TGA是图片格式,是图片文件的存储格式,用于图片文件的存储和传输,通常在磁盘文件,内存以及网络传输中使用。
    2. ETC,ASTC等是纹理格式,纹理格式是显卡能够直接进行采样的纹理数据的格式,是向显卡中加载纹理时使用的数据格式。
    3. 尽管像JPG,PNG的压缩率很高,但并不适合纹理,主要问题是不支持像素的随机访问,这对GPU相当不友好。
  3. 那么有什么压缩格式呢?不同平台又如何选择合适的压缩格式呢?
    1. 常见的纹理压缩格式有DXTC、ATITC、ETC、PVRTC、ASTC等。
    2. 不同平台的一般选择
      1. PC:BC6H,BC7
      2. 安卓:ETC2,并从ETC2转向ASTC,ASTC在Android 5.0/OpenGL ES 3.1后支持,市场大部分机型都支持(98.5%),要处理好兼容性问题。
      3. IOS:在iPhone6以上(包含)都支持ASTC,6以下可以选择PVRTC2。
  4. 对美术同学来说,为什么贴图常有二次幂,4的倍数这种要求?
    1. 除了纹理压缩格式的要求外
    2. 二次幂更优。在递归或循环时,如果一个数是2的幂次方,可以被2整除,而且商也是2的幂次方。OpenGL API支持非2的幂次方,是因为考虑到易用性隐藏了细节,在内部处理了一些必要的拉伸或填充操作。
    3. POT比NPOT性能提升约30%
  5. GPU是怎么读贴图的呢?
    1. GPU当中完成对贴图访问(寻址以及获取数据)的模块是以硬件形式存在的。它是不可编程的,你只能从它所提供的几种方式当中选一种。这是第一个因素。
    2. 贴图在GPU的内存(显存)当中,一般都不是以行优先或者列优先方式存储的,而是以“块”为单位存储的。就好像一块一块马赛克组成的墙面那样,每个马赛克对应的像素在内存上是连续存储的。这是第二个因素。
    3. 上述几个因素(硬件寻址+贴图在内存上的特殊排布方式+压缩算法要求)决定了一款GPU在设计的时候就会将这个“块”的最小尺寸固定下来。有的GPU是2×2像素,有的是4×4像素,还有的是8×8像素。所以当贴图的尺寸不是这些“块”的整数倍的时候,当贴图被传送到GPU内存(显存)的时候,就会被拉伸或者在四周(一般是右侧和下侧)填充无用数据,使其成为这些“块”的整数倍。(称为pitch)
参考
  1. *TEXTURE COMPRESSION TECHNIQUES
  2. 你所需要了解的几种纹理压缩格式原理
  3. 游戏中的压缩纹理格式
  4. 简单聊聊手游开发中使用的ETC2
  5. 常用纹理和纹理压缩格式
  6. Khronos Data Format Specification v1.1 rev 9
  7. ASTC纹理压缩格式详解
  8. Unity Documentation
  9. 几种主流贴图压缩算法的实现原理
  10. OpenGL支持非二次幂纹理的底层原理是什么?
  11. strom-etc2-gh07
  12. StromAkenineGH05
  13. PVRTC Specification and User Guide
  14. Khronos Data Format Specification v1.1 rev 9
  15. ETC2 Compression in a Nutshell
  16. Ericsson-ETC2-SIGGRAPH_Aug12
  17. ASTC-GDC2013
  18. StromPetterssonGH07

One Comment

Add a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注