如何解压 BC3_UNORM DDS 纹理格式?

问题描述

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

m_DDSData

注意: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. } 用来指向纹理数据的最终数据数组,但是当我使用它时 this 是我得到的那种结果,只有一个带有随机颜色的框架,而不是我的实际纹理。我也有其他类型纹理的算法,并且它们可以正常工作,因此我可以确保问题仅出现在 DDS 压缩格式中。 编辑:一个例子,这是一个胸部模型,程序应该渲染胸部的纹理:https://prnt.sc/11b62b6

解决方法

有关 BC3 压缩方案的完整说明,请参阅 Microsoft Docs。 BC3 只是 DXT4/DXT5 压缩的现代名称,也就是 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”又名 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,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,0.5);
    clr2.a = 255;

    clr3 = 0; // alpha of zero
}

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

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

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,2 / 7);
    alpha[4] = lerp(alpha0,3 / 7);
    alpha[5] = lerp(alpha0,4 / 7);
    alpha[6] = lerp(alpha0,5 / 7);
    alpha[7] = lerp(alpha0,6 / 7);
}
else
{
    // 4 interpolated alpha values.
    alpha[2] = lerp(alpha0,1 / 5);
    alpha[3] = lerp(alpha0,2 / 5);
    alpha[4] = lerp(alpha0,3 / 5);
    alpha[5] = lerp(alpha0,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 包括对所有 BC 格式进行所有压缩/解压缩的函数。

如果您想知道伪函数 lerp 的作用,请参阅 wikipediaHLSL 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

有关支持任意 DXGI 格式、mipmap、纹理数组、体积贴图、立方体贴图、立方体贴图数组等的全功能 DDS 加载器。请参阅 DDSTextureLoader。此代码包含在 DX11 / DX12DirectX Tool Kit 中。 DirectXTex 中有 DirectX 9、DirectX 10 和 DirectX 11 的独立版本。

如果加载旧版 DDS 文件(即那些直接映射到 DXGI 格式的文件),则使用 DirectXTex 中的 DDS 函数进行所有所需的各种像素格式转换(3 :3:2、3:3:2:8、4:4、8:8:8、P8、A8P8等)