BC7 格式

BC7 格式是一种用于对 RGB 和 RGBA 数据进行高质量压缩的纹理压缩格式。

有关 BC7 格式的块模式的信息,请参阅 BC7 格式模式参考

关于 BC7/DXGI_FORMAT_BC7

BC7 由以下 DXGI_FORMAT 枚举值指定:

  • DXGI_FORMAT_BC7_TYPELESS.
  • DXGI_FORMAT_BC7_UNORM.
  • DXGI_FORMAT_BC7_UNORM_SRGB.

BC7 格式可用于 Texture2D(包括阵列)、Texture3D 或 TextureCube(包括阵列)纹理资源。 同样,此格式适用于与这些资源相关联的任何 MIP 贴图表面。

BC7 使用 16 字节(128 位)的固定块大小和 4×4 纹素的固定磁贴大小。 与先前的 BC 格式一样,大于支持的磁贴大小 (4x4) 的纹理图像会通过使用多个块进行压缩。 此寻址标识也适用于三维图像和 MIP 贴图、立方体贴图和纹理阵列。 所有图像磁贴必须使用相同的格式。

BC7 可对三通道 (RGB) 和四通道 (RGBA) 固定点数据图像进行压缩。 通常,源数据是每颜色分量(通道)8 位,尽管该格式能够以每颜色分量更高位对源数据进行编码。 所有图像磁贴必须使用相同的格式。

BC7 解码器在应用纹理筛选之前执行解压缩。

BC7 解压缩硬件必须是位精确的;也就是说,该硬件返回的结果必须与此文档中所述的解码器返回的结果相同。

BC7 实现

BC7 实现可以使用在 16 字节(128 位)块的最低有效位中指定的模式指定 8 种模式之一。 该模式由零或更多位进行编码,使用值 0,后面跟着 1。

BC7 块可以包含多个终结点对。 出于本文档的目的,对应于终结点对的索引集可称为“子集”。此外,在某些块模式下,终结点表示形式编码为 ,同样,就本文档而言,应称为“RGBP”,其中“P”位表示终结点颜色分量中共享的最小有效位。 例如,如果该格式的终结点表示是“RGB 5.5.5.1”,则会将终结点解释为一个 RGB 6.6.6 值,其中 P 位的状态定义每个分量的最低有效位。 同样,对于使用某个 alpha 通道的源数据,如果该格式的表示是“RGBAP 5.5.5.5.1”,那么会将该终结点解释为 RGBA 6.6.6.6。 根据块模式,你可以单独为某个子集的全部两个终结点指定共享最低有效位(每个子集 2 个 P 位),或者为在某个子集的终结点之间共享的终结点指定共享最低有效位(每个子集 1 个 P 位)。

对于未显式编码 alpha 分量的 BC7 块,一个 BC7 块由多个模式位、多个分区位、多个压缩的终结点、多个压缩的索引和一个可选 P 位组成。 在这些块中,终结点有一个仅 RGB 表示,并且对于源数据中的所有纹素,alpha 分量被解码为 1.0。

对于已组合了颜色分量和 alpha 分量的 BC7 块,一个块由多个模式位、多个压缩的终结点、多个压缩的索引以及多个可选分区位和一个 P 位组成。 在这些块中,终结点颜色是用 RGBA 格式表示的,而 alpha 分量值会与颜色分量值一起以内插值替换。

对于具有单独的颜色和 alpha 分量的 BC7 块,一个块由多个模式位、多个旋转位、多个压缩的终结点、多个压缩的索引和一个可选索引选择器位组成。 这些块具有单独编码的有效 RG B向量 [R, G, B] 和标量 alpha 通道 [A]。

下表列出了每个块类型的分量。

BC7 块包含的项目 多个模式位 多个旋转位 索引选择器位 多个分区位 多个压缩终结点 P 位 多个压缩的索引
仅颜色分量 必需 空值 空值 必需 必需 可选 必需
组合的颜色 + alpha 必需 空值 空值 可选 必需 可选 必需
单独的颜色和 alpha 必需 必需 可选 空值 必需 空值 必需

 

BC7 在两个终结点之间的近似线上定义一个调色板。 该模式值确定每个块的内插终结点对的数量。 BC7 为每个纹素存储一个调色板索引。

对于与一对终结点对应的每个索引子集,编码器会修复该子集的压缩索引数据的一位的状态。 它完成此操作的方法是:选择一个终结点顺序,该顺序允许指定的“固定”索引的索引将其最高有效位设置为 0,然后就可以丢弃该最高有效位,为每个子集保存一位。 对于仅有一个子集的块模式,固定索引始终为索引 0。

解码 BC7 格式

以下伪代码概述了对于 16 字节 BC7 块,解压缩 (x,y) 处的像素的步骤。

