【技术美术百人计划】纹理压缩
一、什么是纹理压缩
纹理压缩的概念
纹理压缩是为了解决内存、带宽问题,专为在计算机图形渲染系统中存储纹理而使用的图像压缩技术
图片和纹理的区别
图片格式
○是图片文件的存储格式,通常在硬盘、内存中存储,传输文件时使用
○例如:jpg、png、gif、bmp
○图片压缩格式是基于整张图片进行压缩,像素之间解码过程中存在依赖关系
■无法实现单个像素级的解析,发挥不了显卡的并行能力
■并且,无论什么格式在显卡解码后都是RGBA的纹理格式
■总结:无法减少显存的占用率,且需要CPU解压后才能被GPU读取,结果就是:增加了CPU的时间和带宽
纹理格式
○纹理格式是显卡能够直接进行采样的纹理数据格式,通常在向显卡中加载纹理时使用;
○基于块压缩,能够更快的读取像素所属字节块进行解压缩,以支持快速访问
■“随机访问”:如果渲染一个物体时,需要在某个坐标上采样纹理,那么GPU只需要读取该像素所属固定大小字节块,对其进行解压即可。
■如果拿到一张贴图,设置纹理压缩格式,CPU会按照我们设定的格式进行压缩,然后传递给GPU读取
■如果不设置纹理压缩格式,以图片格式进行,CPU也会进行压缩,但是会压缩为RGBA32格式,但其实这个格式是非常大的,并没有起到压缩的作用
二、为什么要纹理压缩

○纹理压缩格式基于块压缩,能够更快读取像素所属字节块进行解压缩以支持随机访问;
○图片压缩格式基于整张图片进行压缩,无法直接实现单个像素的解析;
○图片压缩格式无法被GPU识别,还需要经CPU解压缩成非压缩纹理格式才能被识别;
●总结
○进行了纹理压缩后,可以减少CPU的消耗,还能发挥GPU性能
三、常见的纹理压缩格式

黄色的为常用格式
非压缩格式

格式
占用
RGBA8888(RGBA32)
32bit(4Byte)/Pixel
RGBA4444(RGBA16)
16bit(2Byte)/Pixel
RGBA888(RGB24)
24bit(3Byte)/Pixel
RGBA565(RGB16)
16bit(2Byte)/Pixel
1.DTXC系列
○DCTC纹理压缩格式来源于S3公司提出的S3TC算法
○基本思想是把4×4的像素块压缩成一个64或128位的数据块
○优点:
■创建了一个固定大小且独立的编码片段
■没有共享查找表或其他依赖关系
■简化了解码过程
DXT1(BC1)
原理:
●将4×4的像素块压缩成了一个64位的数据块,这个64位的数据块中包含:
○其中32位是:
■两个16位RGB(RGB565)颜色。
■这两个RGB颜色是4×4像素块中的两个极端颜色值,然后通过线性插值计算出剩余的两个中间颜色
○剩余的32位
■平均分配给了16个像素作为颜色值的索引值,每个像素占2位
详解:

