創作內容

3 GP

【程式】premultiply alpha的妙用

作者:Shark│2016-10-06 22:08:28│巴幣:6│人氣:1758
本篇談另一個圖像處理的問題:兩個圖層都有alpha時的混色,差不多是自己寫繪圖軟體的圖層群組功能了。
這次兩位電子妖精就不出場了。

符號說明:
大寫字母A,B,C,S表示記憶體內的一張圖。
後面的小寫字母c,a表示顏色分量,c代表把r,g,b都按照相同的公式計算,a是alpha(不透明度)。
一般繪圖軟體的RGBA值是0~255,而D3D和OpenGL的shader裡是用0~1的小數,本篇也是用0~1。




如果下層是主畫面,每個像素alpha都是1,只有上層有透明度,那混色公式是很常見的那樣:

AcAa + Sc(1-Aa)

式中的c代表把rgb都用這個公式計算,如下
ArAa + Sr(1-Aa)
AgAa + Sg(1-Aa)
AbAa + Sb(1-Aa)
Aa越大則A佔的比例越大,反之則S的成分越大。
把每個像素都套用這個式子計算。

這個式子暫且稱為「無alpha版本」,下面的說明會用到。



可是如果用framebuffer object做兩階段繪圖,下層不是主畫面,上下層都有透明度,那計算式是怎麼樣呢?
思路如下

左:把A用無alpha版本跟S疊合,再把B用無alpha版本跟S疊合。
右:把B與A用「有alpha版本」疊合產生C,再用無alpha版本把C疊到S上面。

兩個方法最後主畫面的像素要一樣,先計算左邊的方法,再把兩者比對求出有alpha版本的公式。

左邊是把上面的混色公式再疊上B
BcBa + [AcAa + Sc(1-Aa)](1-Ba)
=BcBa + AcAa(1-Ba) + Sc(1-Aa)(1-Ba)  --<1>

右邊C疊上S的步驟是這樣
CcCa + Sc(1-Ca)  --<2>
求出Cc、Ca,讓<2>的結果跟<1>一樣

兩式中有出現Sc的項,<1>是Sc(1-Aa)(1-Ba),<2>是Sc(1-Ca),可發現Ca即等於Aa和Ba做screen運算。
Ca = screen(Aa,Ba) = 1-(1-Aa)(1-Ba) = Ba+Aa(1-Ba)

再比對剩下的項,<1>式的BcBa + AcAa(1-Ba)和<2>式的CcCa
把兩者都除以Ca
Cc=[BcBa + AcAa(1-Ba)] / Ca

這就是「有alpha版本」的混色公式。



如果要在D3D和OpenGL實作這個公式,問題就來了,目前即使最新的D3D和OpenGL,混色這一步也還不能寫shader。

上層c*參數1 + 下層c*參數2
上層a*參數1 + 下層a*參數2

D3D11的device->CreateBlendState和OpenGL的glBlendFunc能做的是設定裡面的參數1和參數2,而且只能在預先定義的幾種裡面選一個,不能造出這個型式以外的算式。
無alpha版本很容易,設參數1=GL_SRC_ALPHA,參數2=GL_ONE_MINUS_SRC_ALPHA。
如果是有alpha版本,Ca=Ba+Aa(1-Ba)做得出來,設參數1=GL_ONE,參數2=GL_ONE_MINUS_SRC_ALPHA。
但是Cc的算式多了一點步驟,這樣就做不到了。



終於要講到本篇的標題:premultiply alpha了。
這是把RGB預先乘以alpha,若原始圖檔是(Ar,Ag,Ab,Aa),讀取時做一點計算讓存在記憶體裡的是(ArAa,AgAa,AbAa,Aa),因為Aa的範圍是0~1,把RGB乘上一個0~1的數會≦原來的值。

印象中有聽過這個方法,有一次就試試看把算式中的c都乘上alpha,發現問題迎刃而解。

令Ap=AcAa,記憶體裡的像素變成(Ap,Aa)、(Bp,Ba)、(Cp,Ca),S因為alpha固定是1.0所以Sp=Sc

把上面公式裡的c×a都換成p
無alpha版本變成
Ap + Sp(1-Aa)
左圖變成
Bp + [Ap + Sp(1-Aa)](1-Ba)
=Bp + Ap(1-Ba) + Sp[1-screen(Aa,Ba)]
右圖是
Cp + Sp(1-Ca)
比對後求出
Cp = Bp+Ap(1-Ba),與無alpha版公式相同
Ca = screen(Aa,Ba) = Ba+Aa(1-Ba)

公式變得很簡單,可以用glBlendFunc湊出來了,這樣算下來才知道為什麼會有人想出premultiply alpha的方法。這方法其實很早以前就有,早期可用來簡化像素計算,現在可能因為電腦變快,能應付比較多計算就很少用了。
於是把艾莉兒改造一下,讓她讀取圖檔後立刻把所有像素做premultiply alpha,glBlendFunc和shader裡讀取貼圖的部分也配合改算式。
不過以上計算數值範圍都是0~1,記憶體裡實際是0~255,所以premultiply alpha的算式是「原來的RGB值×alpha/255」。

