块压缩 (Direct3D 10)

块压缩是一种纹理压缩技术,用于减少纹理大小。 与每种颜色 32 位的纹理相比,块压缩纹理最多可以小 75%。 由于块压缩的内存占用量较少,因此使用块压缩时,应用程序的性能通常会提高。

尽管有损,但块压缩运行良好并推荐用于按管道转换并筛选的所有纹理。 由于项目更为明显,因此直接映射到屏幕的纹理(如图标和文本等 UI 元素)对于压缩而言不是很好的选择。

在所有尺寸上创建的块压缩纹理必须是大小 4 的倍数,且不能用作管道的输出。

块压缩的工作原理是什么?

块压缩是一种用来减少存储颜色数据所需的内存的技术。 通过将某些颜色按其原始大小存储,而其他颜色使用编码模式,你可以大幅减少存储图像所需的内存。 由于硬件会自动解码压缩的数据,因此使用压缩的纹理不会导致性能下降。

若要了解压缩的工作原理,请看下面两个示例。 第一个示例介绍存储未压缩的数据所用的内存量;第二个示例介绍存储压缩的数据所用的内存量。

存储未压缩的数据

下图显示了未压缩的 4×4 纹理。 假设每种颜色包含一个单色分量(例如红色),并且存储在一字节的内存中。

未压缩的 4x4 纹理插图

未压缩的数据在内存中按顺序排列且需要 16 字节,如下图所示。

顺序内存中未压缩数据的插图

存储压缩数据

你已经了解了未压缩的图像使用的内存量,现在来看看压缩的图像节省的内存量。 BC4 压缩格式存储 2 种颜色 (每个) 1 个字节和 16 个 3 位索引 (48 位,或 6 个字节) ,用于在纹理中插入原始颜色,如下图所示。

bc4 压缩格式的插图

存储压缩数据所需的空间总量为 8 字节,与未压缩的示例相比,可节省 50% 的内存量。 如果使用多个颜色分量,节省的内存量会更多。

块压缩节省的大量内存可以让性能提升。 此性能是以牺牲图像质量(因颜色内插导致)为代价的;不过,图像质量的降低通常不是很明显。

下一部分介绍如何通过 Direct3D 10 轻松地在应用程序中使用块压缩。

使用块压缩

创建块压缩纹理,就像未压缩纹理一样 (请参阅 从文件) 创建纹理 ,但指定块压缩格式除外。

loadInfo.Format = DXGI_FORMAT_BC1_UNORM;
D3DX10CreateTextureFromFile(...);

接下来,创建一个视图以将纹理绑定到管道。 由于块压缩纹理只能用作着色器阶段的输入,因此需要通过调用 CreateShaderResourceView 来创建着色器资源视图。

请以使用未压缩纹理的方式来使用块压缩纹理。 如果你的应用程序将获取指向块压缩数据的内存指针,你需要考虑 mipmap 中的内存填充,它会导致声明的大小与实际大小不同。

虚拟大小与物理大小

如果你的应用程序代码使用内存指针遍历块压缩纹理的内存,则你的应用程序代码中会有一个可能需要修改的重要事项。 因为块压缩算法在 4x4 纹素块上操作,所以所有尺寸中的块压缩纹理必须是 4 的倍数。 对于原始尺寸可以被 4 整除、但细分尺寸无法被 4 整除的 mipmap,这将是个问题。 下图显示了每个 mipmap 级别的虚拟(声明)大小和物理(实际)大小之间的区域差。

未压缩和压缩 mipmap 级别的示意图

图示左侧显示为未压缩的 60×40 纹理生成的 mipmap 级别大小。 最高级别的大小取自生成纹理的 API 调用;每个后续级别是上一个级别大小的一半。 对于未压缩的纹理,虚拟(声明)大小和物理(实际)大小之间没有区别。

图示右侧显示了为使用压缩的同一 60×40 纹理生成的 mipmap 级别大小。 注意,第二个和第三个级别均有内存填充,以使每个级别上的大小为 4 的因数。 这很有必要,以便算法可以在 4×4 纹素块上操作。 这在你考虑小于 4×4 的 mipmap 级别时尤为明显;分配纹理内存时,这些非常小的 mipmap 级别的大小将四舍五入为最接近 4 的因数。

采样硬件使用虚拟大小;如果对纹理采样,将忽略内存填充。 对于小于 4×4 的 mipmap 级别,只有前四个纹素将用于 2×2 的映射,并且只有第一个纹素将由 1×1 的块使用。 但是,没有公开物理大小(包括内存填充)的 API 结构。

总之,复制包含块压缩数据的区域时,请小心使用对齐的内存块。 若要在获取内存指针的应用程序中执行此操作,请确保指针使用图面间距以考虑物理内存大小。

压缩算法

Direct3D 中的块压缩技术将未压缩的纹理数据分成 4×4 的块,压缩每个块,然后存储数据。 因此,预期要压缩的纹理尺寸必须是 4 的倍数。

块压缩图

上图显示了一个纹理分成多个纹素块。 第一个块显示了标记为 a-p 的 16 个纹素的布局,但是每个块的数据组织相同。

Direct3D 实施了多个压缩方案,每个方案在存储的分量数、每个分量的位数,以及使用的内存量之间进行了不同的权衡。 使用此表可帮助选择最适合数据类型的格式以及最适合你的应用程序的数据分辨率。

源数据 数据压缩分辨率(位) 选择此压缩格式
三分量颜色和 alpha 颜色 (5:6:5),Alpha (1) 或没有 alpha BC1
三分量颜色和 alpha 颜色 (5:6:5),Alpha (4) BC2
三分量颜色和 alpha 颜色 (5:6:5),Alpha (8) BC3
单分量颜色 单分量 (8) BC4
双分量颜色 双分量 (8:8) BC5

 

BC1

使用第一个块压缩格式 (BC1)(DXGI_FORMAT_BC1_TYPELESS、DXGI_FORMAT_BC1_UNORM 或 DXGI_BC1_UNORM_SRGB)来以 5:6:5 颜色(5 位红色、6 位绿色、5 位蓝色)存储三分量颜色数据。 即使该数据还包含 1 位 alpha 也是如此。 假设 4×4 纹理使用最大的数据格式,BC1 格式会将所需的内存从 48 内存字节(16 种颜色 × 3 个分量/颜色 × 1 字节/分量)减少到 8 内存字节。

该算法在 4×4 纹素块上操作。 该算法并非存储 16 种颜色,而是保存 2 种参考颜色(color_0 和 color_1)和16 个 2 位颜色指数(块 a–p),如下图所示。

bc1 压缩布局示意图

颜色指数 (a–p) 用于从颜色表中查找原始颜色。 颜色表包含 4 种颜色。 前两种颜色(color_0 和 color_1)分别是最小色和最大色。 其他两种颜色(color_2 和 color_3)是使用线性内插计算的中间色。

color_2 = 2/3*color_0 + 1/3*color_1
color_3 = 1/3*color_0 + 2/3*color_1

这四种颜色会获分 2 位指数值,指数值将保存在块 a–p 中。

color_0 = 00
color_1 = 01
color_2 = 10
color_3 = 11

最后,会将块 a–p 中的颜色与颜色表中的这四种颜色进行比较,最接近的颜色的指数将存储在 2 位的块中。

该算法还会将自己借给包含 1 位 alpha 的数据。 唯一区别是 color_3 设置为 0(代表透明色),color_2 是 color_0 和 color_1 的线性混合。

color_2 = 1/2*color_0 + 1/2*color_1;
color_3 = 0;

Direct3D 9 和 Direct3D 10 之间的差异:

此格式同时存在于 Direct3D 9 和 10 中。

  • Direct3D 9 中,BC1 格式称为 D3DFMT_DXT1
  • Direct3D 10 中,BC1 格式由 DXGI_FORMAT_BC1_UNORMDXGI_FORMAT_BC1_UNORM_SRGB 表示。

