2021年6月15日火曜日

BC5 圧縮テクスチャ

はじめに

プログラムから, テクスチャ圧縮がしたいです. BC1, BC3, BC6h BC7についてはispc texture compressionを使おうと思っていましたが, BC5がないのです.

https://software.intel.com/content/www/us/en/develop/articles/fast-ispc-texture-compressor-update.html

ガイドに仕様がちゃんと書いてあります. 簡単そうなので作ってしまいます.

https://docs.microsoft.com/en-gb/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#bc5

ファイルフォーマット

4x4のブロックごとに圧縮するブロック圧縮です. BC4はRだけの1チャネル, BC5はRGの2チャネルを扱います. 4x4x2 = 32 bytesを次の tablex2 = 16 bytesに圧縮します. BC5_UNORMとBC5_SNORMの2バリエーションです. 2つはシェーダでサンプルした場合, [0 1]の浮動小数点数になるか, [-1 1]の浮動小数点になるかが違います.

テーブルは, 4x4の最小値と最大値が先頭に2バイト, 次に3bits x 16のテーブルへのインデックスが並びます.

/*
MSB                   LSB
| red_0               | 1 byte minimum color
| red_1               | 1 byte maximum color
| red_h | red_g | red_f | red_e | red_d | red_c | red_b | red_a | 3 bits x 8
| red_p | red_o | red_n | red_m | red_l | red_k | red_j | red_i | 3 bits x 8
*/

テーブルは明示的に持たず最大値, 最小値から計算します. UNORMとSNORMで1箇所異なりますが, 圧縮するだけならばどちらでも同じなので統一します.

static constexpr u32 Shift = 20;
void createTable(s32 colors[8], s32 block[16])
{
    colors[0] = block[0];
    colors[1] = block[0];
    for(u32 i = 1; i < 16; ++i) {
        colors[0] = minimum(colors[0], block[i]);
        colors[1] = maximum(colors[1], block[i]);
    }
    colors[0] <<= Shift;
    colors[1] <<= Shift;

    if(colors[1] < colors[0]) {
        // 6 interpolated color values
        colors[2] = (6 * colors[0] + 1 * colors[1]) / 7; // bit code 010
        colors[3] = (5 * colors[0] + 2 * colors[1]) / 7; // bit code 011
        colors[4] = (4 * colors[0] + 3 * colors[1]) / 7; // bit code 100
        colors[5] = (3 * colors[0] + 4 * colors[1]) / 7; // bit code 101
        colors[6] = (2 * colors[0] + 5 * colors[1]) / 7; // bit code 110
        colors[7] = (1 * colors[0] + 6 * colors[1]) / 7; // bit code 111
    } else {
        // 4 interpolated color values
        colors[2] = (4 * colors[0] + 1 * colors[1]) / 5; // bit code 010
        colors[3] = (3 * colors[0] + 2 * colors[1]) / 5; // bit code 011
        colors[4] = (2 * colors[0] + 3 * colors[1]) / 5; // bit code 100
        colors[5] = (1 * colors[0] + 4 * colors[1]) / 5; // bit code 101
        colors[6] = 0;                                   // bit code 110
        colors[7] = 255<<Shift;                          // bit code 111
    }
}

ピクセル値に最も近いテーブルインデックスを検索して, 符号化します.

u64 findNearest(s32 colors[8], s32 x)
{
    u32 index = 0;
    s32 mind = absolute(colors[0] - x);
    for(u32 i = 1; i < 8; ++i) {
        s32 d = absolute(colors[i] - x);
        if(d < mind) {
            mind = d;
            index = i;
        }
    }
    return index;
}

まとめ

GPUOpen Compressonatorと結果が異なるため, もっといい方法がありそうです.

0 件のコメント:

コメントを投稿