"눈이 나쁘면 안경을 쓴댔으니 모자란 너에겐 모자를 씌워주마" – 김케장 / 동전주머니엔 동전이 들었지

20160118
셰이더랩: 스텐실

원문 – http://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
    } 
}

결과

댓글 남기기 | 타닥타닥

댓글 남기기

* 표시된 곳은 반드시 입력해주세요