BC2

使用 BC2 格式(DXGI_FORMAT_BC2_TYPELESS、DXGI_FORMAT_BC2_UNORM 或 DXGI_BC2_UNORM_SRGB)来存储包含颜色的数据和低一致性的 alpha 数据(对于高一致性的 alpha 数据则使用 BC3)。 BC2 格式将 RGB 数据存储为 5:6:5 颜色(5 位红色、6 位绿色、5 位蓝色),将 alpha 存储为单独的 4 位值。 假设 4×4 纹理使用最大的数据格式,该压缩技术会将所需的内存从 64 内存字节(16 种颜色 × 4 分量/颜色 × 1 字节/分量)减少为 16 内存字节。

BC2 格式将具有相同位数和数据布局的颜色存储为 BC1 格式;但是,BC2 需要额外 64 位内存来存储 alpha 数据,如下图所示。

bc2 压缩布局图

Direct3D 9 和 Direct3D 10 之间的差异:

此格式同时存在于 Direct3D 9 和 10 中。

  • Direct3D 9 中,BC2 格式称为 D3DFMT_DXT2D3DFMT_DXT3

  • Direct3D 10 中,BC2 格式由 DXGI_FORMAT_BC2_UNORMDXGI_FORMAT_BC2_UNORM_SRGB

BC3

使用 BC3 格式(DXGI_FORMAT_BC3_TYPELESS、DXGI_FORMAT_BC3_UNORM 或 DXGI_BC3_UNORM_SRGB)来存储高一致性的颜色数据(对于一致性较低的 alpha 数据则使用 BC2)。 BC3 格式使用 5:6:5 颜色(5 位红色、6 位绿色、5 位蓝色)存储颜色数据,使用 1 字节存储 alpha 数据。 假设 4×4 纹理使用最大的数据格式,该压缩技术会将所需的内存从 64 内存字节(16 种颜色 × 4 分量/颜色 × 1 字节/分量)减少为 16 内存字节。

BC3 格式将具有相同位数和数据布局的颜色存储为 BC1 格式;但是,BC3 需要额外的 64 位内存来存储 alpha 数据。 BC3 格式通过存储两个参考值并在它们之间进行内插(类似于 BC1 存储 RGB 颜色的方式)来处理 alpha。

该算法在 4×4 纹素块上操作。 该算法并非存储 16 个 alpha 值,而是存储 2 个参考 alpha(alpha_0 和 alpha_1)和 16 个 3 位颜色指数(alpha a 至 p),如下图所示。

bc3 压缩布局示意图

BC3 格式使用 alpha 指数 (a–p) 从包含 8 个值的查找表中查找原始颜色。 前两个值(alpha_0 和 alpha_1)分别是最小值和最大值;其他 6 个中间值是通过线性内插计算所得。

该算法通过检查两个参考 alpha 值确定内插的 alpha 值的数量。 如果 alpha_0 大于 alpha_1,则 BC3 内插 6 个 alpha 值;否则,内插 4 个。 当 BC3 仅内插 4 个 alpha 值时,它会设置两个额外的 alpha 值(0 表示完全透明,255 表示完全不透明)。 BC3 通过存储与最匹配指定纹理的原始 alpha 的内插 alpha 值对应的位代码,将 alpha 值压缩在 4×4 纹理区域中。

