【Shader入土】Stencil模板测试

目录

一、介绍模板测试

① Ref referenceValue     

② ReadMask readMask 

③ WriteMask writeMask

④ Comp comparisonFunction

⑤ Pass stencilOperation 

⑥ Fail stencilOperation 

⑦ ZFail stencilOperation

二、实战测试

2.1 官方实战

2.2 关于ReadMask和WriteMask的测试


2022年8月27日更新 

        模板测试在深度测试之后进行,所以如果想做一个物体背后的物体能够显示在前面,那必须让背后的物体能够通过深度测试,即ZTest Always,但又不是整个物体显示出来,只显示出被遮挡部分,就需用到模板测试,用遮挡物写入模板,被遮挡物进行模板测试,还得注意先后顺序,必须是遮挡物先进行渲染,然后再进行渲染被遮挡物。

一、介绍模板测试

1、模板测试是在片元着色器之后进行的,同时也在深度测试之后进行,它的目的是过滤片元,当参考值和模板缓冲区数值比较后达成条件的才会显示,当然也可以不进行模板测试。

2、在Pass块内定义,以Stencil{}来实现一个Pass它对应的模板测试操作

3、Stencil内有如下指令:

① Ref referenceValue     

Ref指令 + referenceValue参考值(0~255整数值)
当Comp指令指明为非Always时,以此referenceValue整数值与模板缓冲区的数值进行比较,比较方式根据Comp指令指明的条件来进行,当referenceValue数值满足条件时,则算作通过模板测试;当通过模板测试后,且Stencil的Pass指令指明为Replace时则用此值替换掉缓冲区的数值。

② ReadMask readMask 

ReadMask指令 + readMask读(Ref指令指定的数值时的 和 当前模板缓冲区)掩码【需测试】
readMask(0~255整数值)即8bit

③ WriteMask writeMask

写入(Ref指令指定的数值时的 和 当前模板缓冲区)掩码 【需测试验证】
writeMask (0~255整数值) 即8bit

④ Comp comparisonFunction

Comp指令 + comparisonFunction比较函数(如:Always【直接允许通过模板测试】)
Comp是指明模板测试是如何测试的,有如下比较函数:

GreaterOnly render pixels whose reference value is greater than the value in the buffer.
GEqualOnly render pixels whose reference value is greater than or equal to the value in the buffer.
LessOnly render pixels whose reference value is less than the value in the buffer.
LEqualOnly render pixels whose reference value is less than or equal to the value in the buffer.
EqualOnly render pixels whose reference value equals the value in the buffer.
NotEqualOnly render pixels whose reference value differs from the value in the buffer.
AlwaysMake the stencil test always pass.
NeverMake the stencil test always fail.

下面才是按顺序解释,Comp的0-8数字代表意义。

Disabled禁用深度或模板测试。
Never从不通过深度或模板测试。
Less当新值小于旧值时通过深度或模板测试。
Equal当值相等时通过深度或模板测试。
LessEqual当新值小于或等于旧值时通过深度或模板测试。
Greater当新值大于旧值时通过深度或模板测试。
NotEqual当值不同时通过深度或模板测试。
GreaterEqual当新值大于或等于旧值时通过深度或模板测试。
Always始终通过深度或模板测试。

⑤ Pass stencilOperation 

Pass指令 + stencilOperation回调函数(操作)
Pass指令是指明模板测试通过后应执行什么样的操作的,有如下stencilOperation操作

KeepKeep the current contents of the buffer.
ZeroWrite 0 into the buffer.
ReplaceWrite the reference value into the buffer.
IncrSatIncrement the current value in the buffer. If the value is 255 already, it stays at 255.
DecrSatDecrement the current value in the buffer. If the value is 0 already, it stays at 0.
InvertNegate all the bits.
IncrWrapIncrement the current value in the buffer. If the value is 255 already, it becomes 0.
DecrWrapDecrement the current value in the buffer. If the value is 0 already, it becomes 255.

⑥ Fail stencilOperation 

指明模板测试失败后的操作

⑦ ZFail stencilOperation

指明深度测试失败后的操作

二、实战测试

2.1 官方实战

1、制作一个最简单的顶点着色器/片元着色器的Shader,仅进行写入模板值(即2),然后输出一个红色值。