decompress_bc7(x, y, block)
{
    mode = extract_mode(block);
    
    //decode partition data from explicit partition bits
    subset_index = 0;
    num_subsets = 1;
    
    if (mode.type == 0 OR == 1 OR == 2 OR == 3 OR == 7)
    {
        num_subsets = get_num_subsets(mode.type);
        partition_set_id = extract_partition_set_id(mode, block);
        subset_index = get_partition_index(num_subsets, partition_set_id, x, y);
    }
    
    //extract raw, compressed endpoint bits
    UINT8 endpoint_array[2 * num_subsets][4] = extract_endpoints(mode, block);
    
    //decode endpoint color and alpha for each subset
    fully_decode_endpoints(endpoint_array, mode, block);
    
    //endpoints are now complete.
    UINT8 endpoint_start[4] = endpoint_array[2 * subset_index];
    UINT8 endpoint_end[4]   = endpoint_array[2 * subset_index + 1];
        
    //Determine the palette index for this pixel
    alpha_index     = get_alpha_index(block, mode, x, y);
    alpha_bitcount  = get_alpha_bitcount(block, mode);
    color_index     = get_color_index(block, mode, x, y);
    color_bitcount  = get_color_bitcount(block, mode);

    //determine output
    UINT8 output[4];
    output.rgb = interpolate(endpoint_start.rgb, endpoint_end.rgb, color_index, color_bitcount);
    output.a   = interpolate(endpoint_start.a,   endpoint_end.a,   alpha_index, alpha_bitcount);
    
    if (mode.type == 4 OR == 5)
    {
        //Decode the 2 color rotation bits as follows:
        // 00 – Block format is Scalar(A) Vector(RGB) - no swapping
        // 01 – Block format is Scalar(R) Vector(AGB) - swap A and R
        // 10 – Block format is Scalar(G) Vector(RAB) - swap A and G
        // 11 - Block format is Scalar(B) Vector(RGA) - swap A and B
        rotation = extract_rot_bits(mode, block);
        output = swap_channels(output, rotation);
    }
    
}

以下伪代码概述了在给定 16 字节 BC7 块的情况下,针对每个子集完全解码终结点颜色和 alpha 组件的步骤。

fully_decode_endpoints(endpoint_array, mode, block)
{
    //first handle modes that have P-bits
    if (mode.type == 0 OR == 1 OR == 3 OR == 6 OR == 7)
    {
        for each endpoint i
        {
            //component-wise left-shift
            endpoint_array[i].rgba = endpoint_array[i].rgba << 1;
        }
        
        //if P-bit is shared
        if (mode.type == 1) 
        {
            pbit_zero = extract_pbit_zero(mode, block);
            pbit_one = extract_pbit_one(mode, block);
            
            //rgb component-wise insert pbits
            endpoint_array[0].rgb |= pbit_zero;
            endpoint_array[1].rgb |= pbit_zero;
            endpoint_array[2].rgb |= pbit_one;
            endpoint_array[3].rgb |= pbit_one;  
        }
        else //unique P-bit per endpoint
        {  
            pbit_array = extract_pbit_array(mode, block);
            for each endpoint i
            {
                endpoint_array[i].rgba |= pbit_array[i];
            }
        }
    }

    for each endpoint i
    {
        // Color_component_precision & alpha_component_precision includes pbit
        // left shift endpoint components so that their MSB lies in bit 7
        endpoint_array[i].rgb = endpoint_array[i].rgb << (8 - color_component_precision(mode));
        endpoint_array[i].a = endpoint_array[i].a << (8 - alpha_component_precision(mode));

        // Replicate each component's MSB into the LSBs revealed by the left-shift operation above
        endpoint_array[i].rgb = endpoint_array[i].rgb | (endpoint_array[i].rgb >> color_component_precision(mode));
        endpoint_array[i].a = endpoint_array[i].a | (endpoint_array[i].a >> alpha_component_precision(mode));
    }
        
    //If this mode does not explicitly define the alpha component
    //set alpha equal to 1.0
    if (mode.type == 0 OR == 1 OR == 2 OR == 3)
    {
        for each endpoint i
        {
            endpoint_array[i].a = 255; //i.e. alpha = 1.0f
        }
    }
}

要为每个子集生成每个内插分量,请使用以下算法:让“c”成为要生成的分量;让“e0”成为子集的终结点 0 的分量;并让“e1”成为子集的终结点 1 的分量。

UINT16 aWeights2[] = {0, 21, 43, 64};
UINT16 aWeights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
UINT16 aWeights4[] = {0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64};

UINT8 interpolate(UINT8 e0, UINT8 e1, UINT8 index, UINT8 indexprecision)
{
    if(indexprecision == 2)
        return (UINT8) (((64 - aWeights2[index])*UINT16(e0) + aWeights2[index]*UINT16(e1) + 32) >> 6);
    else if(indexprecision == 3)
        return (UINT8) (((64 - aWeights3[index])*UINT16(e0) + aWeights3[index]*UINT16(e1) + 32) >> 6);
    else // indexprecision == 4
        return (UINT8) (((64 - aWeights4[index])*UINT16(e0) + aWeights4[index]*UINT16(e1) + 32) >> 6);
}

以下伪代码说明如何提取颜色分量和 alpha 分量的索引和位计数。 具有单独的颜色和 alpha 的块也有两组索引数据:一组用于矢量通道,一组用于标量通道。 对于模式 4,这些索引具有不同的宽度(2 位或 3 位),并且存在一个一位选择器,该选择器指定矢量或标量数据是否使用 3 位索引。 (提取 alpha 位计数类似于提取颜色位计数,但根据 idxMode 位,其行为是相反的。)

bitcount get_color_bitcount(block, mode)
{
    if (mode.type == 0 OR == 1)
        return 3;
    
    if (mode.type == 2 OR == 3 OR == 5 OR == 7)
        return 2;
    
    if (mode.type == 6)
        return 4;
        
    //The only remaining case is Mode 4 with 1-bit index selector
    idxMode = extract_idxMode(block);
    if (idxMode == 0)
        return 2;
    else
        return 3;
}

Direct3D 11 中的纹理块压缩