預乘Alpha的作用

轉載自:https://www.cnblogs.com/xiaonanxia/p/9448444.html

Premultiplied Alpha 這個概念做遊戲開發的人都不會不知道。Xcode 的工程選項裏有一項 Compress PNG Files,會對 PNG 進行 Premultiplied Alpha,Texture Packer 中也有Premultiplied Alpha 的選項。那麼問題來了,Premultiplied Alpha 是什麼呢?我被這個問題困惑了很久,之前搜到過 Nvidia的這篇文章,其實說的很清楚,只是當時有很多相關概念沒搞清楚,所以沒看懂。直到前幾天讀《Real Time Rendering》時終於搞懂了。

Alpha Blending

要搞清楚這個問題,先得理解Alpha通道的工作原理,如果你已經瞭解可以直接跳過。

最常見的像素表示格式是RGBA8888即 (r, g, b, a),每個通道8位,0-255。例如紅色60%透明度就是 (255, 0, 0, 153),爲了表示方便alpha通道一般記成正規化後的0-1的浮點數,也就是 (255, 0, 0, 0.6)。而 Premultiplied Alpha 則是把RGB通道乘以透明度也就是 (r * a, g * a, b * a, a),50%透明紅色就變成了(153, 0, 0, 0.6)。

透明通道在渲染的時候通過 Alpha Blending 產生作用,如果一個透明度爲 as 的顏色 Cs 渲染到顏色 Cd上,混合後的顏色通過以下公式計算,

 

Co=αsCs+(1−αs)CdCo=αsCs+(1−αs)Cd

 

以60%透明的紅色渲染到白色背景爲例:

Co=(255,0,0)⋅0.6+(255,255,255)⋅(1−0.6)=(255,102,102)Co=(255,0,0)⋅0.6+(255,255,255)⋅(1−0.6)=(255,102,102)


也就是說,從視覺上,(255, 0, 0, 0.6)渲染到白色背景上 和 (255, 102, 102) 是同一個顏色。如果顏色以 Premultiplied Alpha 形式存儲,也就是Cs已經乘以透明度了,所以混合公式變成了:

Co=Cs′+(1−αs)CdCo=Cs′+(1−αs)Cd

 

爲什麼要 Premultiplied Alpha 呢?

Premultiplied Alpha 後的像素格式變得不直觀,因爲在畫圖的時候都是先從調色板中選出一個RGB顏色,再單獨設置透明度,如果RGB乘以透明度就搞不清楚原色是什麼了。從前面的 Alpha Blending 公式可以看出,Premultiplied Alpha 之後,混合的時候可以少一次乘法,這可以提高一些效率,但這並不是最主要的原因。最主要的原因是:

沒有 Premultiplied Alpha 的紋理無法進行 Texture Filtering(除非使用最近鄰插值)。

以最常見的 filtering 方式線性插值爲例,一個寬2px高1px的圖片,左邊的像素是紅色,右邊是綠色10%透明度,如果把這個圖片縮放到1x1的大小,那麼縮放後1像素的顏色就是左右兩個像素線性插值的結果,也就是把兩個像素各個通道加起來除以2。如果使用沒有 Premultiplied Alpha 的顏色進行插值,那麼結果就是:

((255,0,0,1)+(0,255,0,0.1))⋅0.5=(127,127,0,0.55)((255,0,0,1)+(0,255,0,0.1))⋅0.5=(127,127,0,0.55)

 

如果綠色 Premultiplied Alpha,也就是 (0, 255 * 0.1, 0, 0.1),和紅色混合後:

((255,0,0,1)+(0,25,0,0.1))⋅0.5=(127,25,0,0.55)

 

從上面的圖裏第三個顏色是沒有 Premultiplied Alpha 的混合結果,對比第四個 Premultiplied Alpha 後顏色的結果,顯然第四個顏色更符合直覺,第三個顏色太綠了,因爲綠色通道沒有乘以透明度,所以在線性插值的時候佔了過大的權重。

所以 Premultiplied Alpha 最重要的意義是使得帶透明度圖片紋理可以正常的進行線性插值。這樣旋轉、縮放或者非整數的紋理座標才能正常顯示,否則就會像上面的例子一樣,在透明像素邊緣附近產生奇怪的顏色。

紋理處理

我們使用的PNG圖片紋理,一般是不會 Premultiplied Alpha 的。遊戲引擎在載入PNG紋理後回手動處理,然後再glTexImage2D傳給GPU,比如 Cocos2D-x 中的 CCImage::premultipliedAlpha:

複製代碼

void Image::premultipliedAlpha() {
    unsigned int* fourBytes = (unsigned int*)_data;
    for (int i = 0; i < _width * _height; i++) {
        unsigned char* p = _data + i * 4;
        fourBytes[i] = CC_RGB_PREMULTIPLY_ALPHA(p[0], p[1], p[2], p[3]);
    }  
    _hasPremultipliedAlpha = true;
}

複製代碼

 

 

而GPU專用的紋理格式,比如 PVR、ETC 一般在生成紋理都是默認 Premultiplied Alpha 的,這些格式一般是GPU硬解碼,引擎用CPU處理會很慢。

總之 glTexImage2D 傳給 GPU 的紋理數據最好都是 Multiplied Alpha 的,要麼在生成紋理時由紋理工具 Pre-multiplied,要麼載入紋理後由遊戲引擎或UI框架 Post-multiplied。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章