Shader "Unlit/StencilTestRed"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		LOD 100

		Pass
		{
			Stencil
			{
				//Ref参考值: 当Comp非Always时会用于比较(虽然还不清楚是和什么比较,可能是和模板缓冲区的值进行比较)
				//若Pass、Fail、ZFail为Replace时,该值用于写入模板缓冲区,[0,255]
				Ref 2  
				Comp Always  //总是通过模板测试
				Pass Replace //通过模板测试后进行替换操作(将Ref的值写入模板缓冲区)
			}
			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;
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(1, 0, 0, 1);
			}
			ENDCG
		}
	}
}

2、制作一个最简单的Shader,模板测试比较函数为Equal(等于),Ref参考值为2,Pass通过模板测试后Keep(保持原值,即不进行任何操作),ZFail深度测试失败后对模板缓冲区的数值减1(若是0则会减为255),输出颜色值为绿色。

Shader "Unlit/StencilTestGreen"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
		LOD 100

		Pass
		{
			Stencil
			{				
				Ref 2
				Comp equal
				Pass keep 
				ZFail DecrWrap
			}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
	
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;				
			};

			struct v2f
			{				
				float4 vertex : SV_POSITION;
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(0, 1, 0, 1);
			}
			ENDCG
		}
	}
}

3、制作一个最简单的Shader,仅进行了模板测试Comp等于操作,Ref值为1,输出颜色值为蓝色

Shader "Unlit/StencilTestBlue"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}
		LOD 100

		Pass
		{
			Stencil
			{
				Ref 1
				Comp equal			
			}
			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;
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(0, 0, 1, 1);
			}
			ENDCG
		}
	}
}

*注意:上方三个Shader对应红、绿、蓝球,并且Queue渲染队列是先红、然后绿、最后蓝进行渲染的,不然可能会有问题。

上面三张图分别是红球、绿球、篮球的渲染过程,红球渲染时将红球所在的模板缓冲区的数值改为2,绿球渲染时会用2与缓冲区的数值进行对比,结果只有与红球相交的部分通过模板测试(即2==2),所以只有那部分渲染成了绿色,而其他部分无法通过测试抛弃,并且绿球渲染时,当深度测试不通过时,会将那深度测试不通过的部分的模板缓值减1(2-1=1),注意!深度测试不通过的部分,此时是只有红球和绿球渲染时的深度测试不通过部分,而非三个物体都渲染出来后的深度测试不通过部分,这一点要好好地理解!所以最后蓝色球渲染时,会将蓝色球所在区域模板值为1的渲染出来,其他的抛弃。

各位请看上图,右图是我画出来的深紫色区域,这个区域在红球已渲染,绿球渲染时,蓝球还未渲染的情况下,这个区域不是绿球模板测试时的深度测试不通过区域。但当三个球体都被渲染出来后,这个区域才是深度测试不通过区域哟。

2.2 关于ReadMask和WriteMask的测试

1、制作一个Shader,模板参考值为9,总是通过模板测试,Pass后替换模板缓冲区的值,写入掩码为1,所以经过掩码后写入的是1

Shader "Unlit/StencilReadMaskTestShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry" }
		LOD 100

		Pass
		{
			Stencil
			{
				Ref 9
				WriteMask 1
				Comp Always
				Pass Replace				
			}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag			
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
			};

			struct v2f
			{	
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);		
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{			
				return fixed4(1,0,0,1);
			}
			ENDCG
		}
	}
}

2、制作一个Shader,模板参考值为5,模板比较操作是等于Equal,读入掩码为3,所以经过读取掩码后的模板参考值为1。

Shader "Unlit/StencilReadMaskTestShaderReadMask"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry+1" }
		LOD 100

		Pass
		{
			Stencil
			{
				Ref 5
				ReadMask 3
				Comp Equal
			}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag			
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
			};

			struct v2f
			{	
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);		
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{			
				return fixed4(0,0,1,1);
			}
			ENDCG
		}
	}
}

所以读取掩码和写入掩码都是针对模板参考值Ref而言的,并不是针对模板缓冲区的数值。我当初是以为2个会针对缓冲区的值。

详细解释:
红球渲染时写入参考值9时,使用写入掩码1对9进行掩码操作,即&操作,1001&0001=0001,所以写入的是1;
蓝球渲染时在进行模板测试等于操作时,读取模板参考值5,使用读取掩码3进行掩码操作,即&操作,101&011=001,所以读取到的是1,最终篮球区域上模板缓存值为1的区域被渲染出来。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值