如何解压缩BC3_UNORM DDS 纹理格式?

How to decompress a BC3_UNORM DDS texture format?

我已经阅读了很多文章和代码,但我仍然无法让它工作,我已经读取了纹理中 header 的所有 128 字节,它们读取了 65536 字节的压缩数据实际纹理(纹理的分辨率为 256x256,每个压缩像素使用 1 个字节)。我尝试创建我的解压缩算法但没有成功,我决定使用其他人的算法,所以我在这里找到了 this 代码。这是我试图传递给它的参数,因此它会解压缩我的 DDS 纹理。BlockDecompressImageDXT5(textureHeader.dwWidth, textureHeader.dwHeight, temp, packedData) 注意:textureHeader 是一个有效的结构,其中加载了 DDS 纹理的 header 数据,temp 是一个无符号字符数组,包含从中读取的所有 DDS 数据DDS 纹理和 packedData 是一个无符号长数组,我期待收到最终的解压缩数据。所以在我链接的代码中,每个像素的 RGBA 通道都打包在 PackRGBA 函数中,packedData 中每种颜色一个字节。在将数据指向 D3D11_SUBRESOURCE_DATApSysMem 上的纹理数据之前,我已经将每个字节从 unsigned long packedData 分配到 4 个不同的 unsigned char m_DDSData 这样:

for (int i{ 0 }, iData{ 0 }; i < textureHeader.dwPitchOrLinearSize; i++, iData += 4) //dwPitchOrLinearSize is the size in bytes of the compressed data.
{
    m_DDSData[iData] = ((packedData[i] << 24) >> 24); //first char receives the 1st byte, representing the red color.
    m_DDSData[iData + 1] = ((packedData[i] << 16) >> 24); //second char receives the 2nd byte, representing the green color.
    m_DDSData[iData + 2] = ((packedData[i] << 8) >> 24); //third char receives the 3rd byte, representing the blue color.
    m_DDSData[iData + 3] = (packedData[i] >> 24); //fourth char receives the 4th byte, representing the alpha color.
}

注意:m_DDSData应该是D3D11_SUBRESOURCE_DATA用来指向纹理数据的最终数据数组,但是当我使用它时this 是我得到的那种结果,只有一个带有随机颜色的框架而不是我的实际纹理。我也有其他类型纹理的算法,它们工作正常,所以我可以保证问题只出现在 DDS 压缩格式中。 编辑: 另一个例子,这是一个箱子的模型,程序应该渲染箱子的纹理:https://prnt.sc/11b62b6

有关 BC3 压缩方案的完整说明,请参阅 Microsoft Docs. BC3 is just the modern name for DXT4/DXT5 compression a.k.a. S3TC。简而言之,它一次将一个 4x4 的像素块压缩成以下结构,每个块有 16 个字节:

struct BC1
{
    uint16_t    rgb[2]; // 565 colors
    uint32_t    bitmap; // 2bpp rgb bitmap
};

static_assert(sizeof(BC1) == 8, "Mismatch block size");

struct BC3
{
    uint8_t     alpha[2];   // alpha values
    uint8_t     bitmap[6];  // 3bpp alpha bitmap
    BC1         bc1;        // BC1 rgb data
};

static_assert(sizeof(BC3) == 16, "Mismatch block size");

CPU减压

对于颜色部分,它与“BC1”相同a.k.a。 DXT1 压缩块。这是伪代码,但应该明白要点:

auto pBC = &pBC3->bc1;
clr0 = pBC->rgb[0]; // 5:6:5 RGB
clr0.a = 255;

clr1 = pBC->rgb[1]; // 5:6:5 RGB
clr1.a = 255;

clr2 = lerp(clr0, clr1, 1 / 3);
clr2.a = 255;

clr3 = lerp(clr0, clr1, 2 / 3);
clr3.a = 255;

uint32_t dw = pBC->bitmap;

for (size_t i = 0; i < NUM_PIXELS_PER_BLOCK; ++i, dw >>= 2)
{
    switch (dw & 3)
    {
        case 0: pColor[i] = clr0; break;
        case 1: pColor[i] = clr1; break;
        case 2: pColor[i] = clr2; break;
        case 3: pColor[i] = clr3; break;
    }
}

