块压缩 (Direct3D 10)
块压缩是一种用于减小纹理大小的纹理压缩技术。 与每种颜色 32 位的纹理相比,块压缩纹理可以小 75%。 由于内存占用较小,应用程序在使用块压缩时通常会看到性能的提高。
虽然有损,但块压缩效果很好,建议用于所有由管道转换和筛选的纹理。 直接映射到屏幕的纹理(图标和文本等 UI 元素)不是压缩的好选择,因为伪影更明显。
块压缩纹理必须在所有维度上创建为 4 的倍数,不能用作管道的输出。
块压缩的工作原理是什么?
块压缩是一种减少存储颜色数据所需内存量的技术。 通过以原始大小存储一些颜色,并使用编码方案存储其他颜色,可以大大减少存储图像所需的内存量。 由于硬件会自动解码压缩的数据,因此使用压缩纹理不会造成性能损失。
若要了解压缩的工作原理,请查看以下两个示例。 第一个示例描述了存储未压缩数据时使用的内存量;第二个示例描述了存储压缩数据时使用的内存量。
存储未压缩的数据
下图表示未压缩的 4×4 纹理。 假设每种颜色都包含一个颜色分量(例如红色),并存储在一个字节的内存中。
未压缩的数据按顺序排列在内存中,需要 16 个字节,如下图所示。
存储压缩的数据
现在你已经看到了未压缩图像使用了多少内存,接下来看看压缩图像节省了多少内存。 BC4 压缩格式存储2种颜色(每种颜色 1 个字节)和 16 个 3 位索引(48 位或 6 个字节),用于插值纹理中的原始颜色,如下图所示。
存储压缩数据所需的总空间为 8 个字节,比未压缩的示例节省了 50% 的内存。 当使用多个颜色分量时,节省甚至更大。
块压缩提供的大量内存节省可以提高性能。 这种性能是以图像质量为代价的(由于颜色插值);然而,较低的质量往往并不明显。
下一节介绍 Direct3D 10 如何使你在应用程序中轻松使用块压缩。
使用块压缩
创建块压缩纹理,就像创建未压缩纹理一样(请参阅从文件创建纹理),但指定块压缩格式除外。
loadInfo.Format = DXGI_FORMAT_BC1_UNORM;
D3DX10CreateTextureFromFile(...);
接下来,创建一个视图将纹理绑定到管道。 由于块压缩的纹理只能用作着色器阶段的输入,因此需要通过调用 CreateShaderResourceView 来创建着色器资源视图。
使用块压缩纹理的方式与使用未压缩纹理的方式相同。 如果应用程序将获取指向块压缩数据的内存指针,则需要考虑导致声明大小与实际大小不同的 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)、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),如下图所示。
颜色索引 (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_UNORM 或 DXGI_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 数据,如下图所示。
Direct3D 9 和 Direct3D 10 之间的差异:
Direct3D 9 和 10 中都存在此格式。
在 Direct3D 9 中,BC2 格式称为 D3DFMT_DXT2 和 D3DFMT_DXT3。
在 Direct3D 10 中,BC2 格式由 DXGI_FORMAT_BC2_UNORM 或 DXGI_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 位蓝色)存储颜色数据,使用一个字节存储 alpha 数据。 假设使用尽可能大的数据格式的 4×4 纹理,这种压缩技术将所需的内存从 64 字节(16 种颜色 × 4 个分量/颜色 × 1 个字节/分量)减少到 16 字节。
BC3 格式存储的颜色具有与 BC1 格式相同的位数和数据布局;然而,BC3 需要额外的 64 位内存来存储 alpha 数据。 BC3 格式通过存储两个引用值并在它们之间插值来处理 alpha(类似于 BC1 存储 RGB 颜色的方式)。
该算法适用于 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 最接近匹配的插值 alpha 值对应的位码,压缩 4×4 纹理像素区域中的 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
}
Direct3D 9 和 Direct3D 10 之间的差异:
在 Direct3D 9 中,BC3 格式称为 D3DFMT_DXT4 和 D3DFMT_DXT5。
在 Direct3D 10 中,BC3 格式由 DXGI_FORMAT_BC3_UNORM 或 DXGI_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),如下图所示。
该算法使用 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 位索引(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),如下图所示。
该算法使用 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 允许在相同位宽的预结构化类型纹理和块压缩纹理之间进行复制。 可实现此目的的函数是 CopyResource 和 CopySubresourceRegion。
从 Direct3D 10.1 开始,可以使用 CopyResource 和 CopySubresourceRegion 在几种格式类型之间复制。 这种复制操作执行一种格式转换,将资源数据重新解释为不同的格式类型。 考虑以下示例,它显示了用更典型的转换行为重新解释数据之间的区别:
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 |
相关主题