@學習
透明是遊戲中常常要使用的一種效果,在實時渲染中要實現透明效果,一般會在渲染模型時控制它的透明通道(Alpha Channel)。當開啓透明混合後,當一個物體被渲染到屏幕上時,每一個片元除了顏色值和深度值外,它還有另外一個屬性——透明度。當透明度爲1時,表示該像素是徹底不透明的,而當其爲0時,則表示該像素徹底不會顯示。
在Unity中,咱們一般使用兩種方式來實現透明效果:第一種是使用透明度測試(Alpha Test),這種方法其實沒法獲得真正的半透明效果;另外一種是透明度混合(Alpha Blending)。
在以前的學習中,咱們歷來沒有強調過渲染順序的問題。也就是說,當場景中包含不少模型時,咱們沒有考慮是先渲染A,再渲染B,最後再渲染C,仍是按照其餘的順序來渲染。事實上,對於不透明(opaque)物體,不考慮它們的渲染順序也能獲得正確的排序效果,這是因爲強大的深度緩衝(depth buffer,也被稱爲z-buffer)的存在。在實時渲染中,深度緩衝是用於解決可見性(visibility)問題的,它能夠決定哪些物體哪些部分會被渲染在前面,而哪些部分又會被其它物體遮擋。它的基本思想是:根據深度緩衝中的值來判斷該片元距離攝像機的距離,當渲染一個片元時,須要把它的深度值和已經存在於深度緩衝區中的值進行比較(若是開啓了深度測試),若是它的值距離攝像機更遠,那麼說明這個片元不該該被渲染到屏幕上(有物體擋住了它);不然這個片元應該覆蓋掉此時顏色緩衝中的像素值,並把它的深度值更新到深度緩衝中(若是開啓了深度寫入)。
使用深度緩衝,可使咱們沒必要關心不透明物體的渲染順序,例如A擋住了B,即使咱們先渲染A再渲染B也不用擔憂B會遮蓋掉A,由於在進行深度測試時會判斷出B距離攝像機更遠,也就不會寫入到顏色緩衝中。但要實現透明效果,事情就不那麼簡單了,這是由於當使用透明度混合時,咱們關閉了深度寫入(ZWrite)。
簡單來講透明度測試和透明度混合的基本原理以下:
(1)透明度測試:
它是一種「霸道極端」的機制,只要一個片元的透明度不知足條件(一般是小於某個閾值),那麼它對應的片元就會被捨棄。被捨棄的片元不會再進行任何處理,也不會對顏色緩衝產生任何影響;不然就會按照普通的不透明物體的處理方式來處理它,即進行深度測試、深度寫入等。也就是說,透明度測試是不須要關閉深度寫入的,它和其它不透明物體最大的不一樣是它會根據透明度來捨棄一些片元。雖然簡單,可是它產生的效果也很極端,要麼徹底透明,即看不到,要麼徹底不透明,就像不透明物體那樣。
(2)透明度混合:
這種方法能夠獲得真正的半透明效果。它會使用當前片元的透明度做爲混合因子,與已經存儲在顏色緩衝中的顏色進行混合,獲得新的顏色。可是透明度混合須要關閉深度寫入,這使咱們要很是當心物體的渲染順序。須要注意的是,透明度混合子關閉了深度寫入,但沒有關閉深度測試。這意味着當使用透明度深度混合渲染一個片元時,仍是會比較它的深度值與當前深度緩衝區中的深度值,若是它的深度值距離攝像機更遠,那麼就不會再進行混合操做。這一點決定了,當一個不透明物體出如今一個透明物體前面,而咱們先渲染了不透明物體,它仍然能夠正常地遮住不透明物體。也便是說,對於透明混合度來講,深度緩衝是隻讀的。測試
前面說到,對於透明度混合技術,須要關閉深度寫入,此時咱們就須要當心處理透明物體的渲染順序。那麼咱們爲何要關閉深度寫入呢?若是不關閉深度寫入,一個半透明表面背後的表面原本是能夠透過他被咱們看到的,但因爲深度測試時判斷結果是該半透明表面距離攝像機更近,致使後面的表面將會被剔除,咱們也就沒法透過半透明表面看到後面的物體了。可是,咱們由此就破壞了深度緩衝的工做機制,這是一個很是糟糕的事情,儘管咱們不得不這麼作。關閉深度寫入致使渲染順序將變得很是重要。
咱們來考慮最簡單的狀況。假設場景裏有兩個物體A和B,以下圖所示,其中A是半透明物體,而B是不透明物體。
咱們來考慮不一樣的渲染順序會有什麼結果。
(1)第一種狀況:咱們先渲染B在渲染A。那麼因爲不透明物體開啓了深度測試和深度寫入,而此時深度緩衝中沒有任何有效數據,所以B首先會寫入顏色緩衝和深度緩衝。隨後,咱們渲染A,透明物體仍然會進行深度測試,所以咱們會發現和B相比A距離攝像機更近,所以咱們會使用A的透明度來和顏色緩衝區中的B的顏色進行混合,獲得正確的半透明效果。
(2)第二種狀況:咱們先渲染A,再渲染B。渲染A時,深度緩衝區中沒有任何有效數據,所以A直接寫入顏色緩衝,但因爲對半透明物體關閉了深度寫入,所以A不會修改深度緩衝。等到渲染B時,B會進行深度測試,它會發現,「咦,深度緩存中尚未人來過,那我就放心的寫入顏色緩衝了」,結果就是B會直接覆蓋A的顏色。從視覺上來看,B就出如今了A的前面,而這是錯誤的。
從這個例子能夠看出,當關閉了深度寫入後,渲染順序是多麼重要。由此,咱們知道,咱們應該在不透明物體渲染完以後在渲染半透明物體。那麼若是都是半透明物體,渲染順序還重要嗎?答案是確定的。仍是假設場景裏有兩個物體A和B,以下圖所示,其中A和B都是半透明物體。
咱們仍是考慮不一樣的渲染順序有什麼不一樣的結果。
(1)第一種狀況,咱們先渲染B,再渲染A。那麼B會正常寫入顏色緩衝,而後A會和顏色緩衝中的B顏色進行混合,獲得正確的混合結果。
(2)第二種狀況,咱們先渲染A,再渲染B。那麼A會先寫入顏色緩衝,隨後B會和顏色緩衝中的A進行混合,這樣混合結果會徹底反過來,看起來好像B在A的前面,獲得的就是錯誤的半透明結構。
從這個例子能夠看出,半透明物體之間也是要符合必定的渲染順序的。
基於這兩點,渲染引擎通常都會先對物體進行排序,在渲染,經常使用的方法是:
(1)先渲染全部不透明物體,並開啓它們的深度測試和深度寫入。
(2)把半透明物體按它們距離攝像機的遠近進行排序,而後按照從後往前的順序渲染這些半透明物體,並開啓它們的深度測試,但關閉深度寫入。
那麼,問題都解決了嗎?不幸的是,讓然沒有。在一些狀況下,半透明物體仍是會出現穿幫鏡頭。若是咱們仔細想一想的話,上面第二步中的渲染順序仍然是含糊不清的——按它們距離攝像機的遠近進行排序,那麼它們距離攝像機的遠近是如何決定的呢?讀者可能會立刻脫口而出,「就是距離攝像的深度值嘛」。可是深度緩衝中的值實際上是像素級別的,即每一個像素都有一個深度值,可是如今咱們對單個物體級別進行排序,這意味着排序的結果是,要麼物體A所有在B前面渲染,要麼A所有在B後面渲染。但若是存在循環重疊的狀況,那麼使用這種方法就永遠沒法獲得正確的結果。下圖給出了3個物體循環重疊的狀況。
在圖中,因爲3個物體互相重疊,咱們不可能獲得一個正確的排序順序。這種時候,咱們能夠選擇把物體拆分紅兩個部分,而後再進行正確的排序。但即使咱們經過了分割的方法解決了循環覆蓋的問題,還會有其它狀況來搗亂,以下圖所給出的狀況:
這裏的問題是如何排序?咱們知道,一個物體的網格結構每每佔據了空間中的某一塊區域,也就是說,這個網格上每個點的深度值可能都是不同的,咱們選擇哪一個深度值來做爲整個物體的深度值和其它物體進行排序呢?是網格中點嗎?仍是最遠的點?仍是最近的點?不幸的是,對於上圖的狀況,選擇哪一個深度值都會獲得錯誤的結果,咱們的排序結果老是A在B的前面,但實際上A有一部分被B遮擋了。這也意味着,一旦選定了一種判斷方式後,在某些狀況下半透明物體之間必定會出現錯誤的遮擋問題。這種問題的解決方法一般也是分割網格。
儘管結論是,老是會有一些狀況打亂咱們的陣腳,但因爲上述方法足夠有效而且容易實現,所以大多數遊戲引擎都使用了這樣的方法。爲了減小錯誤排序的狀況,咱們能夠儘量讓模型是凸面體,而且考慮將複雜的模型拆分紅能夠獨立排序的多個子模型等。其實就算排序錯誤結果有時也不會很是糟糕,若是咱們不想分割網格,能夠試着讓透明通道更加柔和,使穿插看起來並非那麼明顯。咱們也可使用開啓了深度寫入的半透明效果來近似模擬物體的半透明。spa
Unity爲了解決渲染順序的問題提供了渲染隊列(render queue)這一解決方案。咱們可使用SubShader的Queue標籤來決定咱們的模型將歸於哪一個渲染隊列。Unity在內部中使用一系列整數索引來表示每一個渲染隊列,且索引號越小表示越早被渲染。在Unity5中,Unity提早定義了5個渲染隊列(與Unity5以前的版本相比多了一個AlphaTest渲染隊列),固然在每一個隊列中間咱們可使用其餘隊列。下表給出了這5個提早定義的隊列以及它們的描述。
code
所以,若是咱們想要經過透明度測試來實現透明效果,代碼中應該包含相似下面的代碼:blog
SubShader{ Tags{"Queue"="AlphaTest"} Pass{ ... } }
若是咱們想要經過透明度混合來實現透明效果,代碼中應該包含相似下面的代碼:排序
SubShader{ Tags{"Queue"="Transparent"} Pass{ ZWrite Off ...... } }
其中,ZWrite Off用於關閉深度寫入,在這裏咱們選擇把它寫在Pass中。咱們也能夠把它寫在SubShader中,這意味着該SubShader下的全部Pass都會關閉深度寫入。索引