另外,我除了普通的混色模式以外還有用到add、multiply、screen三種,有了premultiply alpha以後這些公式也簡化了,此篇先不介紹,以後可能另寫一篇介紹各種混色模式。

還有個問題,如果圖不是RGBA格式,而是DXT等壓縮過的格式,那就不能讀取後修改RGB值了。(PNG、JPG讀取時會解碼,但DXT可以給顯卡直接利用,會保持壓縮的狀態存在主記憶體和顯示記憶體)
目前我還沒用到這類格式,先不考慮這個問題,以後用到的話想到的做法是把premultiply alpha移到shader來做,用一個uniform變數告訴shader貼圖是主記憶體傳過去的還是顯卡裡產生的,主記憶體傳過去的才在shader裡乘以alpha。



跟前一篇貼圖坐標一樣,這個問題也是做紙娃娃系統時遇到的。其實只要用到framebuffer object應該就會碰到這個問題,紙娃娃系統是我第一個用到framebuffer object的功能。

我這裡常常要用OpenGL和shader寫出繪圖軟體裡的功能,不知道其他人做遊戲的情況如何,有沒有要寫影像處理演算法。
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=3345419
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:程式|OpenGL|演算法|Direct3D

留言共 7 篇留言

龍恩
話說感覺你好像不是用Unity做的遊戲
是自製引擎??

10-06 22:52

Shark
對,是自製引擎10-06 22:59
我是勇迷~
所以主要是計算速度更快.效能更好??視覺上看起來有差嗎?

06-14 13:49

Shark
主要是計算速度更快。

如果RGBA用8 bit整數儲存(一般繪圖軟體都如此),除法會產生誤差,但肉眼不易看出來。
詳細如下:

例如原本是R=245 A=16,使用premultiply alpha計算:
245*(16/255)=15.372...
8 bit不能儲存小數,所以必須把小數去掉變成15
把它還原:
15*(255/16)=239.0625 → 239
跟原來的245不一樣
但A=16代表這個像素很接近透明,肉眼不易看出誤差。06-15 19:40
我是勇迷~
了解~感謝
如果有一個Shader 裡面有用Blend One OneMinusSrcAlpha 就表示它屬於blend-premultiply.他就是計算速度更快的????(相對於Blend SrcAlpha OneMinusSrcAlpha)

06-17 15:29

Shark
計算速度快是指CPU處理圖像的情況,Blend function是GPU裡專門的步驟,應該兩者一樣快。

shader會有的問題是Blend function能設定的值是有限的,不是所有圖層重疊演算法都湊得出來,像本篇提到「上下層都有透明度的情況」,這個公式就湊不出來了。
Cc=[BcBa + AcAa(1-Ba)] / [Ba+Aa(1-Ba)]

Blend會看混色的需求使用不同的值,不是同一種用到底,Blend One OneMinusSrcAlpha不一定是premultiply alpha。06-18 21:16
我是勇迷~
假設這是一個Unity裡.特效粒子專用的 Shader.他裡面寫Blend One OneMinusSrcAlpha.
是否意味此Shader屬於premultiply alpha.更俱效能??

06-19 09:30

Shark
依我對GPU的了解,Blend One OneMinusSrcAlpha和SrcAlpha OneMinusSrcAlpha應該一樣快。

是不是premultiply alpha的話,只從這一行看不出來,還要看其他部分的code。
可能讀取圖檔時有premultiply alpha所以blend這樣設定。
也可能寫code的人就是想要「Bc+Ac(1-Ba)」的演算法。

這樣解釋可以嗎?06-20 22:30
我是勇迷~
感謝~我了解了

06-25 08:17

我是勇迷~
請問 MODULATE 2X 這種效果 要怎麼用 加減乘除 做出來??(想要用VertexColor混色)跟premultiply 有關嗎??

07-15 11:06

Shark
MODULATE 2X我也沒用過,查了一下計算式是a*b*2。

是想把VertexColor和貼圖顏色混色嗎?這要在pixel shader裡做。
讀取貼圖後假設貼圖顏色是TextureColor,如下計算RGBA值,把計算結果輸出。

output.rgb = TextureColor.rgb*VertexColor.rgb*2;
output.a = TextureColor.a;

Blend是把pixel shader計算的結果跟畫布混色,達不到VertexColor混色的效果。
這跟premultiply alpha無關,premultiply alpha是讀取圖檔時做一點特殊處理。07-15 20:59
我是勇迷~
OK 感謝

07-29 13:17

我要留言提醒:您尚未登入,請先登入再留言

3喜歡★shark0r 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:想一下看板娘的設計... 後一篇:【程式】gl_Verte...

追蹤私訊切換新版閱覽

作品資料夾

hyzgdivina喜歡虹咲的LLer
我的小屋裡有很多又香又甜的Hoenn繪師虹咲漫畫翻譯喔!歡迎LoveLiver來我的小屋裡坐坐~看更多我要大聲說昨天19:40


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】