[轉]圖片Premultiplied Alpha到底是干嘛用的


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)Cd

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

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)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)

如果綠色 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:

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM