유니티 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) (선택) |