块压缩
块压缩是一种有损纹理压缩技术,用于减小纹理大小并减少内存占用,从而提高性能。 块压缩纹理可以比每种颜色 32 位的纹理小。
块压缩是用于减小纹理大小的纹理压缩技术。 与每色 32 位的纹理相比,块压缩的纹理可以小到 75%。 使用块压缩时,应用程序通常会看到性能提高,因为内存占用量较小。
虽然丢失,但块压缩效果良好,建议用于管道转换和筛选的所有纹理。 直接映射到屏幕(UI 元素(如图标和文本)的纹理不适合压缩,因为项目更明显。
块压缩纹理必须作为大小 4 的所有维度的倍数创建,不能用作管道的输出。
块压缩的工作原理
块压缩是减少存储颜色数据所需的内存量的技术。 通过使用编码方案将某些颜色存储在原始大小和其他颜色中,可以显著减少存储图像所需的内存量。 由于硬件会自动解码压缩的数据,因此使用压缩纹理不会造成性能损失。
若要查看压缩的工作原理,请查看以下两个示例。 第一个示例描述了存储未压缩数据时使用的内存量;第二个示例描述存储压缩数据时使用的内存量。
存储未压缩的数据
下图表示未压缩的 4×4 纹理。 假设每个颜色都包含一个颜色组件(例如红色),并存储在一个字节内存中。
未压缩的数据按顺序排列在内存中,需要 16 个字节,如下图所示。
存储压缩的数据
现在,你已了解未压缩映像使用的内存量,请查看压缩映像所节省的内存量。 BC4 压缩格式存储 2 种颜色(每个字节 1 个)和 16 个 3 位索引(48 位或 6 个字节),用于内插纹理中的原始颜色,如下图所示。
存储压缩数据所需的总空间为 8 个字节,这是通过未压缩的示例节省 50% 的内存。 使用多个颜色组件时,节省甚至更大。
块压缩提供的大量内存节省可能会导致性能增加。 这种性能以图像质量为代价(由于颜色内插);然而,较低的质量往往不明显。
下一部分介绍了 Direct3D 如何在应用程序中使用块压缩。
使用块压缩
像未压缩的纹理一样创建块压缩的纹理,但指定块压缩格式除外。
接下来,创建一个视图以将纹理绑定到管道,因为块压缩的纹理只能用作着色器阶段的输入,因此需要创建着色器资源视图。
使用块压缩纹理的方式与使用未压缩纹理的方式相同。 如果应用程序将获取指向块压缩数据的内存指针,则需要在 mipmap 中考虑内存填充,导致声明的大小与实际大小不同。
虚拟大小与物理大小
如果应用程序代码使用内存指针来行走块压缩纹理的内存,则可能需要修改应用程序代码的一个重要注意事项。 块压缩纹理在所有维度中必须是 4 的倍数,因为块压缩算法在 4x4 纹素块上运行。 对于初始维度被 4 除以 4 的 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), 阿尔法 (4) | BC2 |
三分量颜色和 alpha | 颜色 (5:6:5), 阿尔法 (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),如下图所示。
颜色索引(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;
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 数据,如下图所示。
BC3
使用 BC3 格式(DXGI_FORMAT_BC3_TYPELESS、DXGI_FORMAT_BC3_UNORM 或 DXGI_BC3_UNORM_SRGB)来存储高一致性的颜色数据(对于一致性较低的 alpha 数据则使用 BC2)。 BC3 格式使用 5:6:5 颜色(5 位红色、6 位绿色、5 位蓝色)和 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 格式使用 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 值的位代码来压缩 4×4 纹素区域中的 alpha 值,这些位代码与给定纹素的原始 alpha 最匹配。
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
}
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),如下图所示。
该算法使用 3 位索引从包含 8 种颜色的颜色表中查找颜色。 前两种颜色(red_0 和 red_1)分别是最小色和最大色。 该算法使用线性内插计算剩余颜色。
该算法通过检查两个引用值来确定内插颜色值的数量。 如果 red_0 大于 red_1,BC4 内插 6 个颜色值;否则,内插 4 个。 当 BC4 仅内插 4 个颜色值时,它将设置另外两个颜色值(0.0f 表示完全透明,1.0f 表示完全不透明)。 BC4 通过存储对应于与给定纹素的原始 alpha 值最匹配的内插 alpha 值的位代码来压缩 4×4 纹素区域中的 alpha 值。
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 位索引(自有 8 个值以来为 000-111),这将在压缩期间以红色块(通过红色 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 位索引(自有 8 个值以来为 000-111),这将在压缩期间以红色块(通过红色 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),如下图所示。
该算法使用 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 位索引(自有 8 个值以来为 000-111),这将在压缩期间以红色块(通过红色 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 位索引(自有 8 个值以来为 000-111),这将在压缩期间以红色块(通过红色 p 保存)。
格式转换
Direct3D 支持在预结构化类型纹理和相同位宽度的块压缩纹理之间复制。
可以在几个格式类型之间复制资源。 这种类型的复制操作执行一种类型的格式转换,以不同的格式类型重新解释资源数据。 假设此示例显示了重新解释数据与更典型的转换行为方式之间的差异:
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 |