ACDB
●64位分别为:
●其中32位是:A、B两个16位RGB颜色,它们是极端颜色(格式为RGB565)
○极端颜色通过线性插值得到的中间颜色:C、D
●另外32位是:16个像素的颜色索引值,每个像素占2位(可以是 00 01 10 11,可以分别表示上边的A、B、C、D四种颜色)
注意:
●存储极端颜色的格式是RGB565,也就是说绿(G)通道的精度比其他两个通道精度高一些
○这就是有些人说把信息放绿通道精度更高的原因
○//补充:多出来的精度给绿通道是因为:人眼对绿色更敏感
●DXT1格式适用于不具有透明度信息或者具有一位透明度信息(表示完全透明or完全不透明)的贴图
○对于没有Alpha信息的贴图,压缩遵循上文
○对于有Alpha信息的贴图
■极端颜色插值时,中间颜色只有一个,另一个表示完全透明or完全不透明(例如上述例子中,C为中间颜色,D表示透明信息)
■每个像素索引时,极端颜色+中间颜色表示完全不透明,另外一个表示完全透明
DXT1的压缩率
○参照对象:RGB24(DXT1主要用于没有Alpha信息的贴图)
○DXT1:
■总数据块为64位,16个像素共用 =>一个像素4位
○所以压缩率为:24 / 4 = 6:1
DXT2/3(BC2)
DXT2/3与DXT1类似,表示颜色信息的64位数据块不变,另外附加了64位数据来表示每个像素的Alpha信息,整个数据块变为了128位;
○每个像素占用8位,0-3表示透明信息,4-7表示颜色信息
○其中透明信息没有差值,是给每个像素分配了4位
●DXT2和DXT3的区别:
○DXT2是已经完成了颜色与Alpha的混合,当透明度发生改变时,直接改变整体颜色值,不再单独进行复合
○DXT3的Alpha信息相对独立(分开压缩)
●DXT2、3的压缩率
○参照对象:RGBA(32位)
○总数据块为128位,16个像素共用 =>一个像素8位
○压缩率为:32 / 8 = 4:1
DXT4/5(BC3)
DXT4/5与DXT2/3的差异在于其Alpha信息是通过线性插值所得的,表示颜色信息的64位数据块依然不变
○Alpha信息则由2个8位Alpha极端值和16个3位索引值组成;
●DXT4和DXT5的区别
○与2,3的区别相同
●DXT4、5的压缩率
○也为4:1
扩展知识
●Unity将贴图类型选为法线时,会采用DXTnm格式
○它基于DXT5,会把法线贴图的R通道存入A通道,然后将RB通道清除为1
○这样就可以把法线xy信息分别存入到RGB和A中进行压缩,来获得更高的精度
○最后再根据xy构建出z的信息
ATI系列
ATI1/2(BC4/5)
●ATI1为ATI公司开发的纹理压缩格式,也被称为BC4
○64位
○其每个数据块存储单个颜色的数据通道
○与DXT5中的Alpha数据相同的方式进行编码
○常用于存储高度图、光滑度贴图,效果与原始图像基本无差异;
●ATI2也被称为BC5
○128位
○每一个块中存储两个颜色通道的数据
○同上以与DXT5中Alpha数据相同的方式进行编码
○相当于存储了两个BATI1块;
■优点:如果是在将法线存储在XY双通道中采用BC5格式压缩,由于每个通道都有自己的索引,因此法线贴图XY信息可以比在BC1中保留更多的保真度
■缺点:需要使用两倍内存,也需要更多的带宽才能将纹理传递到着色器中;
●压缩比
○ATI1:
■参照对象:单通道 8位
■总数据块为 64位,16个像素,所以每个像素4位
■压缩比为:8 / 4 =2:1
○ATI2:
■参照对象:两个通道 16位
■总数据块为 128位,16个像素,所以每个像素4位
■压缩比为:16 / 8 = 2:1
BC 6/7
●BC6和BC7仅在D3D11级图形硬件中受支持
○他们每个块占用16字节
●BC6:
○BC6针对RGB半精度浮点数据
○是唯一一个可以原生存储HDR的BC格式
○专门针对HDR(高动态范围)图像设计的压缩算法,压缩比为6:1
●BC7:
○BC7针对8位RGB或RGBA数据
○BC7是专门针对LDR(低动态范围)图像设计的压缩算法,压缩比为3:1
○该格式用于高质量的RGBA压缩,可以显著减少由于压缩法线带来的错误效果
ETC系列
●DirectX选择了DXTC作为标准压缩格式,那对于OpenGL则选择了爱立信研发的ETC格式
●几乎所有的安卓设备都可以支持ETC压缩,所以其在移动平台上被广泛应用;
ETC方案与DXTC具有相同特点,将4×4的像素单元压缩成64位数据块,并将像素单元水平或竖直朝向分为两个区块,将颜色和亮度分开储存,每个像素颜色等于基础颜色加上索引指向的亮度范围;

每个区块中有12位用来存储颜色信息(12*2),16位存储其8个像素的索引(每个像素2位,16*2),4位存储亮度索引(4*2)
ETC1

●每一个分块中有一个3位亮度索引值,可以从8个内置亮度表中选取当前分块所用的亮度表
●多出来的一位(2个子块就2位)对应了了flip(2个块的划分横竖方向)和diff(2*R4G4B4还是R5G5B5+R3G3B3)
●每个亮度表有4个值,每个像素也有1个两位响度索引值,表示0-3可以从4个亮度值中选取对应的亮度补偿值
●最终的颜色 = 12位基础颜色信息 + 亮度补偿值
●补充:
●原理:
○将4×4的像素块编码为2×4或者4×2像素的两个块
○每个块指定一个基色,每个像素的颜色铜鼓偶一个编码为相对于这个基色偏移的灰度值确定(上面提到的亮度)
●位数占比:
○亮度索引3位*2
○像素索引2位*16
○基础颜色12位(444*2,或者555+333)*2
○flip1位(控制水平或者竖直划分)*2
○总位数 = 3*2 + 2*16 + 12*2 + 1*2 = 64位
○//注:*2是因为有两个块,*16是因为有16个像素
●压缩率
●参照标准:RBG24
○总共有64个数据块,针对16个像素,也就是每个像素4位
○压缩比 = 24 / 4 = 6:1
●对于ETC1不支持Alpha通道的解决方案
○采用两张纹理混合的方式
●ETC1的适用情况
○长宽为2的幂次的贴图
○不适用于带透明通道的贴图
○适用于基本所有安卓设备
ETC2
●TEC2是ETC1的扩展,支持了Alpha通道(内存占用大于ETC1)
●硬件要求OpenGL ES3.0和OpenGL4.3以上
AST
●ASTC是由ARM和AMD联合开发的纹理压缩格式,ASTC在各项指标上都挺不错
●优点是
○可根据不同图片选择不同压缩率的算法
○图片尺寸不需要为2的幂次
○同时支持LDR和HDR
●缺点是
○兼容性不够完善且解码时间较长;
○无法在Iphone6以下的设备运行
●ASTC也是基于块的压缩算法,与BC7类似
○其数据块大小固定为128位
○不同的是块中的像素数量可变
○从4×4到12×12像素都有,也不一定要长宽相等
●每一个数据块中存储了两个插值端点,但不一定存储的是颜色信息,也可能是Layer信息,这样可以用来对Normal或Alpha进行更好的压缩;
●对于块中每一个纹素,存储其对应插值端点的权重,存储的权重数量可以少于纹素数量,可通过插值得到每一个纹素的权重值,然后再进行颜色的计算;
●128位数据块中存储的信息:

