关于unity shader的StencilBuffer
这几天学了unity shader,不得不说unity shader小小的选择项竟然蕴含了这么多东西,最近看到shaderlab 的stencil buffer真是搞得一头雾水,网上也没有对stencil在untiy 的介绍,有也是很简略的,圣典也还没翻译,就官网那几个字,真不知道写那个的人是怎么想的,就写那几个字怎么让人明白那怎么回事。好了,吐槽这么多,下面来说一下自己对stencil的认识,官网链接如下http://docs.unity3d.com/Manual/SL-Stencil.html,重点介绍那几个例子。
语法介绍
stencil buffer,也是一个buffer(废话),长度是8位,主要用于筛选pixel用,stencil buffer其实是zBuffer其中的一部分,stencil的测试与深度测试也是紧密连接,因为还要用到深度测试的结果。
语法
Stencil {
Ref 2
Comp equal
Pass keep
Fail decrWrap
ZFail keep
}
下面一条一条来:
Ref referenceValue,这个是设定参考值,stencilbuffer里面的值会与他比较
ReadMask readMask,这个是在比较参考值和buffer值的时候用的,用于读取buffer值里面的值。
WriteMask writeMask,这个是写入buffer值用的。
Comp comparisonFunction,这个比较重要,这个是比较方式。大致有Greater,GEqual,Equal等八种比较方式,具体待会列图。
Pass stencilOperation,这个是当stencil测试和深度测试都通过的时候,进行的stencilOperation操作方法。注意是都通过的时候!
Fail stencilOperation,这个是在stencil测试通过的时候执行的stencilOperation方法。这里只要stencil测试通过就可以了
ZFail stencilOperation,这个是在stencil测试通过,但是深度测试没有通过的时候执行的stencilOperation方法。
一般Comp,Pass,Fail,ZFail只用于正面的渲染,除非有Cull front,这样的语句出现。如果要渲染两面,可以用CompFront,PassFront等和CompBack,PassBack等。意思和上面的一样
比较方式:
Greater 大于
GEqual 大于等于
Less 小于
LEqual 小于等于
Equal 等于
NotEqual 不等于
Always 永远通过
Never 永远通不过
这个是在接在Comp之后的,结果可以影响Fail 的执行。
stencilOperation(stencil操作)
Keep 保持
Zero 归零
Replace 拿比较的参考值替代原来buffer的值
IncrSat 值增加1,但是不溢出,如果是255,就不再加
DecrSat 值减少1,不溢出,到0就不再减
Invert 翻转所有的位,所以1会变成254
IncrWrap 值增加1,会溢出,所以255会变成0
DecrWrap 值减少1,会溢出,所以0会变成255
至于官网的延迟光照那段大致意思就是Deffer render里面stencil Function不好用就是了。
下面上代码了,在我看来,官网的这两个例子非常难,也没有什么解释所以很不好理解。
Shader "Red" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}//这里渲染的类型为不透明物体,次序是Geometry。至于Geometry是多少我就不清楚的,求大神科普
Pass {
Stencil {
Ref 2 //参考值为2,stencilBuffer值默认为0
Comp always //stencil比较方式是永远通过
Pass replace //pass的处理是替换,就是拿2替换buffer 的值
ZFail decrWrap<span style="white-space:pre"> </span>//ZFail的处理是溢出型减1
}
<span style="white-space:pre"> </span>//下面这段就不多说了,主要是stencil和Zbuffer都通过的话就执行。把点渲染成红色。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,0,0,1);
}
ENDCG
}
}
}
结果就像这样,至于为什么要用平面来切,待会解释。好,现在在平面以上的点,stencilbuffer值全为2,因为都被replace了。在平面下面的点,通过了stencil测试但是没有通过深度测试,stencil值减一全为255。
下面是第二段。
Shader "Green" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}<span style="white-space:pre"> </span>//渲染次序为Geometry+1,在红球之后
Pass {
Stencil {
Ref 2<span style="white-space:pre"> </span>//参考值为2
Comp equal<span style="white-space:pre"> </span>//stencil比较方式是相同,这回不是都通过了
Pass keep <span style="white-space:pre"> </span>//stencil和Zbuffer都测试通过时,选择保持
Fail decrWrap <span style="white-space:pre"> </span>//stencil没通过,选择溢出型减1,所以被平面挡住的那层stencil值就变成254
ZFail keep<span style="white-space:pre"> </span>//<span style="font-family: Arial, Helvetica, sans-serif;">stencil通过,深度测试没通过时,选择保持</span>
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(0,1,0,1);
}
ENDCG
}
}
}
那个网格线就是附着了第二个shader的球体。神奇的地方来了,这个球体本身是没有颜色的,因为stencil为0的话不可能等于2的。在红球和这个球交汇处变成了绿色,注意,这个绿色不是红球的,而是“绿球的”。有一个隐藏的部分是底下被遮住部分,如果“绿球”有挡住红球部分,则stencil会变为254。这个对于下面这个shader非常重要。
Shader "Blue" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}<span style="white-space:pre"> </span>//渲染次序为Geometry+2,在前面两个shader之后
Pass {
Stencil {
Ref 254<span style="white-space:pre"> </span>//参考值为254
Comp equal<span style="white-space:pre"> </span>//比较方式是是否相等
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(0,0,1,1);
}
ENDCG
}
}
}
好,底下那个部分很神奇吧!先解释一下,底下那个蓝色部分,是红球通过“绿球部分”再转到“蓝球”部分的,红球深度测试失败,stencil减1,再通过“绿球”stencil测试失败,stencil再减1,到这个蓝色上就符合了同样是这个蓝色块是在“蓝球”表面的。但是有个问题是红球的内表面也映到了了蓝球的上面,这个我不清楚,请高手解答
下面介绍另一组官网的shader
Shader "HolePrepare" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}<span style="white-space:pre"> </span>//渲染次序为<span style="font-family: Arial, Helvetica, sans-serif;">Geometry+1</span>
ColorMask 0<span style="white-space:pre"> </span>//开始玩花样了,这个球不渲染任何的颜色
ZWrite off<span style="white-space:pre"> </span>//关闭深度写,代表这个球是透明的
Stencil {
Ref 1<span style="white-space:pre"> </span>//参考值是1
Comp always<span style="white-space:pre"> </span>//比较方式是永远通过
Pass replace<span style="white-space:pre"> </span>//Pass选择stencil操作是替代
}
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span>//下面是两个通道。第一个通道渲染背面(我觉得说内侧更合适),第二个通道渲染正面,但是这个比较难理解的是在深度测试成功时你看到了背面,失败时看到了正面,这就相当于,你看一个不透明的杯子,你直接看到了内表面,你用手遮住这个杯子再看,你就看到了外表面。</span>
<span style="white-space: pre;"> </span>CGINCLUDE
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,1,0,1);
}
ENDCG
Pass {
Cull Front
ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Pass {
Cull Back
ZTest Greater
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
不过因为Colormask不渲染任何颜色,所以上面这个shader看不出来什么东西。下面添加一段对上面代码的改写。
Shader "HolePrepare" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
//ColorMask 0<span style="white-space:pre"> </span>注释掉ColorMask,让他可以显示颜色
ZWrite off
Stencil {
Ref 1
Comp always
Pass replace
}
CGINCLUDE
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,1,0,1);
}
half4 frag2(v2f i) : SV_Target {<span style="white-space:pre"> </span>//添加一段片段函数以供调用
return half4(1,0,0,1);
}
ENDCG
Pass {
Cull Front
ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Pass {
Cull Back
ZTest Greater
CGPROGRAM
#pragma vertex vert
#pragma fragment frag2<span style="white-space:pre"> </span>//渲染正面时,调用frag2函数
ENDCG
}
}
}
如上图,主要的改动的是三个方面,效果如下
嗯,所以可以直接被看见部分(内侧)渲染黄色,被挡住部分(外侧)渲染红色,然后中间的部分,挡住了内侧,暴露了外侧,所以不渲染,为什么要对这个shader解释这么多呢,原因就在下面这个shader。
Shader "Hole" {
Properties {
_Color ("Main Color", Color) = (1,1,1,0)
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}<span style="white-space:pre"> </span>//渲染次序为Geometry+2
ColorMask RGB<span style="white-space:pre"> </span>//显示出颜色,这个默认的,写不写都一样
Cull Front<span style="white-space:pre"> </span>//只渲染背面,嗯,这个shader开始牛起来了
ZTest Always<span style="white-space:pre"> </span>//深度测试永远通过,霸气侧漏!这个意味着不管你怎么挡,这个球始终可以在你眼皮子底下出现
Stencil {
Ref 1<span style="white-space:pre"> </span>//参考值为1
Comp notequal <span style="white-space:pre"> </span>//比较方式为不相等<span style="white-space:pre"> </span>
}
CGPROGRAM
#pragma surface surf Lambert
float4 _Color;
struct Input {
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = _Color.rgb;
o.Normal = half3(0,0,-1);
o.Alpha = 1;
}
ENDCG
}
}
这个球的样子是这样
没人挡得住它的显示了
这是他被平面切割的情况,根本看不出来啊!好,这个球说,“谁能挡我!!”
然后他就被挡了
好,解释一下,这个透明球先渲染,所以他上面的内表面和下面的外表秒stencil值为1,或许有人会问上面内表面可以赋值是可以理解的,下面外表面的怎么回事?我也想了好久,其实答案就在每个通道的ztest,内表面的为ztest Less,说明这个内表面的只要在物体前面就可以渲染出来,即深度测试成功,stencil赋值为1,而下面的外表面为ztest Greater,说明这个外表面需要在被挡住的时候显示出来,很贱有没有,这个时候深度测试成功。中间那部分因为没有深度测试成功,所以shencil值还是为0,所以还是被这个霸气侧漏的透了出来!
好了,先写到这里,我要去吃饭了,有发现错误的地方求大神指正。
---------------------
作者:u013833399
来源:CSDN
原文:https://blog.csdn.net/u013833399/article/details/47340447
版权声明:本文为博主原创文章,转载请附上博文链接!