短篇圖形學——透明紋理與半透明渲染


3樓貓 發佈時間:2024-03-29 11:32:28 作者:Hakumen Language

前言

自然界中存在的“半透明”其實是Transclusent
這個詞,指的是類似玉石那種透亮且光線可穿過的物體。只有這個範疇裡的極端情況例如玻璃,才比較接近渲染上的半透明Transparent這個概念。
如果要簡單來說兩者的區分,可以認為Transparent一般指考慮一層顏色的著色與混合,而Transclusent 則是一個需要結合次表面散射等物理模擬的複雜方案。本文後續中文提到的“半透明”都是指Transparent,對應的度量值是Alpha(往往被俗稱為透明度,但因為Alpha為100%時是不透明,所以嚴格來說應該是不透明程度)。
這個系列之前的文章中也提到過,半透明比不透明物體往往有更多消耗,主要體現在2個方面:
——不透明的片元可以被深度緩衝優化,半透明則不行。
——不透明的片元可以採用延遲渲染管線集中進行光照著色,半透明不行,一般來說必須使用前向渲染。
*這裡提供了之前講深度緩衝延遲渲染的文章鏈接。
要使用半透明渲染(一般包含透明紋理)的原因也很明確,主要是兩方面——使用透明紋理比起3D建模在顯示上有更好的效果和性價比(例如植物、頭髮等),或是要呈現自然界本身就是半透明的物體(如玻璃、水體等)。
在這個基礎上,如果談到粒子特效,那也是半透明渲染的主戰場,稍微用過遊戲引擎的應該都不陌生,不過渲染原理上沒有超出第二點的範疇。另外本文不會談到折射或次表面散射,而這兩者在渲染實際複雜的多層半透明結構中是常常需要考慮的。

1 透明像素裁剪

這裡只介紹透明紋理最基礎的一種應用——植物渲染。
(圖中是一個常見的植物紋理)

(圖中是一個常見的植物紋理)

植物的葉子作為自然界比較複雜的元素,如果每一片葉子都用建模以一些三角面來體現,在寫實風格的遊戲裡肯定是一個巨量的數字。
這種場合就很適合透明紋理出場。以圖中植物的貼圖為例,可以看到圖中有大量的透明像素(Alpha值為0)。
在渲染對應的片元時,對於這種紋理會進行透明度檢測(AlphaTest),丟棄Alpha值在給定閾值以下的像素。這裡的“丟棄”英文是discard,意思是不進行任何處理,即對緩衝區和屏幕像素都沒有後續讀寫操作了。
(圖中是經過AlphaTest丟棄透明像素後得到的結果)

(圖中是經過AlphaTest丟棄透明像素後得到的結果)

這種處理得到的渲染結果,往往由於紋理精度問題而顯得有鋸齒感或顆粒感,一般要結合抗鋸齒使用。另外,相同的像素處理方式也需要應用到對應的陰影pass中,使陰影與實體的輪廓一致。
順便一提,使用AlphaTest會打破現在較新的圖形硬件的Early-Z(預計算深度)優化,這裡就不展開了。

2 半透明渲染基本原理

講到半透明渲染,其實際渲染的結果等同於——使用一定的顏色混合規則,使半透明材質與其後的背景顏色混合得到“看起來是半透明”的最終顏色
這段概括裡引入了兩個基本預設:
1)半透明是一種顏色混合——既有符合視覺直覺的混合,也有其它美術風格化上的混合方案。但反過來顏色混合遠不止用來做半透明混合,還能提供各種圖像處理效果。
2)透明的混合需要遵循從遠到近渲染,因為顏色的前後會直接影響混合的結果。
這兩點在後面的章節裡還會展開論述。
下面的圖片展示了渲染順序錯誤的一種情況,還展示了半透明顏色混合的結果:
(渲染順序錯誤時)

(渲染順序錯誤時)

(正確從後向前渲染並混合時)

(正確從後向前渲染並混合時)

