Unity Shader关于Stencil的理解小记
Posted alicelise
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity Shader关于Stencil的理解小记相关的知识,希望对你有一定的参考价值。
写在前面:本文只介绍Stencil最基本的三种参数:Ref Comp Pass ,其他的略略带过。
碎碎念:之前用过Stencil,但是没有完全理解,原因在于虽然理解了每项的意思,但是实际的使用效果不如人意,最后得出的结果纯靠运气试出来的。昨晚按照渲染流程走了一遍,豁然开朗,感觉自己好瓜皮qaq
Stencil简介
Stencil是模板测试的意思,通常写在Pass里面最开头,CGPROGRAM之前。Stencil会在该场景生成一个Stencil缓冲区,缓冲值初始为0。在运行中,Stencil会通过读取每个像素的缓冲值,与Ref定义的参考值进行比较,若通过则对该像素缓冲值进行Pass后的操作,并渲染该像素,若不通过则进行Fail后的操作。
stencil完整语法格式如下:
stencil{
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
}
但一般来说,我只取Ref Comp Pass
三项
Ref
Ref用来设定参考值,这个值将用来与模板缓冲中的值进行比较。
例:Ref 0
意为“设定参考值0”。
Comp
comp等于英语compare,意思是将参考值与缓冲值相比。
例:Ref 0 Comp Less
意为“若参考值0小于缓冲值”,千万不要弄反了。
Pass
Pass意思是通过了就执行xx操作。
例:Pass keep
意思是若通过了就不改变缓冲区的值,并渲染该像素。
Comp和Pass的可取值类型如上
举例
现在正式进入我们的探索阶段,虽然上面讲得很清楚,但是实践下来可能会发现结果和自己理解的不一样(比如我)。那么现在我们就创建一个平面投影shader,用这个来测试Stencil。
Shader "Custom/PlanarShadow"
Properties
_Tint("_Tint", Color) = (1,1,1,1)
_MainTex("_MainTex (albedo)", 2D) = "white"
[Header(Alpha)]
[Toggle(_CLIPPING)] _Clipping ("Alpha Clipping", Float) = 1
_Cutoff("_Cutoff (Alpha Cutoff)", Range(0.0, 1.0)) = 0.5
[Header(Shadow)]
_GroundHeight("_GroundHeight", Float) = 0
_ShadowColor("_ShadowColor", Color) = (0,0,0,1)
_ShadowFalloff("_ShadowFalloff", Range(0,1)) = 0.05
[HideInInspector] _SrcBlend("__src", Float) = 1.0
[HideInInspector] _DstBlend("__dst", Float) = 0.0
[HideInInspector] _ZWrite("__zw", Float) = 1.0
[HideInInspector] _Cull("__cull", Float) = 2.0
SubShader
Pass
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct a2v
float4 vertex : POSITION;
float3 normal : NORMAL;
;
struct v2f
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
;
v2f vert(a2v v)
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
fixed4 frag(v2f i) : SV_Target
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse , 1.0);
ENDCG
Pass
Name "PlanarShadow"
Stencil
Ref 0
Comp Gequal
Pass DecrWrap
Cull Off
Blend SrcAlpha OneMinusSrcAlpha
ZWrite off
Offset -1 , 0
CGPROGRAM
#pragma shader_feature _CLIPPING
#pragma shader_feature _ALPHATEST_ON
#pragma shader_feature _ALPHAPREMULTIPLY_ON
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
float _GroundHeight;
float4 _ShadowColor;
float _ShadowFalloff;
half4 _Tint;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Clipping;
half _Cutoff;
struct appdata
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
;
struct v2f
float4 vertex : SV_POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD0;
;
float3 ShadowProjectPos(float4 vertPos)
float3 shadowPos;
float3 worldPos = mul(unity_ObjectToWorld , vertPos).xyz;
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
shadowPos.y = min(worldPos .y , _GroundHeight);
shadowPos.xz = worldPos .xz - lightDir.xz * max(0 , worldPos .y - _GroundHeight) / lightDir.y;
return shadowPos;
float GetAlpha (v2f i)
float alpha = _Tint.a * tex2D(_MainTex, i.uv.xy).a;
return alpha;
v2f vert (appdata v)
v2f o;
float3 shadowPos = ShadowProjectPos(v.vertex);
o.vertex = UnityWorldToClipPos(shadowPos);
float3 center = float3(unity_ObjectToWorld[0].w , _GroundHeight , unity_ObjectToWorld[2].w);
float falloff = 1-saturate(distance(shadowPos , center) * _ShadowFalloff);
o.color = _ShadowColor;
o.color.a *= falloff;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
fixed4 frag (v2f i) : SV_Target
if (_Clipping)
float alpha = GetAlpha(i);
i.color.a *= step(_Cutoff, alpha);
return i.color;
ENDCG
在场景中加入多个物体,为它们挂载上Shader,瞎摆一通使它们的阴影重叠。使用不同的Stencil设置,会出现不同的阴影重叠效果,从上到下分别为:无阴影、无重叠阴影、重叠阴影。
我用Ref 0
试了所有的Comp
和Pass
参数,结果如下:
当然,Comp
没有包括Always
和never
,因为它俩的情况比较简单,Always
就是“无论如何都渲染”,never
就是“无论如何都不渲染”。
我们简单分析一下上面的表格,在分析之前我们要明确三点:
- 需要把渲染顺序考虑进去
- 缓冲值实际上是8位二进制数,范围在0-255之间
IncrWrap\\DecrWrap
中,0-1=255,255+1=0IncrSat\\DecrSat
中,0-1=0,255+1=255
首先,如果Comp
后面的值是NotEqual/Greater/Less
,那么都会得到无阴影的结果。可是阴影明明有重叠的范围,如果写了Pass IncrWrap
,为什么在选择Less
的时候不会显示重叠区?那么我们过一遍测试流程,首先第一个物体进行模板测试,它的所有缓冲值都是0,那么比较的结果就是全部不通过,缓冲区没有任何更改,所以第二个物体进入测试的时候,和第一个物体阴影重叠部分的缓冲值仍然为0,这样比较下来依然是全部不通过,层层递推下来就完全没有通过的,最后的结果就是全部不显示。
然后,经过刚刚的推导,表上大部分的情况都可以顺下来了。还有一个问题是,为什么IncrWrap
和DecrWrap
的结果相同呢?这就是因为Wrap的特性,0-1=255,255+1=0。设Ref 0 Comp Lequal
,当Pass DecrWrap
时,第一个全部渲染后Stencil缓冲值-1,该阴影所在区域,其像素的Stencil缓冲值均等于255,因此第二个进入测试时,非重叠部分缓冲值为0,重叠部分缓冲值为255,全部比较通过,渲染并将缓冲值减1;第三个进入测试时,有两次重叠的部分缓冲值为254,一次重叠部分缓冲值为255,无重叠部分缓冲值为0,它的测试也是全部通过的;当Pass IncrWrap
时,第一个全部渲染后Stencil缓冲值+1,那么第二个测试时,与第一个阴影重叠的部分全部比较通过,全部渲染,接下来的也是如此。
最后一个问题,为什么DecrWrap
和DecrSat
的结果不同呢?我们先考虑DecrSat
,它在测试通过的时候,缓冲值-1,但前面说到,IncrSat\\DecrSat
中,0-1=0,所以缓冲值依然为0,那么不通过的时候,如果不写Fail,则默认为keep,所以还是缓冲值还是0,如此算下来,DecrSat
=zero
。DecrWrap
前面已经推导过,这里不再赘述。
写在后面:还是写了蛮多的,都是因为我的蠢问题> <估计没有人会像我这么傻哈哈哈
Unity3D UGUI Shader画一个圆环
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
_StencilComp("Stencil Comparison", Float) = 8
_Stencil("Stencil ID", Float) = 0
_StencilOp("Stencil Operation", Float) = 0
_StencilWriteMask("Stencil Write Mask", Float) = 255
_StencilReadMask("Stencil Read Mask", Float) = 255
_ColorMask("Color Mask", Float) = 15
_Width("_Width", Float) = 0
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
}
Cull Off
Lighting Off
Fog{ Mode Off }
Blend SrcAlpha OneMinusSrcAlpha
Stencil
{
Ref[_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask[_StencilReadMask]
WriteMask[_StencilWriteMask]
}
ColorMask[_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Width;
float4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float d = distance(i.uv,float2(0.5,0.5));
if (d < 0.5- 2/ _Width)
{
discard;
}
if (d > 0.5)
{
discard;
}
fixed4 col = tex2D(_MainTex, i.uv);
return col*_Color;
}
ENDCG
}
}
}
以上是关于Unity Shader关于Stencil的理解小记的主要内容,如果未能解决你的问题,请参考以下文章
Unity开发:Material xxx doesn‘t have _Stencil property问题记录
Unity影响渲染顺序因素的总结
UnityShader之Shader格式篇Shader资料1
关于Unity中Shader的使用
关于Unity中Shader的使用
Unity3D Shader编程之十一 深入理解Unity5中的Standard Shader&屏幕像素化特效的实现