원문
https://docs.unity3d.com/Manual/SL-Stencil.html
우리말로 보려고 언어를 한국어로 바꾸고 봤더니 ‘구문’하고 ‘예'(example) 이거 두 개만 번역되어 있어서(놀리는 건가…) 직접 해서 적어둡니다. 오역이 있을 수 있습니다. T_T
셰이더랩: 스텐실
스텐실 버퍼는 픽셀을 보존하거나 버리는 픽셀당 마스크를 일반적인 용도로써 사용할 수 있습니다.
스텐실 버퍼는 보통, 픽셀당 8비트 정수입니다. 값은 쓰거나 늘리거나 줄일 수 있습니다. 다음번의 draw call은 픽셀 셰이더가 진행되기 전에 픽셀을 버려야 할지 결정하기 위해 값을 점검할 수 있습니다.
구문
Ref
Ref 참조할 값
(Comp가 always가 아니고 다른 것일 때) 비교하는 값이며/또는 (Pass, Fail이나 ZFail이 replace로 설정된 경우) 버퍼에 써넣을 값. 0-255 정수.
ReadMask
ReadMask 읽기 마스크 값
0-255 정수 8비트 마스크, 버퍼 안의 값을 참조 값과 비교 할 때 사용합니다. (참조 값 & 읽기 마스크 값) 비교함수 (스텐실 버퍼 값 & 읽기 마스크 값). 기본 255.
WriteMask
WriteMask 쓰기 마스크 값
0-255 정수 8비트 마스크, 버퍼에 값을 써넣을 때 사용합니다. 다른 쓰기 마스크와 마찬가지로, 쓰기로 인해 스텐실 버퍼의 어느 비트가 영향을 받을지 지정하는 데 사용 한다는 점을 유의 하세요. (예를 들어 WriteMask 0 이 뜻하는 것은 아무 비트도 영향을 받지 않는 다는 것이며 0을 쓴다는 의미가 아닙니다.) 기본 255.
Comp
Comp 비교 함수
버퍼 안의 현재 값과 참조 값을 비교할 때 사용할 함수. 기본 always.
Pass
Pass 스텐실 작업
스텐실 검사(와 깊이 검사)가 통과한 경우 버퍼 안의 값을 어떻게 할지 정합니다. 기본 keep.
Fail
Fail 스텐실 작업
스텐실 검사가 실패한 경우 버퍼 안의 값을 어떻게 할지 정합니다. 기본 keep.
ZFail
ZFail 스텐실 작업
스텐실 검사는 통과했지만 깊이 검사가 실패한 경우 버퍼 안의 값을 어떻게 할지 정합니다. 기본 keep.
Comp, Pass, Fail 그리고 ZFail은 Cull Front를 적용하지 않는 이상(하는 경우에는 뒷면 기하), 앞면 기하(geometry)에 적용됩니다. 또한 CompFront, PassFront, FailFront, ZFailFront (앞면 기하의 경우), 그리고 CompBack, PassBack, FailBack, ZFailBack (뒷면 기하의 경우)를 정의하여 명시적으로 양면의 스텐실 상태를 적용할 수 있습니다.
비교 함수
비교 함수는 다음 중 하나를 따릅니다.
Greater | 버퍼의 값보다 참조 값이 큰 픽셀만 그립니다. |
GEqual | 버퍼의 값보다 참조 값이 크거나 같은 픽셀만 그립니다. |
Less | 버퍼의 값보다 참조 값이 작은 픽셀만 그립니다. |
LEqual | 버퍼의 값보다 참조 값이 작거나 같은 픽셀만 그립니다. |
Equal | 버퍼의 값과 참조 값이 같은 픽셀만 그립니다. |
NotEqual | 버퍼의 값과 참조 값이 다른 픽셀만 그립니다. |
Always | 스텐실 검사가 무조건 통과됩니다. |
Never | 스텐실 검사가 무조건 실패합니다. |
스텐실 작업
스텐실 작업은 다음 중 하나를 따릅니다.
Keep | 버퍼의 현재 값을 유지합니다. |
Zero | 버퍼에 0을 써넣습니다. |
Replace | 버퍼에 참조 값을 써넣습니다. |
IncrSat | 버퍼의 현재 값을 올립니다. 값이 이미 255이면 255를 유지합니다. |
DecrSat | 버퍼의 현재 값을 내립니다. 값이 이미 0이면 0을 유지합니다. |
Invert | 모든 비트를 반전합니다. |
IncrWarp | 버퍼의 현재 값을 올립니다. 값이 이미 255이면 0으로 변합니다. |
DecrWarp | 버퍼의 현재 값을 내립니다. 값이 이미 0이면 255로 변합니다. |
Deferred rendering path
지연 렌더링 패스(path)에서 그려진 물체에 적용된 스텐실 기능은 기본 패스(pass)와 조명 패스를 진행하면서 스텐실 버퍼를 다른 용도로 사용하기 때문에 사용이 제한적입니다. 셰이더에 정의된 앞서 말한 두 단계 동안의 스텐실 상태는 무시되고 최종 패스에만 고려(적용)됩니다. 왜냐하면 (다른 용도로) 스텐실 검사를 사용하는 이 물체들에 마스킹하는 것은 불가능하기 때문이지만, 프레임에서 나중에 그려진 물체에 사용하기 위해서 버퍼 값을 수정할 수는 있습니다. 지연 패스에 있는 전방 렌더링 패스에서 그려진 물체(예. 투명 물체나 표면 셰이더가 없는 물체)는 이후 다시 일반적으로 스텐실 상태를 설정할 수 있습니다.
지연 렌더링 패스는 스텐실 버퍼의 가장 높은 세 개의 비트를 사용하며 얼마나 많은 조명 마스크 레이어가 장면에서 사용될지에 따라 추가로 가장 높은 네 개의 비트를 더 사용합니다. (조명에 사용되지 않은) “깨끗한” 범위 안에 있는 비트는 마스크를 읽거나 써서 스텐실 조작이 가능하고, 조명 패스 이후에 Camera.clearStencilAfterLightingPass를 사용해서 카메라로 스텐실 버퍼를 강제로 초기화할 수 있습니다.
예제
첫 번째 예제 셰이더는 깊이 검사를 통과하면(스텐실 검사는 무조건 통과되게 설정됨) 값 2를 써넣으며, 깊이 검사가 실패하면 (스텐실 버퍼를 초기화하고 시작했다고 가정하고) 현재 값을 255로 내립니다. (0인 경우 255로 변경)
Shader "Red" { SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry"} Pass { Stencil { Ref 2 Comp always Pass replace ZFail decrWrap } 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 } } }
두 번째 셰이더는 값이 2와 같은지 확인한 후, 첫 번째(빨강) 셰이더를 통과한 픽셀만 통과시킵니다. 여기서도 스텐실 검사가 실패하면 버퍼의 값을 내립니다.
Shader "Green" { SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry+1"} Pass { Stencil { Ref 2 Comp equal Pass keep Fail decrWrap ZFail keep } 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 "Blue" { SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry+2"} Pass { Stencil { Ref 254 Comp equal } 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 } } }
결과
좀 더 직접적은 효과를 보여주는 다른 예제입니다. 스텐실 버퍼의 적절한 영역을 지정하기 위해 구체를 아래의 셰이더로 먼저 그립니다.
Shader "HolePrepare" { SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry+1"} ColorMask 0 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); } 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 } } }
그러고 나서 아주 표준적인 표면 셰이더로 앞서 지정된 픽셀을 무시하기 위해 깊이 검사와 스텐실 검사를 끄고 전면 기하를 배제하고 한 번 더 그립니다.
Shader "Hole" { Properties { _Color ("Main Color", Color) = (1,1,1,0) } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry+2"} ColorMask RGB Cull Front ZTest Always Stencil { Ref 1 Comp notequal } 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 } }
결과