转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3246

DXT到了D3D10之后,就改名为BC(Block Compression)。到了D3D11/OpenGL 4.2时代,又增加了BC6和BC7两种高效的压缩法,分别针对HDR和LDR。但是,由于BC6/7的压缩复杂度远超过以往的BC1-5,D3DX的压缩函数又不是特别给力,限制了它的应用。本系列将专注于介绍一个BC7的快速压缩算法。

和其他BC的比较

BC1的只能用于RGB565,或者RGBA5551。压缩质量不高但速度很快。BC2和BC3的RGB部分和BC1完全一样,都是RGB565。BC2的A是4bit采样,BC3的A是8bit插值。如果要用他们来压缩非颜色信息,比如normal,结果基本都不怎么样。因为最好的BC3也只能提供一个8bit通道和一个6bit通道。

BC4和BC5只有1通道和2通道。BC5在用于normal压缩的时候能提供2个8bit通道,由于使用BC3。

而BC7的颜色bit数会随着mode的不同而不同,最多提供4个7bit通道和一个共享bit。适用于高质量的颜色压缩,带不带alpha均可,normal也可以。误差比BC1/2/3降低8-10倍。

BC7的结构

和BC1-5一样,BC7也是把图片分成4×4的block。但BC1的单线性拟合对于很多情况来说,质量损失严重。举个最简单的例子,如果一个4×4的 block中包含红色、蓝色、绿色的三个像素,用BC1压缩后必然会有个颜色消失,因为这三种颜色在RGB空间中是无法拟合成一条直线的。

BC7引入了subset的概念,把4×4进一步分成最多3个子集。每个子集类似于BC1,选出两个端点颜色,其他像素编码成端点的插值。按照排列组合来说,这样会有3^(4×4)种划分方式。而BC7在设计的时候对此进行了选择,选出了64种划分方式,称为partition。前32种partition和BC6共享,如下图所示:

其中0和1属于不同的subset。任意一个4×4的block,都需要指派到某个partition上。

确定了partition之后,下一个需要确定的是mode。BC7一共有8种mode。每一种mode有自己的编码方式,精确定义每一个bit的作用,已达到压缩的目的。有的mode有1个subset,有的有2个,有的有3个;有的mode有16个partition,有的有64个;有的mode支持alpha,有的不支持……

确定了mode之后,还需要确定颜色的编码方式和rotation方式。在BC6/7中,每个subset的两个端点不一定是直接编码,也可能是拿第一个颜色和两个颜色之差来编码。如果不溢出的话,表示成查分可以得到更好的压缩效果。同时,在BC6/7中,在mode 4和5的时候,颜色的排列顺序不一定是RGBA,每个块有2个bit专门用来表示哪个通道和A做了交换。前三个通道是一起编码的,最后一个通道是独立编码的。如果某个通道和A做了交换后,质量更高,就会选择这样的交换,称为rotation。

把所有这些分支都考虑起来的话,就会发现每个4×4的block会有几百甚至上千种编码的选择。因此,BC7的编码不可能像BC1那样简单快速。

传统做法

古老的D3DX11系列函数提供了压缩BC6和BC7的方法,但它用的是穷举。通过把任意一个4×4的块用所有可能的编码方式全都编码一遍,解码回来,算误差,找一个误差最小的。这么暴力穷举固然能找到最高质量的编码方式,但时间消耗也是惊人的。由于某种原因,D3DX11的BC6/7压缩既没有使用多线程,有没有使用SIMD。结果就是压缩一个1024×1024的图片需要几十分钟。DirectXTex的CPU压缩加了block之间的多线程,把时间消耗降低到了几分钟。而来自于BC6H/BC7 DirectCompute Encoder Tool的GPU方法(这是我们几年前为DX SDK做的一个sample,现在已经集成到DirectXTex中)能在block之间和block之内都并行,压缩一张图只要一两秒钟。即便如此,算法框架没什么改变,还是需要穷举所有的编码方式,以至于效率很低。

下一篇我会介绍一个称为FasTC的框架,可以显著提高对组合的选择。