PVRTC
●PVRTC由Imagination公司专为PowerVR显卡设计,仅支持Iphone、Ipad和部分安卓机;
●不同于DXTC和ETC这类基于块的算法,PVRTC将图像分为了低频信号和高频信号
○低频信号由两张低分辨率图像AB组成
○高频信号则是全分辨率低精度的调制图像,记录了每个像素混合的权重
○解码时AB图像经过双线性插值放大,然后根据调制图像的权重进行混合;
●压缩原理
●分为4-bpp 和 2-bpp(bpp = Bit Per Pixel,即每个像素占的位数)
●4-bpp为例:
○把4×4的像素单元压成一个64位数据块
■64位数据块中包含了A、B两张图(在原图基础上压缩到1/4的低分辨率图像)
■不同模式下每个像素调制数据可以得到不同的混合值,根据这个混合值用A和B混合得出最终颜色值
○位数占比:
■32位的调制数据(2*16)
■1位的调制标志(也称为模式,结合调制数据得出混合值)
■15位的颜色A(555或4443),1位颜色A的不透明标志(决定存储模式)
■14位颜色B(554或4433),1位颜色B的不透明标志
■共计:32 +1 + 16 + 15 = 64位
○压缩率
■以RGB为参照标准
●压缩率 = 24 / (64/16) = 6:1
■以RGBA为参照标准
●压缩率 = 32 / (64/16) = 8:1
●2-bpp
○把一个8×4的像素单元压成了64位数据块
总结

四、实际应用中的选择
PC
●① 低质量使用DXT1格式不支持A通道,使用DXT5格式支持A通道;
●② 高质量使用BC7格式,支持A通道;
安卓
●① 低质量使用ETC1格式,但不支持A通道;
●② 低质量使用ETC2格式,支持A通道,需要在OpenGL ES 3.0/OpenGL 4.3以上版本;
●③ 高质量使用ASTC格式,需要在Android 5.0/OpenGL ES 3.1以上版本;
IOS
●① 高质量使用ASTC格式,需要Iphone6以上版本;
●② 低质量使用PVRTC2格式,支持Iphone6以下版本;
补充
●实际手机端项目中,我们比较常用ASTC(安卓和IOS通用)
●英伟达和Unity官方对于不同类型贴图给出了不同的压缩方案建议,感兴趣的同学可以看下:
○Using ASTC Texture Compression for Game Assets | NVIDIA Developer
○Unity - Manual: Recommended, default, and supported texture compression formats, by platform (unity3d.com)
作业:
针对ASTC与ETC2两个格式进行打包测试,分析内存占用对比
测试使用SD中自带的材质导出的贴图

RGB24bit


贴图大小4096x4096采用RGB24bit,64MB大小,安装包大小66.4MB
ETC2


贴图大小4096x4096采用ETC2压缩,10.7MB大小(1:6),安装包大小28.4MB
ASTC4x4


贴图大小4096x4096采用 ASTC4x4 压缩,21.3MB大小(1:3),安装包大小40.6MB
ASTC12x12


贴图大小4096x4096采用 ASTC12x12 压缩,21.3MB大小(1:26.7),安装包大小2.4MB(采用这个等级的压缩会对精细的贴图造成一定的影响)
数据对比
首先是包体大小的对比,从上面的数据也可以看出,是否进行压缩差别还是很大的,不过最重要的还是实际的测试情况

依然采用UPR的华为P50进行测试,可以看到在TextureSize这一块,压缩后的纹理该项数值明显减少
下图分别为
RAG24 ETC ASTC4x4 ASTC12x12