和不透明片元渲染比較起來,由於不能依賴深度緩衝,因此半透明渲染在遠近排序或是順序無關的渲染策略上都提出了新的課題。

3 半透明渲染的顏色

圖形學上的顏色混合可以認為是將當前顏色和目標顏色進行混合,代入各種公式進行預算,得到最終的結果。因此混合模式可以理解成源色(及係數)、目標色(及係數)、公式這3者的各種組合。
最常用的有俗稱AdditiveAlphaBlend這2種歸納好的方案(即這兩者都不是學術概念)。前者是一種常用的提高亮度的半透明渲染方案,主要用於粒子特效之類,最終顏色是當前顏色和背景像素顏色直接相加的;後者得到的顏色則是當前顏色和背景色的按一定規則的組合。(這裡背景像素顏色指顏色緩衝中以渲染出的顏色)
Additive(圖形軟件裡也被稱為Linear Dodge)的公式是:
C¯result=Src+Dst (相當於前面一篇文章提到的人眼感知顏色的線性疊加)
傳統意義上的AlphaBlend(圖形軟件裡也被稱為Normal混合)公式是(:
C¯result=C¯source∗Fsource+C¯destination∗Fdestination(F = Alpha時
其中C就是顏色,F是混合係數,而最傳統的混合係數就是Alpha值。GL裡寫法就是 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ,這裡就會進行RGB和A通道統一規則的混合。
這裡引用圖文來說明一個基本的AlphaBlend計算例子,假設綠色Alpha值為0.6:
(圖中行向量數字代表最終的RGBA值)

(圖中行向量數字代表最終的RGBA值)

在此基礎上還可以拆分RGB和A通道的混合規則,或者進一步引入其它的混合公式。例如還有一些不常見的如減法 C¯result=Src−Dst 和反減 C¯result=Dst -Src,往往在對比色相關的效果中有其作用;F乘數也可以有不同的係數,而不僅僅只是Alpha值;甚至有一些公式是分段函數的形式,或者是更復雜的指數或對數形式。
(這裡列舉了GL中的BlendFunc的一些參數)

(這裡列舉了GL中的BlendFunc的一些參數)

在這個基礎上把公式擴展,又能帶來更廣泛的一些混合模式——人們已經為一些主流的混合模式起了各種符合直覺(或反直覺)的名字,如果用過圖形軟件的話應該都不陌生。這些在遊戲引擎中也可以通過Shader實現,很多也已經被引擎預集成了。
(這裡不特別指出時透明度都是要經過混合的,例如T=S也不是直接賦值而是經過了AlphaBlend函數)

(這裡不特別指出時透明度都是要經過混合的,例如T=S也不是直接賦值而是經過了AlphaBlend函數)

*個人覺得這些名詞的中文翻譯其實都挺抽象的,例如Multiply是正片疊底 、Screen是濾色——可能因為我不是學美術的。另外,上面公式對於兩個顏色Alpha值都為1也能產生混合效果,圖形軟件中也有大量應用(但已經超出這裡討論的半透明混合的範疇了)。
(一圖流展示各種混合結果)

(一圖流展示各種混合結果)

4 半透明物體排序的難度

不透明物體的渲染一般考慮剔除即可,具體片元可以靠深度緩衝來實現正確渲染。而半透明由於需要混合,只比對錨點或是以包圍盒做簡單策略都無法解決物體有穿插的情況。
進一步能想到的就是比對三角面,以及比對片元像素——精度逐步變高,但性能消耗則是能做和基本不能做的區別了。後面章節會介紹現有的一些可行方案,基本都是一定的近似策略。
目前主流引擎的傳統渲染管線裡也不提供基於三角面或片元的排序比對方案,基本思路還是物體排序(可以一定程度上修正渲染隊列Queue),並關閉深度寫入只進行顏色混合。
按照這一策略進行前向渲染時,即便出現了多透明體重疊的情況,在關閉深度寫入的情況下,至少能保證所有位於前方的透明體穩定通過深度測試,並保證能以某種混合呈現出來,不至於在攝像機變換時突顯或消失。但是基於前面提到的,如果局部順序不對得到的顏色其實是不對的
在不進行定製化渲染的情況下,多個透明片穿插的情況其實渲染結果是不對的,在透明物體是凹面體時其內部結構的排序可能也是不對的。
(圖中列舉了半透明渲染中常見的一種無法通過整體排序做對的情況)

(圖中列舉了半透明渲染中常見的一種無法通過整體排序做對的情況)


5 Screen-Door Transparency

玩過一些主機遊戲的玩家應該對這個效果不陌生,因為這是一類把物體整個渲染出來並進行紋理遮罩處理的方案。
(圖中展示了這一方案展示出的不同半透明效果)

(圖中展示了這一方案展示出的不同半透明效果)

這種透明感來自於像素渲染的間隔,即渲染的像素是完全不透明的,而不渲染的像素則透出背景色。可以認為是利用像素比較近產生的視覺混合感,並不是真的顏色混合;真實的顏色取捨類似AlphaTest,用或者丟棄。
這個方案的基礎思想還是不透明渲染的那一套,且透明度只能是幾個特定的值,並且在接近50%時才有最好的效果;同時整體的透明度必須是均勻的,即不能表現出基於局部Alpha值不同的混合效果。
如果需要多層的半透明混合,就需要在不同的層使用不同的遮罩模式(遮罩紋理或遮罩密度不同),且層數不能太多。

6 順序無關半透明算法

在不透明渲染中,深度緩衝就是一個順序無關的好方案;所以在做半透明渲染時,人們還是想以類似的思想來設計管線,付出額外繪製或計算次數的代價來避免做整體的精確排序。為此人們提出了順序無關的半透明算法(Order-independent transparency OIT)的思想。
OIT思想在發展過程中主要有精確(Exact)非精確(Approximate 詞語原意是近似兩個方向。精確OIT最終還是要對每個半透明片元做排序,目前在算法上沒什麼突破,主要是通過硬件加速來優化;非精確OIT則不要求絕對的精確,這就為遊戲引擎引入一些Trick提供了空間。
這裡稍微展開介紹非精確OIT其中一種——深度剝離(Depth Peeling)
這一方案的基本思想是先進行無關順序的半透明片元繪製(這裡可以理解成是pass而不是實際繪製),並將最淺的深度排入一個多層深度緩衝結構中;渲染時在基於這個多層深度緩衝進行從遠到近的渲染,就能逐層正確混合了。
(圖中展示了Nvidia官方文檔中演示的不同層深度剝離效果)

(圖中展示了Nvidia官方文檔中演示的不同層深度剝離效果)

這一方案的侷限也很明顯,就是層數不能是無限的,因此也不能保證百分百就是精確的。不過對於半透明物體有限的場合來說基本是足夠了。後續人們還提出了雙向深度剝離作為優化方案,用來提高計算效率——類似的雙向優化思想其實圖形學看多了會發現很多。
最後再列舉一些其它的算法,例如Stochastic Transparency主要基於超採樣思想, Adaptive Transparency 考慮減少被排序的片元數量等。這裡就不逐一展開了,有興趣可以去看看Wiki。

結語

查資料的時候發現了一個趣聞,就是世嘉的Dreamcast主機曾經在硬件設計上支持過OIT。印象中那時候主機遊戲似乎不太多用半透明渲染,因此這一設計也不知道是不是太過超前了。
由於半透明渲染為了不丟失信息要逐層進行顏色混合,因此引擎中也有一個性能監測指標叫Overdraw——因為很多時候疊特別多層一起混合得到一個顏色其實往往也是不合理的,因此對於Overdraw特別多的場合往往需要進行針對性的優化(調整美術資源或引入LOD等)。
最後是一些資料鏈接:
混合模式Wiki
Screen-door transparency 的shaderToy
順序無關半透明渲染Wiki
Nvidia的OIT文檔,包含深度剝離


© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com