if( alpha_0 > alpha_1 )
{
  // 6 interpolated alpha values.
  alpha_2 = 6/7*alpha_0 + 1/7*alpha_1; // bit code 010
  alpha_3 = 5/7*alpha_0 + 2/7*alpha_1; // bit code 011
  alpha_4 = 4/7*alpha_0 + 3/7*alpha_1; // bit code 100
  alpha_5 = 3/7*alpha_0 + 4/7*alpha_1; // bit code 101
  alpha_6 = 2/7*alpha_0 + 5/7*alpha_1; // bit code 110
  alpha_7 = 1/7*alpha_0 + 6/7*alpha_1; // bit code 111
}
else
{
  // 4 interpolated alpha values.
  alpha_2 = 4/5*alpha_0 + 1/5*alpha_1; // bit code 010
  alpha_3 = 3/5*alpha_0 + 2/5*alpha_1; // bit code 011
  alpha_4 = 2/5*alpha_0 + 3/5*alpha_1; // bit code 100
  alpha_5 = 1/5*alpha_0 + 4/5*alpha_1; // bit code 101
  alpha_6 = 0;                         // bit code 110
  alpha_7 = 255;                       // bit code 111
}

Direct3D 9 和 Direct3D 10 之间的差异:

  • Direct3D 9 中,BC3 格式称为 D3DFMT_DXT4D3DFMT_DXT5

  • Direct3D 10 中,BC3 格式由 DXGI_FORMAT_BC3_UNORMDXGI_FORMAT_BC3_UNORM_SRGB 表示。

BC4

使用 BC4 格式存储单分量颜色数据(每种颜色使用 8 位)。 由于准确度更高(与 BC1 相比),因此 BC4 是使用 DXGI_FORMAT_BC4_UNORM 格式存储 [0 到 1] 范围内的浮点数据以及使用 DXGI_FORMAT_BC4_SNORM 格式存储 [-1 到 +1] 范围内的浮点数据的理想之选。 假设 4×4 纹理使用最大的数据格式,该压缩技术会将所需的内存从 16 字节(16 种颜色 × 1 分量/颜色 × 1 字节/分量)减少为 8 字节。

该算法在 4×4 纹素块上操作。 该算法并非存储 16 种颜色,而是存储 2 种参考颜色(red_0 和 red_1)和 16 个 3 位颜色指数(红色 a 至红色 p),如下图所示。

bc4 压缩布局图

该算法使用 3 位指数从包含 8 种颜色的颜色表中查找颜色。 前两种颜色(red_0 和 red_1)分别是最小色和最大色。 该算法使用线性内插计算剩余颜色。

该算法通过检查两个参考值确定内插的颜色值的数量。 如果 red_0 大于 red_1,BC4 内插 6 个颜色值;否则,内插 4 个。 如果 BC4 仅内插 4 个颜色值,它将设置两个额外的颜色值(0.0f 表示完全透明,1.0f 表示完全不透明)。 BC4 通过存储与最匹配指定纹理的原始 alpha 的内插 alpha 值对应的位代码,将 alpha 值压缩在 4×4 纹理区域中。

BC4_UNORM

以下代码示例显示了单分量数据的内插完成情况。

unsigned word red_0, red_1;

if( red_0 > red_1 )
{
  // 6 interpolated color values
  red_2 = (6*red_0 + 1*red_1)/7.0f; // bit code 010
  red_3 = (5*red_0 + 2*red_1)/7.0f; // bit code 011
  red_4 = (4*red_0 + 3*red_1)/7.0f; // bit code 100
  red_5 = (3*red_0 + 4*red_1)/7.0f; // bit code 101
  red_6 = (2*red_0 + 5*red_1)/7.0f; // bit code 110
  red_7 = (1*red_0 + 6*red_1)/7.0f; // bit code 111
}
else
{
  // 4 interpolated color values
  red_2 = (4*red_0 + 1*red_1)/5.0f; // bit code 010
  red_3 = (3*red_0 + 2*red_1)/5.0f; // bit code 011
  red_4 = (2*red_0 + 3*red_1)/5.0f; // bit code 100
  red_5 = (1*red_0 + 4*red_1)/5.0f; // bit code 101
  red_6 = 0.0f;                     // bit code 110
  red_7 = 1.0f;                     // bit code 111
}

参考颜色将会获分 3 位指数(000–111,因为有 8 个值),在压缩过程中将会保存在红色 a 块至红色 p 块中。

BC4_SNORM

DXGI_FORMAT_BC4_SNORM 完全相同,以 SNORM 范围编码数据和内插 4 个颜色值时除外。 以下代码示例显示了单分量数据的内插完成情况。

