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」
http://blog.csdn.net/u013833399/article/details/47340447(第二個例子比上面說的更簡單)
《Unity Shader入門精要》p15
推薦閱讀: