1. 程式人生 > >逐片元操作

逐片元操作

轉載自: http://blog.sina.cn/dpool/blog/s/blog_17148af6d0102wzi2.html?from=timeline


這是渲染流水線的最後一步。逐片元操作(Per-Fragment Operation)是OpenGL中的說法,在DirectX中,這一階段被稱為輸出合併階段(Output-Merger)。Merger這個詞可能更容易讓讀者明白這一步驟的目的:合併。而OpenGL中的名字可以讓讀者明白這個階段的操作單位,即對每個片元進行一些操作。但要合併哪些資料,又要進行哪些操作呢?

這一階段有幾個主要任務.

1.決定每個片元的可見性。這涉及了很多測試工作,比如深度測試、模板測試等。

2.如果一個片元通過了所有測試,就需要把這個片元的顏色值和已經儲存在顏色緩衝區中的顏色進行合併或者說是混合。

需要指明的是,逐片元操作階段是高度可配置的,即我們可以設定每一步的操作細節。這個階段首先需要解決每個片元的可見性問題。需要進行一系列測試。就好比考試,一個片元只有通過了所有考試,才能最終獲得和GPU談判的資格,這個資格指的是它可以和顏色緩衝區進行合併。如果它沒有通過其中的某個測試,那麼之前為了產生這個片元所做的所有工作都是白費的,因為這個片元會被捨棄掉。下圖顯示了簡化後逐片元操作所做的操作:

逐片元操作 X

測試過程是個比較複雜的過程,而不同的圖形介面實現細節也不盡相同。這裡有兩個基本的測試–深度測試和模板測試的實現過程。能夠理解這些測試過程將關乎讀者是否可以理解以後提到的渲染佇列,尤其是處理透明效果時出現的問題。下圖給出了深度測試和模板測試的簡化流程:

逐片元操作

首先看模板測試(Stencil Test)

與之相關的是模板緩衝(Stencil
Buffer)。實際上,模板緩衝和顏色緩衝、深度緩衝是一類東西。GPU首先會讀取(使用讀取掩碼)模板緩衝中該片元位置的模 板值,然後將該值和讀取(使用讀取掩碼)到的參考值(reference
value)進行比較,這個比較函式可以是有開發者指定的,比如小於時捨棄該片元,或者大於等於時捨棄該片元。如果這個片元沒有通過測試,該片元就會被捨棄。不管一個片元有沒有通過模板測試。我們都可以通過模板測試和下面的深度測試結果來修改模板緩衝區,這個修改操作是由開發者指定的。開發者可以設定不同結果下的修改操作,比如,在失敗時模板緩衝區保持不變,通過時將模板緩衝區中對應位置的值加1等。模板測試通常用於限制渲染的區域。另外,模板測試還有一些更高階的用法,比如渲染陰影、輪廓渲染等。

如果一個片元幸運的是通過了模板測試,那麼它會進行下一個測試—深度測試(Depth Test)
。這個測試同樣是可以高度配置的。如果開啟了深度測試,GPU會把該片元的深度值和已經存在於深度緩衝區中的深度值進行比較。這個比較函式也由開發者設定,比如,如果小於時捨棄該片元,或者大於等於時捨棄該片元。通常這個比較函式是小於等於的關係。即如果這個片元的深度值大於等於當前深度緩衝區的值,那麼就會捨棄它。這是因為我們總想值顯示出離攝像機最近的物體,那麼那些被其他物體遮擋的就不需要出現在螢幕上。如果該片元沒有通過測試,該片元就會被捨棄。和模板測試有些不同的是,如果一個片元沒有通過深度測試,它就沒有權利更改深度緩衝區中的值。而如果它通過了測試,開發者還可以指定是否要用這個片元的深度值覆蓋掉原有的深度值,這是通過開啟/關閉深度寫入來做到的。以後我們會發現,透明效果和深度測試以及深度寫入的關係非常密切。

如果一個片元幸運的通過了上面的所有測試,它就可以自豪的來到合併功能的面前。

那麼為什麼需要合併呢?這裡討論的渲染過程是一個物體接著一個物體畫到螢幕上的。而每個畫素的顏色資訊被儲存到一個名為顏色緩衝的地方。因此,當我們執行這次渲染時,顏色緩衝區中往往已經有了上次渲染之後的顏色結果,那麼,我們是使用這次渲染得到的顏色完全覆蓋之前的結果,還是進行其他處理?這就是合併需要處理的問題。