signed word red_0, red_1;

if( red_0 > red_1 )
{
  // 6 interpolated color values
  red_2 = (6*red_0 + 1*red_1)/7.0f; // bit code 010
  red_3 = (5*red_0 + 2*red_1)/7.0f; // bit code 011
  red_4 = (4*red_0 + 3*red_1)/7.0f; // bit code 100
  red_5 = (3*red_0 + 4*red_1)/7.0f; // bit code 101
  red_6 = (2*red_0 + 5*red_1)/7.0f; // bit code 110
  red_7 = (1*red_0 + 6*red_1)/7.0f; // bit code 111
}
else
{
  // 4 interpolated color values
  red_2 = (4*red_0 + 1*red_1)/5.0f; // bit code 010
  red_3 = (3*red_0 + 2*red_1)/5.0f; // bit code 011
  red_4 = (2*red_0 + 3*red_1)/5.0f; // bit code 100
  red_5 = (1*red_0 + 4*red_1)/5.0f; // bit code 101
  red_6 = -1.0f;                     // bit code 110
  red_7 =  1.0f;                     // bit code 111
}

参考颜色将会获分 3 位指数(000–111,因为有 8 个值),在压缩过程中将会保存在红色 a 块至红色 p 块中。

BC5

使用 BC5 格式存储双分量颜色数据(每种颜色使用 8 位)。 由于准确度更高(与 BC1 相比),因此 BC5 是使用 DXGI_FORMAT_BC5_UNORM 格式存储 [0 到 1] 范围内的浮点数据以及使用 DXGI_FORMAT_BC5_SNORM 格式存储 [-1 到 +1] 范围内的浮点数据的理想之选。 假设 4×4 纹理使用最大的数据格式,该压缩技术会将所需的内存从 32 字节(16 种颜色 × 2 分量/颜色 × 1 字节/分量)减少为 16 字节。

该算法在 4×4 纹素块上操作。 该算法并非为每个分量存储 16 种颜色,而是为每个分量存储 2 种参考颜色(red_0、red_1、green_0 和 green_1)和 16 个 3 位颜色指数(红色 a 至红色 p 以及绿色 a 至绿色 p),如下图所示。

bc5 压缩布局图

该算法使用 3 位指数从包含 8 种颜色的颜色表中查找颜色。 前两种颜色(red_0 和 red_1,或者 green_0 和 green_1)分别是最小色和最大色。 该算法使用线性内插计算剩余颜色。

该算法通过检查两个参考值确定内插的颜色值的数量。 如果 red_0 大于 red_1,BC5 内插 6 个颜色值;否则,内插 4 个。 如果 BC5 仅内插 4 个颜色值,它将设置 0.0f 和 1.0f 上的其余两个颜色值。

BC5_UNORM

以下代码示例显示了单分量数据的内插完成情况。 绿色分量的计算类似。

unsigned word red_0, red_1;

if( red_0 > red_1 )
{
  // 6 interpolated color values
  red_2 = (6*red_0 + 1*red_1)/7.0f; // bit code 010
  red_3 = (5*red_0 + 2*red_1)/7.0f; // bit code 011
  red_4 = (4*red_0 + 3*red_1)/7.0f; // bit code 100
  red_5 = (3*red_0 + 4*red_1)/7.0f; // bit code 101
  red_6 = (2*red_0 + 5*red_1)/7.0f; // bit code 110
  red_7 = (1*red_0 + 6*red_1)/7.0f; // bit code 111
}
else
{
  // 4 interpolated color values
  red_2 = (4*red_0 + 1*red_1)/5.0f; // bit code 010
  red_3 = (3*red_0 + 2*red_1)/5.0f; // bit code 011
  red_4 = (2*red_0 + 3*red_1)/5.0f; // bit code 100
  red_5 = (1*red_0 + 4*red_1)/5.0f; // bit code 101
  red_6 = 0.0f;                     // bit code 110
  red_7 = 1.0f;                     // bit code 111
}