请注意,虽然 BC3 包含 BC1 块,但 BC1 的解码规则略有修改。解压BC1时,一般会检查颜色顺序如下:

if (pBC->rgb[0] <= pBC->rgb[1])
{
    /* BC1 with 1-bit alpha */
    clr2 = lerp(clr0, clr1, 0.5);
    clr2.a = 255;

    clr3 = 0; // alpha of zero
}

BC2 和 BC3 已经包含了 alpha 通道,所以没有使用这个额外的逻辑,你总是有 4 种不透明的颜色。

对于 alpha 部分,BC3 使用两个 alpha 值,然后根据这些值生成查找 table:

alpha[0] = alpha0 = pBC3->alpha[0];
alpha[1] = alpha1 = pBC3->alpha[1];

if (alpha0 > alpha1)
{
    // 6 interpolated alpha values.
    alpha[2] = lerp(alpha0, alpha1, 1 / 7);
    alpha[3] = lerp(alpha0, alpha1, 2 / 7);
    alpha[4] = lerp(alpha0, alpha1, 3 / 7);
    alpha[5] = lerp(alpha0, alpha1, 4 / 7);
    alpha[6] = lerp(alpha0, alpha1, 5 / 7);
    alpha[7] = lerp(alpha0, alpha1, 6 / 7);
}
else
{
    // 4 interpolated alpha values.
    alpha[2] = lerp(alpha0, alpha1, 1 / 5);
    alpha[3] = lerp(alpha0, alpha1, 2 / 5);
    alpha[4] = lerp(alpha0, alpha1, 3 / 5);
    alpha[5] = lerp(alpha0, alpha1, 4 / 5);
    alpha[6] = 0;
    alpha[7] = 255;
}

uint32_t dw = uint32_t(pBC3->bitmap[0]) | uint32_t(pBC3->bitmap[1] << 8)
    | uint32_t(pBC3->bitmap[2] << 16);

for (size_t i = 0; i < 8; ++i, dw >>= 3)
    pColor[i].a = alpha[dw & 0x7];

dw = uint32_t(pBC3->bitmap[3]) | uint32_t(pBC3->bitmap[4] << 8)
    | uint32_t(pBC3->bitmap[5] << 16);

for (size_t i = 8; i < NUM_PIXELS_PER_BLOCK; ++i, dw >>= 3)
    pColor[i].a = alpha[dw & 0x7];

DirectXTex includes functions for doing all the compression/decompression for all BC formats.

If you want to know what the pseudo-function lerp does, see wikipedia or HLSL docs.

使用压缩纹理渲染

如果您要使用 Direct3D 进行渲染,则不需要解压缩纹理。所有 Direct3D 硬件功能级别都包括对 BC1 - BC3 纹理压缩的支持。您只需使用 DXGI_FORMAT_BC3_UNORM 格式创建纹理并正常创建纹理。像这样:

D3D11_TEXTURE2D_DESC desc = {};
desc.Width = textureHeader.dwWidth;
desc.Height = textureHeader.dwHeight;
desc.MipLevels = desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_BC3_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;


D3D11_SUBRESOURCE_DATA initData = {}; 
initData.pSrcBits = temp;
initData.SysMemPitch = 16 * (textureHeader.dwWidth / 4);
    // For BC compressed textures pitch is the number of bytes in a ROW of blocks

Microsoft::WRL::ComPtr<ID3D11Texture2D> pTexture;
hr = device->CreateTexture2D( &desc, &initData, &pTexture );
if (FAILED(hr))
    // error

For a full-featured DDS loader that supports arbitrary DXGI formats, mipmaps, texture arrays, volume maps, cubemaps, cubemap arrays, etc. See DDSTextureLoader. This code is included in DirectX Tool Kit for DX11 / DX12. There's standalone versions for DirectX 9, DirectX 10, and DirectX 11 in DirectXTex.

If loading legacy DDS files (i.e. those that do not map directly to DXGI formats), then use the DDS functions in DirectXTex which does all the various pixel format conversions required (3:3:2, 3:3:2:8, 4:4, 8:8:8, P8, A8P8, etc.)