【Shader入土】Stencil模板測試

目錄

一、介紹模板測試

① Ref referenceValue     

② ReadMask readMask 

③ WriteMask writeMask

④ Comp comparisonFunction

⑤ Pass stencilOperation 

⑥ Fail stencilOperation 

⑦ ZFail stencilOperation

二、實戰測試

2.1 官方實戰

2.2 關於ReadMask和WriteMask的測試


一、介紹模板測試

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是指明模板測試是如何測試的,有如下比較函數:

   
Greater Only render pixels whose reference value is greater than the value in the buffer.
GEqual Only render pixels whose reference value is greater than or equal to the value in the buffer.
Less Only render pixels whose reference value is less than the value in the buffer.
LEqual Only render pixels whose reference value is less than or equal to the value in the buffer.
Equal Only render pixels whose reference value equals the value in the buffer.
NotEqual Only render pixels whose reference value differs from the value in the buffer.
Always Make the stencil test always pass.
Never Make the stencil test always fail.

⑤ Pass stencilOperation 

Pass指令 + stencilOperation回調函數(操作)
Pass指令是指明模板測試通過後應執行什麼樣的操作的,有如下stencilOperation操作

   
Keep Keep the current contents of the buffer.
Zero Write 0 into the buffer.
Replace Write the reference value into the buffer.
IncrSat Increment the current value in the buffer. If the value is 255 already, it stays at 255.
DecrSat Decrement the current value in the buffer. If the value is 0 already, it stays at 0.
Invert Negate all the bits.
IncrWrap Increment the current value in the buffer. If the value is 255 already, it becomes 0.
DecrWrap Decrement 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的區域被渲染出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章