目錄
一、介紹模板測試
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的區域被渲染出來。