}

유니티 VR에서 한쪽 눈만 렌더링될 때, 셰이더 작법

유니티 URP환경에서 OpenXR 환경 혹은 Meta XR환경에서 SPI(Single Pass Instance) 문제, 한 쪽 눈에만 렌더링 되는 문제를 두 가지 예제로 알아보겠습니다.

그전에 SPI 등에 대한 개념은 아래 포스팅을 참고해주세요.

 

유니티 VR에서 한 쪽눈만 렌더링되는 경우 #싱글 패스 인스턴스화 렌더링(Single Pass Instanced)

VR을 개발하다 보면 한 쪽 눈에만 오브젝트 혹은 UI가 렌더링되는 경우가 종종 발생하시죠?이러한 문제들을 해결하기 위해서는 싱글 패스 인스턴스(Single Pass Instanced)라는 개념을 아셔야 합니다. 

wlsdn629.tistory.com

 

유니티 Multi Pass와 Single Pass Instanced/Multi View에 대해

VR작업을 하다보면 초반 세팅하는 부분에 한 번씩쯤 다들 봤을 것입니다. Rendering Mode에 보면 Multi Pass와 Single Pass Instanced , 모바일의 경우 Multi View가 존재합니다. 이 3개의 차이는 아래 표와 같습니

wlsdn629.tistory.com

 

유니티 Oculus XR Plugin에 대해

Windows standalone settings Stereo Rendering Mode - Multi Pass 또는 Single Pass Instanced 렌더링 모드를 선택할 수 있습니다. Multi Pass 또는 Single Pass Instanced에 대해서는 언급한 적이 있으니 해당 포스트를 보고 오시

wlsdn629.tistory.com

 


예제: UnlitTransparentAlwaysOnTop

Shader "Custom/UnlitTransparentAlwaysOnTop"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Color ("Tint Color", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        ZWrite Off
        ZTest Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;

            v2f vert(appdata v)
            {
                UNITY_SETUP_INSTANCE_ID(v);
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                col.a = _Color.a;
                return col;
            }
            ENDCG
        }
    }
}

이 셰이더는 multi_compile_instancing과 관련 매크로가 완벽히 포함되어 있어 Single-Pass Instanced(SPI) 환경에서도 양쪽 눈에 잘 출력됩니다.


예제: 스텐실 기반 유리 뒤에서만 오브젝트 보이게 하기

앞서 달리, 이 경우엔 하나의 오브젝트(유리)를 기준으로 뒤 오브젝트만 보이도록 스텐실 마스크를 활용합니다.

MaskGlass (스텐실 1 기록)

Shader "Custom/MaskGlass"
{
    Properties {
        _Cutoff ("Alpha Cutoff", Range(0,1)) = 0.1
        _BaseColor ("Base Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Opaque" "Queue"="Geometry+0" }
        Pass
        {
            ColorMask 0
            ZWrite Off
            ZTest LEqual
            Cull Off

            Stencil {
                Ref 1
                Comp Always
                Pass Replace
            }

            HLSLPROGRAM
            #pragma target 4.5
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert(appdata v) {
                UNITY_SETUP_INSTANCE_ID(v);
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.uv = v.uv;
                return o;
            }

            float4 frag(v2f i) : SV_Target {
                discard;
            }
            ENDHLSL
        }
    }
}

이 셰이더는 유리 위치에 Stencil = 1 값을 기록합니다.
ColorMask 0 덕분에 눈에 보이진 않지만, 마스킹만 정확히 수행합니다.


RevealBehindGlass (스텐실 1인 곳만 출력)

Shader "Custom/RevealBehindGlass"
{
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Color ("Tint Color", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        Stencil {
            Ref 1
            Comp Equal
            Pass Keep
        }

        ZWrite Off
        ZTest Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma target 4.5 //여기부터
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc" //여기까지 순서 중요

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID //GPU 인스턴싱을 위한 unity_InstanceID 제공
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO //각 눈에 맞는 Render Target Slice를 지정할 필드 자동 생성
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;

            v2f vert(appdata v) {
                UNITY_SETUP_INSTANCE_ID(v); //GPU 인스턴싱 활성화 및 현재 인스턴스(눈-Eye) 인식
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //(오른쪽/왼쪽) 눈을 렌더 타겟으로 정확히 분리 지정
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                col.a = _Color.a;
                return col;
            }
            ENDCG
        }
    }
}

이 셰이더는 Stencil == 1인 영역에서만 동작합니다.
즉, MaskGlass가 마킹한 유리 너머 오브젝트만 보여집니다.


정리: 한쪽 눈만 보일 때 체크리스트 

항목 설명
#pragma target 4.5 SPI에서 SV_RenderTargetArrayIndex 사용 가능
#pragma multi_compile_instancing 인스턴싱 변형 키워드 활성화
UNITY_VERTEX_INPUT_INSTANCE_ID 앱데이터에 인스턴스 ID 제공
UNITY_SETUP_INSTANCE_ID(IN) GPU 인스턴싱 초기화
UNITY_VERTEX_OUTPUT_STEREO + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT) Eye 별 렌더타겟 분리
인스턴싱을 준비하고 UNITY_VERTEX_INPUT_INSTANCE_ID
인식시켜주고 UNITY_SETUP_INSTANCE_ID(v)
눈별 출력 필드를 만들고 UNITY_VERTEX_OUTPUT_STEREO
어떤 눈인지 알려주고 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o)
실수 방지용 초기화 UNITY_INITIALIZE_OUTPUT(v2f, o) (선택)