in XNA

XNA – RenderTarget & AlphaBlend (上)

完全出乎我意料的,AlphaBlend真是有夠麻煩的…
終於,找到原因(應該),也順利解決了問題(似乎)!
嘛~結果是我要的就好了咩XDDD

AlphaBlend會出問題的原因在於「算法」和「認知」的差異。
對,或許你腦中的混和方程式其實是錯的,書上寫的甚至也是錯誤的
(也許)
如果不想被我誤導,請先瀏覽一下參考文章:
Drawing problems with RenderTarget2D
SpriteBatch::DrawString() into RenderTarget2D odd effect
Premultiplied alpha
Alpha Blending (Part 1)
Alpha Blending, Part 2
Alpha Blending, Part 3




先來看看預設的計算公式:

FinalColor = (SourceColor * SourceBlend) + (DestinationColor * DestinationBlend);

其中:
FinalColor – 最終顏色。
SourceColor – 來源顏色。
DestinationColor – 目標顏色。
SourceBlend – 混合時,來源的混合方式。預設為「SourceAlpha」,也就是來源顏色的透明值。
DestinationBlend – 混合時,目標的混合方式。預設為「InverseSourceAlpha」,也就是來源顏色透明值的反差。

也就是…

FinalColor.rgb = (SourceColor.rgb* SourceColor.a) + (DestinationColor.rgb * (1 – SourceColor.a));

最終結果的顏色 = 來源提供的量(依照透明值) + 目標提供的量(依照透明值)。
看似合情合理,卻和我們直覺的混合有所不同,舉個簡單的例子。
假設我們將一顆純紅且半透明(1, 0, 0, 0.5)的顏色,畫到一個純黑且完全透明(0, 0, 0, 0)的顏色上。
那麼,直覺上來說,結果應該就是來源的顏色rgb(0.5, 0, 0)吧?
接著來看看實際的計算過程:

FinalColor = (SourceColor* SourceColor.a) + (DestinationColor * (1 – SourceColor.a));
FinalColor = ((1, 0, 0) * 0.5) + ((0, 0, 0) * (1 – 0.5));
FinalColor = ((1, 0, 0) * 0.5) + ((0, 0, 0) * (0.5));
FinalColor = (0.5, 0, 0) + (0, 0, 0);
FinalColor = (0.5, 0, 0);

到這裡,毫無破綻,確實符合裡想的結果。
所以,當你直接把圖片畫在Backbuffer上是完全看不出異樣的。



現在,我們把上面的顏色畫在某個畫布(RenderTarget)上,我們在將畫布畫到BackBuffer。
首先,先把上半部沒有明確列出來的Alpha計算補完。
在「SeparateAlphaBlendEnabled」開啟之前(預設為關閉),Alpha的混合是參照rgb的混合方式。
也就是依照
SourceAlpha和InverseSourceAlpha計算,先來看看預設的計算結果:

FinalColor.a = (SourceColor.a * SourceColor.a) + (DestinationColor.a* (1 – SourceColor.a));
FinalColor.a = (0.5 * 0.5) + (0 * (1 – 0.5));
FinalColor.a = (0.5 * 0.5) + (0 * 0.5);
FinalColor.a = (0.25) + (0);
FinalColor.a = 0.25;

很顯然的,這個透明值是錯誤的,也就是一切錯誤的根源…
後續畫到BackBuffer的步驟就省略了,其
結果應該不難想像。
既然如此,我想我們需要將SeparateAlphaBlendEnabled打開,這是為了讓透明值獨立計算。
分離後的透明值計算公式如下:

FinalColor.a = (SourceColor.a * AlphaSourceBlend) + (DestinationColor.a * AlphaDestinationBlend);

其中:
AlphaSourceBlend – 來源透明質的混合方式。預設為One。
AlphaDestinationBlend – 目標透明質的混合方式。預設為Zero。

OK,來走一次計算過程吧!

FinalColor.a = (SourceColor.a * AlphaSourceBlend) + (DestinationColor.a * AlphaDestinationBlend);
FinalColor.a = (SourceColor.a * One) + (DestinationColor.a * Zero);
FinalColor.a = (0.5 * 1) + (0 * 0);
FinalColor.a = (0.5) + (0);
FinalColor.a = 0.5;

Great!!正確的結果。
那麼,我們在經過一層呢?這次畫在純藍的些許透明(0, 0, 1, 0.8)的顏色上~

FinalColor.a = (0.5 * 1) + (0.8 * 0);

…?目標的透明值呢?它天生是個M,就該拋棄它嗎?
而且,如果我們選擇拋棄它,顯然是個極大的錯誤!
那麼,是要讓兩個透明值相加?還是相乘呢?
哪一種方式全看個人需求,但是請看看這篇的參考文章,就跟著用相乘吧XDDD
將AlphaSourceBlend設為One,
AlphaDestinationBlend設為InverseSourceAlpha。

再走一次計算流程吧!

FinalColor.a = (SourceColor.a * One) + (DestinationColor.a * InverseSourceAlpha);
FinalColor.a = (0.5 * 1) + (0.8 * (1 – 0.5));
FinalColor.a = (0.5 * 1) + (0.8 * 0.5);
FinalColor.a = (0.5) + (0.4);
FinalColor.a = 0.9;

嗯~看起來似乎正確了!要來幾層都沒問題啦!



本來想寫得簡單一些,沒想到還是打了這大串
就此暫時打住,剩下的留到下篇補完吧!
另外,如果有錯誤的地方麻煩告知一下,小弟手殘眼殘腦殘也不是五天六天的事情了

–Next

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.