Unity ShaderLab 模板緩存(Stencil Buffer) 基本概念

1. 概念:

什麼是模板緩存?

在渲染平面上,每個像素都存有一個模板值(0-255,一個位元組)。打個比方,屏幕好比一塊活字印刷版,像素好比字,模板值好比是啥字。這樣我們就可以通過改變模板的值,來渲染制定區域的像素。比如下圖中,我們設定中間區域的模板值為1,然後只渲染模板為1的區域。

下圖中,「模板測試」指模板緩存值(stencil buffer)與你自定義的參考值(Ref)的比較結果。在傳統PC渲染流水線(手機平台可能略有不同)中,模板測試是在深度測試(ZTest)之前的,沒有通過模板測試的像素就會被捨棄不渲染,甚至直接跳過ZTest。

2. 語法:

要加模板測試,就在Shader的Pass開頭寫Stencil{ }結構體。如果每個Pass都用,則可以提到外面。

還是舉個栗子吧:

Ref 2:設置參考值。除了2,還可以設置0-255的任意數。

Comp equal:表示通過模板測試的條件。這裡只有等於2的像素才算通過測試。除了equal,還有Greater、Less、Always、Never等,類似ZTest。

Pass keep:表示通過模板測試和Z測試(注意是都通過)的像素,怎麼處置它的模板值,這裡我們我們保留它的模板值。除了keep,還有Replace,IncrWrap(循環自增1,超過255為0),IncrSat(自增1,超過255還是255),DecrWrap,DecrSat等。

Fail decrWrap:表示沒通過模板測試的像素, 怎麼處置它的模板值。這裡為循環自減。

ZFail keep:表示通過了模板測試但沒通過Z測試的像素,怎麼處置它的模板值。這裡為保持不變。

除此之外,還有ReadMask和WriteMask語法,用來提取Ref值或模板緩存的某幾位,默認是255,全部提取。

好了,語法就這麼多,是不是很精簡!不信請查閱Unity官方文檔,搜Stencil即可。

3. 應用例子:

畫紅綠藍三個紅球,目標是在紅綠交界且被平面擋住的部分畫出藍球。官網效果如下:

藍色部分貌似在平面下面,其實是在平面上面的,是不是很神奇!請看分解:

第一步畫紅球:

Shader "Red" { n SubShader { n Tags { "RenderType"="Opaque" "Queue"="Geometry"}n Pass { n Stencil { n Ref 2 //參考值為2,stencilBuffer值默認為0 n Comp always //stencil比較方式是永遠通過 n Pass replace //pass的處理是替換,就是拿2替換buffer 的值 n ZFail decrWrap //ZFail的處理是溢出型減1 n } n // stencil和Zbuffer都通過的話就執行。把點渲染成紅色。 n CGPROGRAM n #pragma vertex vert n #pragma fragment frag n struct appdata { n float4 vertex : POSITION; n }; n struct v2f { n float4 pos : SV_POSITION; n }; n v2f vert(appdata v) { n v2f o; n o.pos = mul(UNITY_MATRIX_MVP, v.vertex); n return o; n } n half4 frag(v2f i) : SV_Target { n return half4(1,0,0,1); n } n ENDCG n } n }n} n

平面以上的點,通過了模板測試和深度測試,則模板值。在平面下面的半球,通過stencil測試但沒通過深度測試,stencil值減一為255。

第二步畫綠球:

Shader "Green" { n SubShader { n Tags { "RenderType"="Opaque" "Queue"="Geometry+1"} //渲染次序為Geometry+1,在紅球之後 n Pass { n Stencil { n Ref 2 //參考值為2 n Comp equal //stencil比較方式是相同,只有等於2的才能通過 n Pass keep //stencil和Zbuffer都測試通過時,選擇保持 n Fail decrWrap //stencil沒通過,選擇溢出型減1,所以被平面擋住的那層stencil值就變成254 n ZFail keep //stencil通過,深度測試沒通過時,選擇保持 n } nn CGPROGRAM n #pragma vertex vert n #pragma fragment frag n struct appdata { n float4 vertex : POSITION; n }; n struct v2f { n float4 pos : SV_POSITION; n }; n v2f vert(appdata v) { n v2f o; n o.pos = mul(UNITY_MATRIX_MVP, v.vertex); n return o; n } n half4 frag(v2f i) : SV_Target { n return half4(0,1,0,1); n } n ENDCG n } n }n} n

還記得紅球的上半部是2嗎?綠球與這部分相交的部分就通過了模板測試。不管怎麼移動綠球,只渲染與紅球上半部分相交的區域。

另外注意,紅球下半區域與綠球的交界處的模板值已經變成了254,成為接下來藍球的繪製區域。

第三步畫藍球:

Shader "Blue" { n SubShader { n Tags { "RenderType"="Opaque" "Queue"="Geometry+2"} //渲染次序為Geometry+2,在前面兩個shader之後 n Pass { n Stencil { n Ref 254 //參考值為254 n Comp equal //比較方式是是否相等,即只會渲染n } nn CGPROGRAM n #pragma vertex vert n #pragma fragment frag n struct appdata { n float4 vertex : POSITION; n }; n struct v2f { n float4 pos : SV_POSITION; n }; n v2f vert(appdata v) { n v2f o; n o.pos = mul(UNITY_MATRIX_MVP, v.vertex); n return o; n } n half4 frag(v2f i) : SV_Target { n return half4(0,0,1,1); n } n ENDCG n } n }n} n

藍球限定了只有模板值為254的區域能通過測試,因此藍球只渲染254且也通過了ZTest的部分。也就說圖中的藍色區域是在白色平面上面的。下圖為藍球從上往下移的過程。

4. 總結:

模板測試的語法參考UNITY官網手冊,但例子解釋得很晦澀。還可以參考網上的例子。不管怎樣,強烈建議自己把這個幾個shader拷進去試一試!!!文章看著會暈,自己拷代碼試一下就很容易明白了。

5. 參考文獻:

Unity官方手冊搜「stencil」

blog.csdn.net/u01383339(第二個例子比上面說的更簡單)

《Unity Shader入門精要》p15


推薦閱讀:

unity移動開發如何依據性能選擇shader?
Instagram濾鏡,影視級調色演算法實現

TAG:Unity游戏引擎 | shader |