参考颜色将会获分 3 位指数(000–111,因为有 8 个值),在压缩过程中将会保存在红色 a 块至红色 p 块中。

BC5_SNORM

DXGI_FORMAT_BC5_SNORM 完全相同(以 SNORM 范围编码数据和内插 4 个数据值时除外),两个额外的值为 -1.0f 和 1.0f。 以下代码示例显示了单分量数据的内插完成情况。 绿色分量的计算类似。

signed word red_0, red_1;

if( red_0 > red_1 )
{
  // 6 interpolated color values
  red_2 = (6*red_0 + 1*red_1)/7.0f; // bit code 010
  red_3 = (5*red_0 + 2*red_1)/7.0f; // bit code 011
  red_4 = (4*red_0 + 3*red_1)/7.0f; // bit code 100
  red_5 = (3*red_0 + 4*red_1)/7.0f; // bit code 101
  red_6 = (2*red_0 + 5*red_1)/7.0f; // bit code 110
  red_7 = (1*red_0 + 6*red_1)/7.0f; // bit code 111
}
else
{
  // 4 interpolated color values
  red_2 = (4*red_0 + 1*red_1)/5.0f; // bit code 010
  red_3 = (3*red_0 + 2*red_1)/5.0f; // bit code 011
  red_4 = (2*red_0 + 3*red_1)/5.0f; // bit code 100
  red_5 = (1*red_0 + 4*red_1)/5.0f; // bit code 101
  red_6 = -1.0f;                    // bit code 110
  red_7 =  1.0f;                    // bit code 111
}

参考颜色将会获分 3 位指数(000–111,因为有 8 个值),在压缩过程中将会保存在红色 a 块至红色 p 块中。

使用 Direct3D 10.1 进行格式转换

Direct3D 10.1 支持在预结构化类型纹理和相同位宽度的块压缩纹理之间进行复制。 可实现此目的的函数是 CopyResourceCopySubresourceRegion

从 Direct3D 10.1 开始,可以使用 CopyResourceCopySubresourceRegion 在几个格式类型之间复制。 此类复制操作执行一种将资源数据重新解释为不同格式类型的格式转换。 看看下面的示例,它介绍了采用更典型的转换的行为类型重新解释数据之间的差异:

    FLOAT32 f = 1.0f;
    UINT32 u;

若要将“f”重新解释为“u”类型,请使用 memcpy

    memcpy( &u, &f, sizeof( f ) ); // ‘u’ becomes equal to 0x3F800000.

在上一重新解释中,数据的基础值不变;memcpy 将浮点数重新解释为无符号整数。

若要执行更典型的转换类型,请使用分配:

    u = f; // ‘u’ becomes 1.

在上一转换中,数据的基础值发生变化。

下表列出了你可以在这种重新解释类型的格式转换中使用的允许源和目标格式。 你必须对值进行正确编码,重新解释才能按预期运行。

位宽 未压缩的资源 块压缩资源
32 DXGI_FORMAT_R32_UINT
DXGI_FORMAT_R32_SINT
DXGI_FORMAT_R9G9B9E5_SHAREDEXP
64 DXGI_FORMAT_R16G16B16A16_UINT
DXGI_FORMAT_R16G16B16A16_SINT
DXGI_FORMAT_R32G32_UINT
DXGI_FORMAT_R32G32_SINT
DXGI_FORMAT_BC1_UNORM[_SRGB]
DXGI_FORMAT_BC4_UNORM
DXGI_FORMAT_BC4_SNORM
128 DXGI_FORMAT_R32G32B32A32_UINT
DXGI_FORMAT_R32G32B32A32_SINT
DXGI_FORMAT_BC2_UNORM[_SRGB]
DXGI_FORMAT_BC3_UNORM[_SRGB]
DXGI_FORMAT_BC5_UNORM
DXGI_FORMAT_BC5_SNORM

 

资源 (Direct3D 10)