iOS之深入解析预乘透明度Premultiplied Alpha
生活随笔
收集整理的這篇文章主要介紹了
iOS之深入解析预乘透明度Premultiplied Alpha
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、前言
- Premultiplied Alpha 的概念,做過游戲開發的應該都知道,Xcode 的工程選項里有一項 Compress PNG Files,會對 PNG 進行 Premultiplied Alpha,Texture Packer 中也有Premultiplied Alpha 的選項。那么,Premultiplied Alpha 到底是什么呢?
- 在 Alpha Blending: To Pre or Not To Pre 一文中,詳情地闡明了 Premultiplied Alpha 的相關解釋,如果還需要深入理解的可以閱讀《Real Time Rendering》這本書。
二、Alpha Blending
① Alpha Blending 的原理
- 在圖形學中,Alpha 指的是除了顏色的三個分量(RGB)外的第四個分量:透明度。因此一個真彩色(指利用 RGB 分量合成顏色)的像素就變成由四個分量組成:R、G、B、A。我們這里討論,設 R、G、B、A 均為從 0 到 1 的值,其中 Alpha = 0 為完全透明,Alpha = 1 為完全覆蓋,中間的數值代表半透明,這樣的設定是為了能使本文獨立于顯示硬件,我們把諸如(R,G,B,A)這樣的東西稱為四元組。一個這樣的四元組代表一個由 RA、GA、B*A 組合而成的顏色。
- 有一點重要的是,要清楚分辨如下兩個關鍵像素的意義:
- 那么,如何根據 Alpha 通道數據進行混合的算法呢?
-
- 簡單地,只需要把需要組合的顏色計算出不含 Alpha 分量的原始 RGB 分量然后相加便可,比如現在有兩幅圖象,分別稱為圖象 A 和圖象 B,由這兩幅圖象組合而成的圖象稱為 C,則有如下的四元組:
-
- 以及組合后的 RGB 三元組:
-
- 那么:
-
- 便可得出混合后的顏色。如果有多幅圖像需要混合,則按照以上方法兩幅兩幅地進行混合。
② Alpha Blending 的混合公式
- 最常見的像素表示格式是 RGBA8888 即 (r, g, b, a),每個通道 8 位,0255。例如紅色 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 上,混合后的顏色通過以下公式計算:
- 以 60% 透明的紅色渲染到白色背景為例:
- 也就是說,從視覺上(255, 0, 0, 0.6)渲染到白色背景上和(255, 102, 102)是同一個顏色。如果顏色以 Premultiplied Alpha 形式存儲,也就是 Cs 已經乘以透明度了,所以混合公式變成:
三、為什么要 Premultiplied Alpha?
- Premultiplied Alpha 后的像素格式變得不直觀,因為在畫圖的時候都是先從調色板中選出一個 RGB 顏色,再單獨設置透明度,如果 RGB 乘以透明度就搞不清楚原色是什么。
- 從前面的 Alpha Blending 公式可以看出,Premultiplied Alpha 之后,混合的時候可以少一次乘法,這可以提高一些效率,但這并不是最主要的原因,最主要的原因是:沒有 Premultiplied Alpha 的紋理無法進行 Texture Filtering(除非使用最近鄰插值)。
- 以最常見的 filtering 方式線性插值為例,一個寬 2px 高 1px 的圖片,左邊的像素是紅色,右邊是綠色 10% 透明度,如果把這個圖片縮放到 1x1 的大小,那么縮放后 1 像素的顏色就是左右兩個像素線性插值的結果,也就是把兩個像素各個通道加起來除以2,如果使用沒有 Premultiplied Alpha 的顏色進行插值,那么結果就是:
- 如果綠色 Premultiplied Alpha,也就是(0, 255 * 0.1, 0, 0.1),和紅色混合后:
- Premultiplied Alpha 最重要的意義是使得帶透明度圖片紋理可以正常的進行線性插值,這樣旋轉、縮放或者非整數的紋理坐標才能正常顯示,否則就會像上面的例子一樣,在透明像素邊緣附近產生奇怪的顏色。
四、紋理處理
- 使用的 PNG 圖片紋理,一般是不會 Premultiplied Alpha 的。游戲引擎在載入 PNG 紋理后會手動處理,然后再 glTexImage2D 傳給 GPU,比如 Cocos2D-x 中的 CCImage::premultipliedAlpha:
- 而 GPU 專用的紋理格式,比如 PVR、ETC 一般在生成紋理都是默認 Premultiplied Alpha 的,這些格式一般是 GPU 硬解碼,引擎用 CPU 處理會很慢。
- 總之 glTexImage2D 傳給 GPU 的紋理數據最好都是 Multiplied Alpha 的,要么在生成紋理時由紋理工具 Pre-multiplied,要么載入紋理后由游戲引擎或 UI 框架 Post-multiplied。
五、iOS 中的 Premultiplied Alpha
- Core Graphics 的 CGImage.h 對圖像透明度信息有如下定義:
- 預乘透明度(Premultiplied Alpha)圖像簡單地說,即每個顏色分量都乘以 alpha 通道值作為結果值:
- 為什么關注預乘透明度圖像?微信團隊因 AR 搶紅包場景的 OpenGL 混色結果出錯引起注意:
六、理解 Premultiplied Alpha 的 Tips
① 理解 Alpha 混合
- 最常見的混合是“over”混合,假設已經有一張 RenderTexture,RT 上像素的 RGB 稱其為 RGBdst,Alpha 為 Adst 。現在有一個像素(RGBsrc,Asrc)要和 RT 上的像素混合,那正確的混合會這樣進行:
- 最終混合出來的顏色由兩部組成:
-
- Asrc * RGBsrc 代表 RGBsrc 對最終顏色的貢獻,它受 Alpha 影響,如果 Alpha 為 0 則對最終像素沒有影響,如果 Alpha 為 1 則貢獻 100% 的 RGBsrc;
-
- Adst * RGBdst 是 RT 中像素原本沒有其它像素覆蓋時的貢獻值,但是現在被一個新來的像素遮擋了,被遮擋了 (1 - Asrc),因此 RT 中像素最終貢獻 (Adst * RGBdst)*(1 - Asrc)。
- 由此可見,無論對于 src 還是 dst, A * RGB 才是實際的有效顏色,稱其為 premultiplied alpha。令:
- 則顏色混合可以改寫為:
- 這么看就更加清楚:
② SrcAlpha,OneMinusSrcAlpha 顏色混合正確有前提
- 在 Unity 中的透明混合,默認采用 SrcAlpha,OneMinusSrcAlpha 方式,用符號表示出來:
- 對比之前給出的計算:
- 加號右側少乘 Adst ,這是為什么呢?因為 SrcAlpha, OneMinusSrcAlpha 正確的前提是混合目標是不透明的,即 Adst 為 1。
- 平時渲染時,常見的情況是先渲染不透明物體,再渲染不透明的天空盒,最后再渲染半透明物體做 Alpha 混合,在這種情況下渲染目標是不透明的,不透明物體的有效顏色即其顏色本身。
- 在滿足這個前提下:
- 才會成立,其本質為:
- 依然是符合上文中給出的結論:
- 只不過此時的 RGB’dst 等于 RGBdst??赡苡腥藭a生疑問,不透明背景上混合半透明后,怎么看待混合后的透明度?我們看看上文中的 Alpha 的計算:
- 發現沒有,其本質是以 Asrc 作為參數的 Adst 到 1.0 線性插值。當 Adst 為 1.0 時,無論 Asrc 是何值,最終輸出都是 1.0?;氐浆F實中,這很好理解,磚墻前放一塊玻璃,當我們將玻璃和墻看作一個整體時,它們是不透明的。
③ SrcAlpha, OneMinusSrcAlpha 混合出來的 Alpha 值是無意義的
- 這種常見混合方式根據上文 ② 中的,其已默認渲染目標的 Alpha 為 1,因此它不關心 Alpha 結果的正確性。根據其表達式:
- 可以清晰的看到,這里沒有出現 Adst ,得出正確的 RGB 與 RT 中的 Alpha 存什么沒有任何關聯。
- 通過 SrcAlpha, OneMinusSrcAlpha 方式計算得到:
- 這個結果沒有意義。有些情況下,可以利用這種性質,將 RT 中沒有被用到的 Alpha 通道利用起來,例如存儲 bloom 系數。
④ 理解預乘 Alpha 混合公式的顏色部分
- Premultiplied alpha 混合采用 One, OneMinusSrcAlpha,其實我們在 ① 中就已經看到:
- 即:
- One 就是這里的 1.0 而 OneMinusSrcAlpha 就是 (1 - Asrc) 。RGB’dst 來自于混合的結果,真正的問題是 RGB’rsc 如何獲得,最簡單方式就是紋理中的 RGB 預乘好 Alpha,那么采樣得到的顏色直接就是有效 RGB。
⑤ 紋理預乘 Alpha 實踐上可能有潛在問題
- 在實踐中,紋理的數據源大多是 RGBA32,即單通道 8 比特,只能表示 0-255 的整數,同時游戲資產還會根據目標平臺做紋理壓縮。
- 由于精度問題,原本相近的顏色在預乘后會存儲為更相近,甚至相同的顏色,經壓縮后很容易產生大量 artifacts。要使用預乘 Alpha 的紋理,一般會建議采用單通道 16 位的存儲。
- 由于這種情況,即使預乘有很好的紋理過濾特性,也沒有被廣泛采用,我所了解 WebGL 由于網頁對于 Alpha composition 的天然需求,做了這方面的支持。
⑥ 即使不紋理預乘,采用預乘 Alpha 的混合公式也有好處
- 采用 One, OneMinusSrcAlpha 混合有個很好的特性,可以統一 Blend 和 Additive,減少 BlendState 切換,還能增加效果,推薦閱讀:A Mind Forever Programming。
- 簡單理一下思路:
-
- 把非預乘紋理的采樣到的 RGBA,在 shader 中輸出 (RGB*A, A) 就是 Blend 模式;
-
- 把非預乘紋理的采樣到的 RGBA,在 shader 中輸出 (RGB*A, 0) 就是 Additive 模式。
- 輸出的 Alpha 可以定義一個 uniform t 控制,輸出 (RGBA, At ),這樣通過 t 就是控制 Blend 和 Additive 模式之間的過渡。
- 如果再定義一個 uniform s,輸出 (RGBAs, Ats),還可以通過 s 控制其整體透明度,用于淡入淡出,簡直就是特效的救星。
- 眾所周知,采用 Additive 模式的特效,在亮的場景中幾乎看不到效果,而 Blend 模式的特效在暗的場景中提不亮。采用 One OneMinusSrcAlpha 就可以使用中間態來做出適配比較好的特效,而且不需要 framebuffer fetch。
⑦ Premultiplied Alpha 運算是封閉的
- 換言之,預乘 alpha 混合得到的顏色也是預乘 alpha 的。細心的你可能會注意到,在 ① 中:
- 作為運算結果的 RGB’result 是有 prime 符號的,正是想提示這一點。最終輸出的有效顏色來自兩部分:
-
- 疊加上去的 src 像素貢獻的有效顏色;
-
- 背景 dst 像素貢獻的有效顏色,它被 src 遮擋掉一部分,遮擋的量是 (1 - Asrc)。
- 觀察 ① 中給出的兩式:
- (1)(2) 的計算過程是一樣的,這就不禁會產生疑問:(1)式混合兩個未預乘 alpha 的RGB,結果是預乘 Alpha 的RGB?這沒錯,未預乘 Alpha 的顏色經混合得到的是預乘 Alpha 的顏色。
- 那平時用 SrcAlpha, OneMinusSrcAlpha 為什么能得到未預乘的結果呢?正是 ② 中的原因,由于 SrcAlpha, OneMinusSrcAlpha 混合隱含了一個假設,渲染目標是不透明的,在這個前提下,用正確的混合公式計算,可以得到:
-
- 預乘 Alpha 的 RGB’result;
-
- Aresult = 1.0。
- 在 ② 中已經講過,與不透明目標混合得到的 Alpha 恒為 1。顯而易見,當 Alpha 為 1 時, RGB’result 等于 RGBresult 。因此(1)式在當渲染目標是不透明時,改成下式是成立的:
⑧ 理解預乘 Alpha 混合公式的 Alpha 部分
- 預乘 Alpha 混合時,顏色分量和 Alpha 分量的運算是一致的,對比一下:
- 都是:
- 因此,不需要額外指定 Alpha 分量的混合公式,就能得到有意義的 Alpha 值,而且無論渲染目標是透明還是不透明,結果都是正確的。
⑨ 僅當必要時 Unmultiply
- 凡是講 premultiplied alpha 都會告訴你,可以通過以下方式,還原未預乘的顏色值:
- 常見的、未預乘的顏色值也叫 straight alpha 或 unassociated alpha,而預乘好的叫 premultiplied alpha 或 associated alpha。這種還原操作在渲染自己可控的環境下幾乎用不到。
- 根據上文中的 Premultiplied Alpha 運算是封閉的,預乘 Alpha 混合時運算封閉,可以多次混合不需要還原 straight alpha。但如果用未預乘 Alpha 混合時,如果渲染目標是半透明的,每次混合完成都要 unmultiply 回 straight alpha 才能繼續混合,而且當一個網格有多層透明疊加時結果是錯誤的。
- 從實踐上講,預乘 Alpha 混合的結果需要 unmultiply 主要就這種情況:三方組件只接受 straight alpha 表示的紋理。Framebuffer 顯示到屏幕上輸出時,RT 最終總是不透明的,不透明的 Alpha 為 1,預乘和未預乘沒有區別,也不用特殊處理。
⑩ Bleed Alpha 與預乘 Alpha 原理不同,目的相同,結果略有不同
- 預乘 alpha 和 bleed alpha 目的都是減少半透明紋理過濾產生的瑕疵,但它們有一些比較顯著的區別:
-
- Bleed alpha 不需要修改混合公式;
-
- Bleed alpha 只能優化完全透明和非完全透明像素邊緣的過濾瑕疵;
-
- 預乘 alpha 不僅可以達到 bleed alpha 的結果,半透像素之間的過濾效果也能得到優化;
-
- 預乘 alpha 需要修改混合公式,可能產生 tip6 中提到的情況。
- 當不使用 premultiplied alpha 時,預處理貼圖 bleed alpha 是一個“免費”替代品。雖然效果上會有折扣,但性價比極高。
? 紋理預乘 Alpha 可以減少紋理過濾帶來的 artifacts
- 紋理預乘 alpha 可以減少 downsampling、upsampling、非 pixel perfect 各種情況下半透紋理過濾產生的 artifacts,推薦閱讀:
-
- Alpha Blending: To Pre or Not To Pre;
-
- Beware of Transparent Pixels。
總結
以上是生活随笔為你收集整理的iOS之深入解析预乘透明度Premultiplied Alpha的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python之深入解析如何使用Pytho
- 下一篇: RxSwift之深入解析如何创建观察者O