A. Unity Shader-渲染隊列,ZTest,ZWrite,Early-Z(轉)
簡介
在渲染階段,引擎所做的工作是把所有場景中的對象按照一定的策略(順序)進行渲染。最早的是畫家演算法,顧名思義,就是像畫家畫畫一樣,先畫後面的物體,如果前面還有物體,那麼就用前面的物體把物體覆蓋掉,不過這種方式由於排序是針對物體來排序的,而物體之間也可能有重疊,所以效果並不好。所以目前更加常用的方式是z-buffer演算法,類似顏色緩沖區緩沖顏色,z-buffer中存儲的是當前的深度信息,對於每個像素存儲一個深度值,這樣,我們屏幕上顯示的每個像素點都會進行深度排序,就可以保證繪制的遮擋關系是正確的。而控制z-buffer就是通過ZTest,和ZWrite來進行。但是有時候需要更加精準的控制不同類型的對象的渲染順序,所以就有了渲染隊列。今天就來學習一下渲染隊列,ZTest,ZWrite的基本使用以及分析一下Unity為了Early-Z所做的一些優化。
Unity中的幾種渲染隊列
首先看一下Unity中的幾種內置的渲染隊列,按照渲染順序,從先到後進行排序,隊列數越小的,越先渲染,隊列數越大的,越後渲染。
Unity中設置渲染隊列也很簡單,我們不需要手動創建,也不需要寫任何腳本,只需要在shader中增加一個Tag就可以了,當然,如果不加,那麼就是默認的渲染隊列Geometry。比如我們需要我們的物體在Transparent這個渲染隊列中進行渲染的話,就可以這樣寫:
我們可以直接在shader的Inspector面板上看到shader的渲染隊列:
另外,我們在寫shader的時候還經常有個Tag叫RenderType,不過這個沒有Render Queue那麼常用,這里順便記錄一下:
Opaque : 用於大多數著色器(法線著色器、自發光著色器、反射著色器以及地形的著色器)。
Transparent :用於半透明著色器(透明著色器、粒子著色器、字體著色器、地形額外通道的著色器)。
TransparentCutout : 蒙皮透明著色器(Transparent Cutout,兩個通道的植被著色器)。
Background : 天空盒著色器。
Overlay : GUITexture,鏡頭光暈,屏幕閃光等效果使用的著色器。
TreeOpaque : 地形引擎中的樹皮。
TreeTransparentCutout : 地形引擎中的樹葉。
TreeBillboard : 地形引擎中的廣告牌樹。
Grass : 地形引擎中的草。
GrassBillboard : 地形引擎何中的廣告牌草。
相同渲染隊列中不透明物體的渲染順序
拿出Unity,創建三個立方體,都使用默認的bump diffuse shader(渲染隊列相同),分別給三個不同的材質(相同材質的小頂點數的物體引擎會動態合批),用Unity5帶的Frame Debug工具查看一下Draw Call。(Unity5真是好用得多了,如果用4的話,還得用NSight之類的抓幀)
可以看出,Unity中對於不透明的物體,是採用了從前到後的渲染順序進行渲染的,這樣,不透明物體在進行完vertex階段,進行Z Test,然後就可以得到該物體最終是否在屏幕上可見了,如果前面渲染完的物體已經寫好了深度,深度測試失敗,那麼後面渲染的物體就直接不會再去進行fragment階段。(不過這里需要把三個物體之間的距離稍微拉開一些,本人在測試時發現,如果距離特別近,就會出現渲染次序比較亂的情況,因為我們不知道Unity內部具體排序時是按照什麼標准來判定的哪個物體離攝像機更近,這里我也就不妄加猜測了)
相同渲染隊列中半透明物體的渲染順序
透明物體的渲染一直是圖形學方面比較蛋疼的地方,對於透明物體的渲染,就不能像渲染不透明物體那樣多快好省了,因為透明物體不會寫深度,也就是說透明物體之間的穿插關系是沒有辦法判斷的,所以半透明的物體在渲染的時候一般都是採用從後向前的方法進行渲染,由於透明物體多了,透明物體不寫深度,那麼透明物體之間就沒有所謂的可以通過深度測試來剔除的優化,每個透明物體都會走像素階段的渲染,會造成大量的Over Draw。這也就是粒子特效特別耗費性能的原因。
我們實驗一下Unity中渲染半透明物體的順序,還是上面的三個立方體,我們把材質的shader統一換成粒子最常用的Particle/Additive類型的shader,再用Frame Debug工具查看一下渲染的順序:
半透明的物體渲染的順序是從後到前,不過由於半透相關的內容比較復雜,就先不在這篇文章中說了,打算另起一篇。
自定義渲染隊列
Unity支持我們自定義渲染隊列,比如我們需要保證某種類型的對象需要在其他類型的對象渲染之後再渲染,就可以通過自定義渲染隊列進行渲染。而且超級方便,我們只需要在寫shader的時候修改一下渲染隊列中的Tag即可。比如我們希望我們的物體要在所有默認的不透明物體渲染完之後渲染,那麼我們就可以使用Tag{「Queue」 = 「Geometry+1」}就可以讓使用了這個shader的物體在這個隊列中進行渲染。
還是上面的三個立方體,這次我們分別給三個不同的shader,並且渲染隊列不同,通過上面的實驗我們知道,默認情況下,不透明物體都是在Geometry這個隊列中進行渲染的,那麼不透明的三個物體就會按照cube1,cube2,cube3進行渲染。這次我們希望將渲染的順序反過來,那麼我們就可以讓cube1的渲染隊列最大,cube3的渲染隊列最小。貼出其中一個的shader:
其他的兩個shader類似,只是渲染隊列和輸出顏色不同。
通過渲染隊列,我們就可以自由地控制使用該shader的物體在什麼時機渲染。比如某個不透明物體的像素階段操作較費,我們就可以控制它的渲染隊列,讓其渲染更靠後,這樣可以通過其他不透明物體寫入的深度剔除該物體所佔的一些像素。
PS:這里貌似發現了個問題,我們在修改shader的時候一般不需要什麼其他操作就可以直接看到修改後的變化,但是本人改完渲染隊列後,有時候會出現從shader的文件上能看到渲染隊列的變化,但是從渲染結果以及Frame Debug工具中並沒有看到渲染結果的變化,重啟Unity也沒有起到作用,直到我把shader重新賦給材質之後,變化才起了效果...(猜測是個bug,因為看到網上還有和我一樣的倒霉蛋被這個坑了,本人的版本是5.3.2,害我差點懷疑昨天是不是喝了,剛實驗完的結果就完全不對了...)
**ZTest(深度測試)和ZWrite(深度寫入) **
上一個例子中,雖然渲染的順序反了過來,但是物體之間的遮擋關系仍然是正確的,這就是z-buffer的功勞,不論我們的渲染順序怎樣,遮擋關系仍然能夠保持正確。而我們對z-buffer的調用就是通過ZTest和ZWrite來實現的。
首先看一下ZTest,ZTest即深度測試,所謂測試,就是針對當前對象在屏幕上(更准確的說是frame buffer)對應的像素點,將對象自身的深度值與當前該像素點緩存的深度值進行比較,如果通過了,本對象在該像素點才會將顏色寫入顏色緩沖區,否則否則不會寫入顏色緩沖。ZTest提供的狀態較多。 ZTest Less(深度小於當前緩存則通過, ZTest Greater(深度大於當前緩存則通過),ZTest LEqual(深度小於等於當前緩存則通過),ZTest GEqual(深度大於等於當前緩存則通過),ZTest Equal(深度等於當前緩存則通過),ZTest NotEqual(深度不等於當前緩存則通過),ZTest Always(不論如何都通過)。注意,ZTest Off等同於ZTest Always,關閉深度測試等於完全通過。
下面再看一下ZWrite,ZWrite比較簡單,只有兩種狀態, ZWrite On(開啟深度寫入)和ZWrite Off(關閉深度寫入) 。當我們開啟深度寫入的時候,物體被渲染時針對物體在屏幕(更准確地說是frame buffer)上每個像素的深度都寫入到深度緩沖區;反之,如果是ZWrite Off,那麼物體的深度就不會寫入深度緩沖區。但是,物體是否會寫入深度,除了ZWrite這個狀態之外,更重要的是需要深度測試通過,也就是ZTest通過,如果ZTest都沒通過,那麼也就不會寫入深度了。就好比默認的渲染狀態是ZWrite On和ZTest LEqual,如果當前深度測試失敗,說明這個像素對應的位置,已經有一個更靠前的東西佔坑了,即使寫入了,也沒有原來的更靠前,那麼也就沒有必要再去寫入深度了。所以上面的ZTest分為通過和不通過兩種情況,ZWrite分為開啟和關閉兩種情況的話,一共就是四種情況:
1.深度測試通過,深度寫入開啟:寫入深度緩沖區,寫入顏色緩沖區;
2.深度測試通過,深度寫入關閉:不寫深度緩沖區,寫入顏色緩沖區;
3.深度測試失敗,深度寫入開啟:不寫深度緩沖區,不寫顏色緩沖區;
4.深度測試失敗,深度寫入關閉:不寫深度緩沖區,不寫顏色緩沖區;
Unity中默認的狀態(寫shader時什麼都不寫的狀態)是ZTest LEqual和ZWrite On,也就是說默認是開啟深度寫入,並且深度小於等於當前緩存中的深度就通過深度測試,深度緩存中原始為無限大,也就是說離攝像機越近的物體會更新深度緩存並且遮擋住後面的物體。如下圖所示,前面的正方體會遮擋住後面的物體:
寫幾個簡單的小例子來看一下ZTest,ZWrite以及Render Queue這幾個狀態對渲染結果的控制。
讓綠色的對象不被前面的立方體遮擋,一種方式是關閉前面的藍色立方體深度寫入:
通過上面的實驗結果,我們知道,按照從前到後的渲染順序,首先渲染藍色物體,藍色物體深度測試通過,顏色寫入緩存,但是關閉了深度寫入,藍色部分的深度緩存值仍然是默認的Max,後面渲染的綠色立方體,進行深度測試仍然會成功,寫入顏色緩存,並且寫入了深度,因此藍色立方體沒有起到遮擋的作用。
另一種方式是讓綠色強制通過深度測試:
這個例子中其他立方體的shader使用默認的渲染方式,綠色的將ZTest設置為Always,也就是說不管怎樣,深度測試都通過,將綠色立方體的顏色寫入緩存,如果沒有其他覆蓋了,那麼最終的輸出就是綠色的了。
那麼如果紅色的也開了ZTest Always會怎麼樣?
在紅色立方體也用了ZTest Always後,紅色遮擋了綠色的部分顯示為了紅色。如果我們換一下渲染隊列,讓綠色在紅色之前渲染,結果就又不一樣了:
更換了渲染隊列,讓綠色的渲染隊列+1,在默認隊列Geometry之後渲染,最終重疊部分又變回了綠色。可見,當ZTest都通過時,上一個寫入顏色緩存的會覆蓋上一個,也就是說最終輸出的是最後一個渲染的對象顏色。
再看一下Greater相關的部分有什麼作用,這次我們其他的都使用默認的渲染狀態,綠色的立方體shader中ZTest設置為Greater:
這個效果就比較好玩了,雖然我們發現在比較深度時,前面被藍色立方體遮擋的部分,綠色的最終覆蓋了藍色,是想要的結果,不過其他部分哪裡去了呢?簡單分析一下,渲染順序是從前到後,也就是說藍色最先渲染,默認深度為Max,藍色立方體的深度滿足LEqual條件,就寫入了深度緩存,然後綠色開始渲染,重疊的部分的深度緩存是藍色立方體寫入的,而綠色的深度值滿足大於藍色深度的條件,所以深度測試通過,重疊部分顏色更新為綠色;而與紅色立方體重合的部分,紅色立方體最後渲染,與前面的部分進行深度測試,小於前面的部分,深度測試失敗,重疊部分不會更新為紅色,所以重疊部分最終為綠色。而綠色立方體沒有與其他部分重合的地方為什麼消失了呢?其實是因為綠色立方體渲染時,除了藍色立方體渲染的地方是有深度信息的,其他部分的深度信息都為Max,藍色部分用Greater進行判斷,肯定會失敗,也就不會有顏色更新。
有一個好玩的效果其實就可以考ZTest Greater來實現,就是游戲裡面經常出現的,當玩家被其他場景對象遮擋時,遮擋的部分會呈現出X-光的效果;其實是在渲染玩家時,增加了一個Pass,默認的Pass正常渲染,而增加的一個Pass就使用Greater進行深度測試,這樣,當玩家被其他部分遮擋時,遮擋的部分才會顯示出來,用一個描邊的效果渲染,其他部分仍然使用原來的Pass即可。
Early-Z技術
傳統的渲染管線中,ZTest其實是在Blending階段,這時候進行深度測試,所有對象的像素著色器都會計算一遍,沒有什麼性能提升,僅僅是為了得出正確的遮擋結果,會造成大量的無用計算,因為每個像素點上肯定重疊了很多計算。因此現代GPU中運用了Early-Z的技術,在Vertex階段和Fragment階段之間(光柵化之後,fragment之前)進行一次深度測試,如果深度測試失敗,就不必進行fragment階段的計算了,因此在性能上會有很大的提升。但是最終的ZTest仍然需要進行,以保證最終的遮擋關系結果正確。前面的一次主要是Z-Cull為了裁剪以達到優化的目的,後一次主要是Z-Check,為了檢查,如下圖:
Early-Z的實現,主要是通過一個Z-pre-pass實現,簡單來說,對於所有不透明的物體(透明的沒有用,本身不會寫深度),首先用一個超級簡單的shader進行渲染,這個shader不寫顏色緩沖區,只寫深度緩沖區,第二個pass關閉深度寫入,開啟深度測試,用正常的shader進行渲染。其實這種技術,我們也可以借鑒,在渲染透明物體時,因為關閉了深度寫入,有時候會有其他不透明的部分遮擋住透明的部分,而我們其實不希望他們被遮擋,僅僅希望被遮擋的物體半透,這時我們就可以用兩個pass來渲染,第一個pass使用Color Mask屏蔽顏色寫入,僅寫入深度,第二個pass正常渲染半透,關閉深度寫入。
關於Early-Z技術可以參考ATI的論文Applications of Explicit Early-Z Culling以及PPT,還有一篇Intel的文章。
Unity渲染順序總結
如果我們先繪制後面的物體,再繪制前面的物體,就會造成over draw;而通過Early-Z技術,我們就可以先繪制較近的物體,再繪制較遠的物體(僅限不透明物體),這樣,通過先渲染前面的物體,讓前面的物體先佔坑,就可以讓後面的物體深度測試失敗,進而減少重復的fragment計算,達到優化的目的。Unity中默認應該就是按照最近距離的面進行繪制的,我們可以看一下Unity官方的文檔中顯示的:
從文檔給出的流程來看,這個Depth-Test發生在Vertex階段和Fragment階段之間,也就是上面所說的Early-Z優化。
簡單總結一下Unity中的渲染順序: 先渲染不透明物體,順序是從前到後;再渲染透明物體,順序是從後到前 。
Alpha Test(Discard)在移動平台消耗較大的原因
從本人剛剛開始接觸渲染,就開始聽說移動平台Alpha Test比較費,當時比較納悶,直接discard了為什麼會費呢,應該更省才對啊?這個問題困擾了我好久,今天來刨根問底一下。還是跟我們上面講到的Early-Z優化。正常情況下,比如我們渲染一個面片,不管是否是開啟深度寫入或者深度測試,這個面片的光柵化之後對應的像素的深度值都可以在Early-Z(Z-Cull)的階段判斷出來了;而如果開啟了Alpha Test(Discard)的時候,discard這個操作是在fragment階段進行的,也就是說這個面片光柵化之後對應的像素是否可見,是在fragment階段之後才知道的,最終需要靠Z-Check進行判斷這個像素點最終的顏色。其實想像一下也能夠知道,如果我們開了Alpha Test並且還用Early-Z的話,一塊本來應該被剃掉的地方,就仍然寫進了深度緩存,這樣就會造成其他部分被一個完全沒東西的地方遮擋,最終的渲染效果肯定就不對了。所以,如果我們開啟了Alpha Test,就不會進行Early-Z,Z Test推遲到fragment之後進行,那麼這個物體對應的shader就會完全執行vertex shader和fragment shader,造成over draw。有一種方式是使用Alpha Blend代替Alpha Test,雖然也很費,但是至少Alpha Blend雖然不寫深度,但是深度測試是可以提前進行的,因為不會在fragment階段再決定是否可見,因為都是可見的,只是透明度比較低罷了。不過這樣只是權宜之計,Alpha Blend並不能完全代替Alpha Test。
關於Alpha Test對於Power VR架構的GPU性能的影響,簡單引用一下官方的鏈接以及一篇討論帖:
最後再附上兩篇參考文章
http://blog.csdn.net/candycat1992/article/details/41599167
http://blog.csdn.net/arundev/article/details/7895839
B. shader怎麼控制2d描邊的寬度
繪制路徑以後選擇畫筆工具——然後在路徑面板上右擊,選擇沿路徑描邊就可以了你可能是沒有選擇畫筆工具就進行描邊了吧?這是不行的。
C. shader描邊效果出現問題,求助
步驟1:
比如策劃提出區域地圖需要描邊效果,那麼美術在切圖的時候,再復制一份圖層,變大一點點,放在原圖的下面,並將透明度調到大於零的最低值,這個值和之後要改的shader中的透明度值相關,如下圖:
步驟2:
導入Unity3d中,步驟就不說明了,請參考NGUI的圖集製作過程。在Unity中的效果如下:
步驟3:
修改shader【Unlit/Transparent Colored】,找到frag方法,在col = tex2D(_MainTex, i.texcoord) * i.color;下加上關於透明度值的判斷。
如果透明度小於1並且大於0.1,那麼將此點顏色變為黃色。
col = tex2D(_MainTex, i.texcoord) * i.color;
if(col.a < 1.0 && col.a > 0.1)
{
col.rgba = float4(1.0,1.0,0.0,1.0);
}
效果出來了,最終效果由美術和shader決定,這種方法實現起來是不是很簡單呢?O(∩_∩)O
如果要實現滑鼠懸停才出現描邊效果,那麼我們就可以犧牲一種顏色值,這樣才可以加入邏輯判斷的代碼。
在這里犧牲g色的0.6值, 在邏輯代碼中,設置當前sprite的顏色為Color(1f, 0.6f, 1f),那麼就會出現描邊的效果了。
if(i.color.g > 0.599 && i.color.g < 0.601)
{
col = tex2D(_MainTex, i.texcoord) * i.color;
if(col.a < 1.0 && col.a > 0.1)
{
col.rgba = float4(1.0,1.0,0.0,1.0);
}
}
D. Shader筆記4-描邊
描邊原理:在片段著色器裡面,對於每個像素:1. 如果它是不透明的像素,則不管,維持原本顏色;2. 如果透明,是360度判斷它四周有沒有不透明的像素,如果有,則把它設成描邊顏色,否則保持透明。
頂點著色器 Outline.vert
片段著色器 Outline.frag
先判斷當前像素是否透明,如果不透明則直接返回。如果是透明像素,就判斷這個點周圍12個方向,每個方向距離當前像素距離是outlineSize的像素點是否透明,只要有一個是非透明像素,就把當前像素點設為描邊的顏色,並設置成非透明。
使用示例
效果
E. unity shadergraph怎麼實現3d描邊效果
我對這個也感興趣,爬了下論壇,貌似目前shadergraph不支持關閉Z Test,所以無法實現這個效果;
但是據說已經在開發人員todo list上了;
上面的論壇大佬給出了當前唯一的解決方案,拷貝shadergraph主節點生成的代碼,然後復制到自己新建的shader來加入關閉Z Test的標簽。
F. 通過Shader實現的uGUI文本及Image描邊和高光等效果
鏈接: http://pan..com/s/1c1eqPnq 密碼: f4ut
G. 如何在cocos creater如何做出字體描邊效果
目前的引擎有兩種做法: 第一種就是寫shader,來實現,找到邊緣像素點,進行修改, 第二種就是用利用引擎RenderTexture來做,都可以實現的。
具體的就要看自己了,3.1 版本的 cpp tests 有 shader sprite 的描邊例子。