對於不透明的物體,開發者可以關閉混合(Blend)操作。這時片元著色器得到的顏色值會直接覆蓋掉顏色緩衝區中的畫素值。但對於半透明物體,我們就可以使用混合操作來讓這個物體看起來是透明的。如下圖簡化版的混合操作流程圖:

逐片元操作

從上圖我們發現,混合操作也是可以高度配置的。開發者可以選擇開啟/關閉混合功能。如果沒有開啟混合功能,就會直接使用片元的顏色覆蓋掉顏色緩衝區的顏色,而這也是很多開發者無法得到透明效果的原因(沒有開啟混合功能)。如果開啟了混合,GPU會取出源顏色和目標顏色,將這兩種顏色進行混合。源顏色是指片元著色器中的顏色值,而目標顏色則是已經存在於顏色緩衝區中的顏色值。之後,會有一個混合函式來進行混合操作。這個混合函式通常和透明通道息息相關,比如根據透明通道的值進行相加、相減、相乘等。混合很像ps中對圖層的操作,每一層圖層可以選擇混合模式,混合模式決定了該圖層和下層圖層的混合結果,而我們看到圖片就是混合後的圖片。

上面的測試順序不是唯一的,而且雖然從邏輯上來說這些測試是在片元著色器之後進行的。但對於大多數GPU來說,它們儘可能在執行片元著色器之前就進行這些測試。為啥呢?想象下,當GPU在片元著色階段花了很大力氣終於計算出片元的顏色後,卻發現這個片元根本沒有通過這些檢驗,也就是說這個片元還是被捨棄了。
那之前的花費的計算成本全都浪費了。例如下圖:

逐片元操作

作為一個想充分提高效能的GPU,它會希望儘可能的早知道哪些片元是會被捨棄的,對於這些片元就不要在使用片元說色器來計算它們的顏色。在unity給出的渲染流水中,我們也可以發現它給出的深度測試是在片元著色器之前,這些深度測試提前執行的技術通常也被稱作Early-Z技術。以後再討論這個技術。

但如果將這些測試提前執行的話,其檢驗結果可能會與片元著色器中的一些操作衝突。比如我們在片元著色器進行了透明測試,而這個片元沒有通過,我們會在著色器中呼叫API來手動將其捨棄掉。這就導致GPU無法提前執行各種測試。因此,現代的GPU會判斷片元著色器中的操作是否和提前測試發生衝突,如果有衝突,就會禁用提前測試。但,這樣會造成效能上的下降,因為由更多片元需要被處理了。這也是透明測試會導致效能下降的原因。

當模型的圖元經過了上面層層計算和測試後,就會顯示到我們的螢幕上。我們螢幕行顯示的是顏色緩衝區中的顏色值。但,為了避免我們看到那些正在進行光柵化的圖元,GPU會使用雙重緩衝(Double
Buffering)的策略。這意味著,對場景的渲染是在幕後發生的,即後置緩衝(Back
Buffer)中。一旦場景已經被渲染到了後置緩衝中,GPU就會交換後置緩衝區和前置緩衝區中的內容,而前置緩衝區是之前顯示在螢幕上的影象。因此,保證了我們看到的影象是連續的。

上面講了很多,但真正的實現過程遠比上面講的要複雜,需要注意的是,上面給出的流水線的名稱、順序、可能和其他資料有些不同,一個原因是由於影象程式設計介面(如OpenGL和DirectX)的實現不僅相同。另一個原因是GPU在底層可能做了很多優化,比如上面說的會在片元著色器之前就進行深度測試,以避免無謂計算。

雖然渲染流水線比較複雜,但Untiy這個平臺為我們封裝了很多功能。更多時候,我們只需要在一個unity Shader
中設定一些輸入、編寫頂點著色器和片元著色器、設定一些狀態就可以達到大部分常見的螢幕效果。但這樣的缺點在於,封裝性會導致程式設計自由下降,導致初學者迷失方向,無法掌握其背後的原理,並在出現問題時,無法找到錯誤原因,這是在學習unity時普遍的遭遇。