貼圖壓縮, 在遊戲裡相當重要的功能之一
不單單只是減少記憶體/硬碟使用容量而已
渲染速度也會受惠於貼圖壓縮, 雖然GPU因此多了解壓縮的動作
但解壓縮的計算仍快於存取未壓縮的記憶體頻寬
而今天來寫寫經典的Block Compression, 它是一種破壞性壓縮
1. Graphics API如何使用BC貼圖
假設已經有BC壓縮過的貼圖, 不論是自己寫壓縮還是用外部工具
首要都是先建立正確的貼圖格式 (以BC1為例)
D3D12: DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM_SRGB
Vulkan: VK_FORMAT_BC1_RGB_UNORM_BLOCK, VK_FORMAT_BC1_RGB_SRGB_BLOCK
建立好相對應的資源後, 把raw data複製過去即可, 解壓縮的部分GPU會自己處理
2. 該使用哪個BC格式呢
目前主要有BC1~BC7, 可針對不同場合作使用
BC1: 以壓縮RGB為主, 可以有0個或1個bit來處理Alpha通道. 是的, BC1也可以要求為RGBA, 但是1個bit意味著Alpha值不是開就是關(0或255), 故實用性不是很大, 還是以RGB為主
BC2: 和BC1差不多, 但alpha到了4 bits, 由於BC3的存在此種格式顯得很尷尬
BC3: 和BC1差不多, 但alpha有著8 bits, 最適合用來壓縮RGBA的貼圖
BC4: 適合用來存單一通道(8 bits), 純灰階圖可以考慮, 但在使用時要注意是用R通道而非A
BC5: 適用於雙通道 (各8 bits), 法線貼圖就是它的完美應用, 為什麼呢?由於法線貼圖數值基本上是正規化過的向量(總和為1), 所以只需要存兩個通道, 第三個通道的值用簡單的平方根就能求得
BC6H: 存16bits float RGB, 由於是為了HDR而存在所以名字多了一個H (絕對不是Hentai)
很顯而易見的, skybox的cube map很適合使用
BC7: 最複雜的格式, 特點是儲存了"mode bits", 可以根據場合來決定想要的壓縮動作!
總共有
8種模式可以使用, 簡單來說它企圖結合1~6的優點並給予最大彈性
3. BC1、BC3的資料結構
那麼最近在自己的小引擎裡, 加入了BC1 BC3壓縮, 基於練習目的, 當然是自己寫壓縮了
首先看一下BC1的資料結構:
在這個結構中, 它儲存了兩個色彩值(RGB565), 以及16個2-bit的索引值, 總和8 bytes
也就是說每4x4個像素的區塊, 與未壓縮的資料比(16 * 3 bytes), 只要六分之一的容量!
解壓縮時, GPU會讀取這兩個參考顏色, 然後根據索引來決定色彩輸出的動作
所以資料上的儲存是相當簡單明瞭的, 主要問題在於如何挑選參考點
兩個參考顏色深深地影響著最後的結果
而BC3的資料結構長這樣:
RGB的部分與BC1完全一樣, Alpha的部分存了兩個參考點(各1 byte), 然後存了16個3-bit的索引來查找Alpha值, 所以資料總和16 bytes, 也就是說與未壓縮的資料相比(16*4 bytes), 能省下四分之一的容量
主要還是怎麼選擇Alpha的參考點, 但Alpha的索引資料量比RGB還充裕, 選擇上可以單純點
而解壓縮時, Alpha的部分會這個規則來內插數值:
4. BC1、BC3實作
BlockCompressionAlpha()主要用來處理Alpha
BlockCompressionColor()則是RGB
索引的部分很單純, 根據解壓縮的規則, 算出alpha2~alpha7以及color2~color3
而當前區塊裡的每一個像素都跟這些顏色去比, 計算距離最近的索引
Alpha參考點選擇:
由於Alpha部分線性內插的數值高達8個, 單純選擇該區塊裡的min/max顏色就足夠了
09/01更: 後來實作BC4/BC5時發現,單純選最大最小還是有可能破壞邊緣那些像素,所以還是用了兩兩找最佳的方法
Color參考點選擇:
很遺憾, 對RGB來說min/max並不適用所有情況
試想一種極端情況 (白-紅-黑), 這時候最大最小必定取得(255,255,255), (0,0,0)
不論怎樣線性內插, 紅色就是被吞掉了, 只有該區塊剛好存在兩種顏色時效果最好
所以問題可以描述成, 找一組參考點, 讓區塊中所有顏色跟這組參考點有著最短距離
而在小引擎裡, 我實作了"簡化"過的版本
EvaluateBC1(const UHColorRGB BlockColors[16], const UHColorRGB& Color0, const UHColorRGB& Color1, float& OutMinDiff)
EvaluateBC1的最後一個參數OutMinDiff就是區塊中所有顏色跟參考值的差距加總
那我的測試就是兩兩所有點都呼叫過EvaluateBC1一次, 取差值加總最小的結果作為最終結果
複雜度都是15+14+13+12+...+1次, 而且單純評估最小BC1距離比算外積快多了
而程式的最後, 我還是有去評估min/max顏色的結果, 回到前面所說的
如果一個區塊真的只存在兩種值端顏色, 線性回歸反而不會得到最佳解
想像一下有個區塊8個黑色, 4個暗灰, 4個綠色, 那麼線性回歸很高機率回傳黑色/暗灰做為參考值
那綠色就直接掰啦
5. 壓縮結果
而原圖長這樣:
這張圖是典型的Texture Altas, 在盾牌跟旗幟佔據了貼圖那麼小一部分的情況下
壓縮前後的結果卻看不太出來顯著差異, 更不用說整張貼圖都只為了單一物件的狀況了
可見Block Compression確實是很值得使用, Unreal更是直接把BC1作為預設(他們用的是第三方軟體OodleTexture)
(完)