Unity 검색

XR 그래픽스: 베스트 프랙티스와 문제 해결 방법

최근 업데이트: 2019년 1월

이 페이지의 내용: 셰이더를 최적화하고 포스트 프로세싱 스택을 가장 효율적으로 사용하는 방법에 대한 다양한 팁입니다. 이 게시물은 Unity에 숙련된 프로그래머이거나 XR(VR 또는 AR) 콘텐츠를 개발할 예정이거나 이미 개발 중인 개발자에게 유용합니다.

현재 Unity는 네 가지 XR 렌더링 방법인 멀티 패스, 싱글 패스, 최근에 릴리스된 싱글 패스 인스턴스화, 그리고 (싱글 패스 인스턴스화와 유사한) Android 싱글 패스를 지원합니다.

멀티 패스
  • 씬 그래프를 두 번 패스해야 합니다. 두 눈을 나타내는 텍스처 2개에 대해 오브젝트/씬을 각각 별도로 렌더링합니다.
  • GPU 작업을 여러 텍스처에 걸쳐 공유하면 렌더링 경로가 매우 비효율적으로 구성되므로 주의하세요. 하지만 대부분의 기기에서 기본적으로 작동하므로 변경할 필요가 거의 없습니다.
  • 컬링과 그림자 생성 렌더링의 일부를 공유합니다.
싱글 패스
  • 이 방법을 사용하면 텍스처 2개가 '더블 와이드 텍스처'라고 불리는 더 큰 텍스처 하나로 패킹됩니다.
  • 첫 오브젝트를 드로우하는 경우 뷰포트를 왼쪽으로 설정하고 오브젝트를 드로우한 다음, 뷰포트를 오른쪽으로 전환하고 오브젝트를 다시 드로우합니다.
  • 씬 그래프를 한 번만 패스하므로 CPU에서 훨씬 더 빠르게 처리됩니다. 하지만 이렇게 하려면 추가 GPU 상태 변경이 많이 필요합니다.

Unity XR 그래픽스

싱글 패스 스테레오 렌더링의 간단한 예

싱글 패스 인스턴스화
  • 2017.2 및 이후 버전에서 사용 가능합니다.
  • 두 눈을 나타내는 텍스처 두 개가 모두 텍스처 배열 하나에 포함됩니다(같은 텍스처 배열의 슬라이스 두 개).
  • 뷰포트를 설정하면 두 슬라이스에 모두 자동으로 적용됩니다. 드로우를 명령하면 노멀의 두 배인 인스턴스 드로우가 명령됩니다. 인스턴스에 따라 Unity가 어떤 슬라이스에 렌더링할지를 결정합니다.
  • 같은 수준의 GPU 작업이 필요하지만, 드로우 콜과 CPU 작업이 더 적으므로 훨씬 효율적입니다.
  • 사용 가능 조건:
    • Windows 10, D3D11, 최신 그래픽 드라이버.
    • 홀로렌즈.
    • PS4.
  • 사용 기기에서 확장 기능이 지원되지 않는 경우에는 멀티 패스를 대신 사용합니다.
  • DrawProceduralIndirect(...)는 수동 변경이 필요합니다.

Unity XR 그래픽스

싱글 패스 인스턴스화의 예

싱글 패스 인스턴스화를 사용하는 경우 셰이더에 추가할 매크로

싱글 패스 인스턴스화를 선택하는 경우 다음 매크로를 버텍스와 프래그먼트 셰이더에 추가할 것을 적극 권장합니다. 추가하는 과정은 매우 간단하며, 모든 Unity의 빌트인 셰이더가 이를 지원하도록 업데이트되었습니다.

버텍스 셰이더의 경우:

Unity XR 그래픽스

매크로 없는 셰이더 코드.

Unity XR 그래픽스

첫 매크로가 추가된 같은 코드...

Unity XR 그래픽스

두 번째 매크로...

Unity XR 그래픽스

세 번째...

Unity XR 그래픽스

네 번째 매크로가 추가된 모습.

unity_StereoEyeIndex를 프래그먼트 셰이더에 사용해야 하는 경우 아래 캡처를 참조하세요.

Unity XR 그래픽스

RenderScaleRenderViewportScale 사용 시의 몇 가지 차이점

텍스처 크기를 1에서 0.5로 조절하려는 경우를 가정해 보겠습니다. RenderScale을 사용하여 크기를 조절하면 오리지널 텍스처가 삭제되고, 각 차원에서 텍스처가 0.5이므로 크기가 오리지널 텍스처의 4분의 1인 새로운 텍스처가 생성됩니다.

게임 실행 중에 이 작업을 동적으로 수행하려면 리소스가 많이 사용되므로, RenderViewportScale이 더 효율적인 선택입니다. RenderViewportScale은 뷰포트를 동일 텍스처의 더 작은 부분으로 설정하고 해당 부분만 다음과 같이 렌더링합니다.

Unity XR 그래픽스

이렇게 하면 훨씬 더 효율적이지만, 텍스처의 일부분만 사용하므로 몇 가지 문제가 발생합니다. RenderViewportScale에 대한 추가 팁은 아래 섹션을 참조하세요.

따라서 RenderScale은 텍스처를 실제로 삭제한 후 새 텍스처를 만드는 반면, RenderViewportScale은 뷰포트를 수정합니다. 그 외에도 다음과 같은 몇 가지 차이점이 있습니다.

  • XRSettings.eyeTextureResolutionScale은 실제 텍스처 크기에 적용되는 반면, XRSettings.renderViewportScale은 렌더링에 사용되는 뷰포트에 적용됩니다.
  • 디퍼드 렌더링 사용 시에는 XRSettings.renderViewportScale이 지원되지 않습니다.
  • 스케일은 두 차원 모두에 적용되므로, 값이 0.5면 이미지 크기가 원본의 25%가 됩니다.
XR의 포스트 프로세싱 스택: 문제와 솔루션

우수한 포스트 프로세싱 스택의 최신 버전이 출시되었습니다. 이 스택을 XR 콘텐츠에 사용할 때 해결해야 할 몇 가지 문제가 있습니다. 다음 섹션에서는 이러한 문제와 현재 사용 가능한 주요 솔루션에 대해 설명합니다.

문제

여러 최신 포스트 프로세싱 효과를 사용하려면 소스 타겟 크기와 프로퍼티에 기반한 중간 렌더링 타겟을 렌더링해야 합니다.

  • 더블 와이드의 경우, 렌더 텍스처는 각 눈 부분의 너비보다 두 배 이상 넓습니다.
  • 싱글 패스 인스턴스 및 이와 유사한 Android 싱글 패스를 사용하는 경우 눈 하나에 슬라이스가 하나씩인 텍스처 배열을 만들어야 합니다.

솔루션

  • 올바른 스크린 공간 텍스처 포맷을 생성하는 과정에서 추측을 최소화하고 시간을 절약하기 위해, Unity는 XRSettings.eyeTextureDesc를 노출합니다(버전 2017.2 이상).
  • 그러면 엔진 XRTexture 관리자로부터 소싱되어 엔진으로 관리되는 텍스처와 일치하도록 설정된 RenderTextureDescriptor가 반환됩니다.
  • 소스에서 RenderTextureDescriptor를 쿼리할 수도 있습니다(사용 가능한 경우). 예를 들어 레거시 MonoBehaviour.OnRenderImage 인프라를 사용하는 경우 해당 소스 텍스처를 얻게 되고 거기서 디스크립터를 바로 가져올 수 있습니다.
  • eyeTextureDesc를 사용하면 모든 RenderTexture 할당을 제공된 RenderTextureDescriptor로 대체할 수 있습니다. 이 방법은 파라미터를 수동으로 생성하는 것보다 훨씬 더 효율적입니다.
  • RenderTextureDescriptor는 더 간단한 API로, 기본적인 API가 수행하는 작업을 따라 수행합니다. 명시적인 인수를 사용하는 경우 Unity는 인수를 RenderTextureDescriptor에 패킹하고 코어 엔진에 (RenderTexture.GetTemporary 또는 CommandBuffer.GetTemporaryRT에) 전달합니다. 따라서 중간 레이어가 모든 관리 작업을 대신 수행하도록 하는 대신 스크립트 쪽에서 사용자가 직접 관리하게 됩니다.

문제

XR에서는 물리적 렌더 텍스처 크기와 논리적 화면/눈 크기에 차이가 있습니다.

솔루션

  • 중간 렌더 텍스처 할당 시에는 해당 물리적 텍스처를 할당하도록 eyeTextureDesc를 사용하는 것이 좋습니다.
  • 화면 크기 기반 스크립트 또는 셰이더 로직이 있는 경우(예를 들어 화면 크기 파라미터를 셰이더에 사용하거나 텍스처 피라미드를 빌드하는 경우) 논리적 크기를 기준으로 하는 것이 좋습니다. 이 경우 XRSettings.eyeTextureHeightXRSettings.eyeTextureWidth를 사용할 수 있습니다.
  • 이 둘은 작업하는 화면의 크기를 인지하는 데 필요한 “각 눈에 대한” 텍스처 크기를 나타냅니다.
  • 더블 와이드의 경우 eyeTextureWidtheyeTextureDesc 너비의 절반과 정확히 같지 않다는 점에 주의하세요. 그 이유는 디스크립터가 텍스처 할당 목적으로 너비를 두 배로 늘린 다음 약간의 패딩을 적용하여 밉 매핑에 사용할 수 있도록 설정하기 때문입니다.

다음은 여러분이 eyeTextureWidth를 사용하게 되기 전에 화면 너비로 사용할 너비를 결정하기 위해 사용했을 법한 코드의 예입니다.

Unity XR 그래픽스

이제는 간단히 eyeTexturewidth에서 화면 너비를 소스로 사용하여 화면 비율과 같은 부분에 적용할 수 있습니다. 아래 스크립트 예를 참조하세요.

Unity XR 그래픽스

문제

텍스처 좌표가 스테레오에 적합하고 올바른 눈으로 출력하여 샘플링하고 있는지 어떻게 확인할 수 있을까요?

더블 와이드 텍스처로부터 샘플링하는 경우, 텍스처 좌표가 텍스처의 올바른 반쪽(각 반쪽은 눈 하나에 해당됨)에서 샘플링하는지 확인해야 합니다.

솔루션

각 눈마다 텍스처 좌표 보정자가 하나씩 필요합니다. Unity에서는 이를 위해 다음 두 가지 솔루션을 제공합니다.

1. TRANSFORM_TEX 매크로

  • This is a macro that works with _ST properties in ShaderLab. It’s most commonly used with MainTex_ST.
  • _ST 프로퍼티는 싱글 패스 더블 와이드 모드에서 각 눈보다 먼저 업데이트되어 텍스처 좌표를 눈에 적합한 범위로 변환합니다. 또한, RenderViewportScale를 자동으로 처리해 줍니다.

Unity XR 그래픽스

  • Unity will populate _ST shader properties if you’re using Graphics.Blit or CommandBuffer.Blit:
    • MainTex_ST는 항상 채워져 있습니다.
    • 나머지 _ST 프로퍼티는 텍스처가 XR 텍스처(VRTextureUsage!=None)인 경우에 채워집니다.
  • 이 매크로는 최신 포스트 프로세싱 스택의 BlitFullScreenTriangle 같은 커스텀 빌드 절차와 함께 작동하지 않습니다.

2. unity_StereoScaleOffsetUnityStereoTransformScreenSpaceTex/TransformStereoScreenSpaceTex 헬퍼 함수

unity_StereoScaleOffset는 float4s의 배열입니다.

  • UnityStereoTransformScreenSpaceTex
  • TransformStereoScreenSpaceTex

이는 싱글 패스 상수 블록의 일부로 선언되고(UnityStereoGlobals), Unity_StereoEyeIndex를 사용하여 이 블록 안에 인덱싱됩니다.

더블 와이드의 경우, unity_StereoEyeIndex는 상수 버퍼 안에 바인드된 다음 각 드로우마다 올바른 눈이 업데이트됩니다. 이에 따라 왼쪽 눈 값이 상수 버퍼에 삽입되고 왼쪽 눈이 드로우된 다음, 오른쪽 눈 값이 상수 버퍼에 삽입된 후 오른쪽 눈이 드로우됩니다. 셰이더 인스턴스는 사용하는 모든 드로우에 대해 상수 버퍼를 확인하여 unity_StereoEyeIndex의 올바른 값을 찾을 수 있다는 것을 압니다.

Unity XR 그래픽스

unity_StereoEyeIndex가 올바르게 채워졌다는 것을 알면 unity_StereoScaleOffset에서 올바른 요소가 선택될 것이라는 점에 대해서도 확신할 수 있습니다.

Unity XR 그래픽스

unity_StereoScaleOffset을 사용하는 경우 다음과 같은 단점이 있습니다.

  • 싱글 패스에만 사용 가능하므로 헬퍼 메서드를 사용해야 합니다.
  • 이 메서드를 직접 사용하면 멀티 패스와 단안으로 이어지고 셰이더 컴파일 오류가 발생합니다. 또한, RenderViewportScale이 고려되지 않습니다.

텍스처 배열이 올바르게 선언되고, 적절한 슬라이스에서 샘플링하고 있는지 확인하려면 어떻게 해야 할까요?

솔루션

UNITY_DECLARE_SCREENSPACE_TEXTURE를 사용합니다. 셰이더 언어에서는 텍스처 배열을 '노멀' 텍스처와 다르게 처리합니다. 싱글 패스 인스턴스화와 Android 싱글 패스에서는 텍스처 배열을 사용하므로, 텍스처를 올바르게 선언해야 합니다.

Unity XR 그래픽스

더블 와이드의 경우와 유사하게 올바른 눈 부분에서 샘플링해야 합니다. 해당 눈 부분은 텍스처 배열의 슬라이스에 삽입됩니다. unity_StereoEyeIndex에서 UNITY_SAMPLE_SCREENSPACE_TEXTURE 매크로를 사용하여 올바른 슬라이스를 소싱할 수 있습니다.

Unity XR 그래픽스

RenderViewportScale 관리: 몇 가지 기억할 사항

  • unity_StereoScaleOffset는 지원을 제공하지 않습니다.
  • 대부분의 포스트 프로세싱 효과는 CommandBuffer.Blit/Graphics.Blit을 사용하므로, use_MainTex_ST(및 기타 _ST 메서드)를 사용하여 RenderViewportScale을 지원할 수 있습니다.
  • 하지만 Unity 포스트 프로세싱 스택의 v.2에서는 이러한 메서드를 사용할 수 없습니다. 따라서 이 문제는 자체적으로 해결해야 합니다. 스택의 v.2는 자체 셰이더 인프라 버전을 사용하므로 쉽게 오버라이드할 수 있어 매우 간단하게 문제를 해결할 수 있습니다. 다음 작업을 수행해야 합니다.

    셰이더 측에서:

    • 새 상수를 생성합니다. xrLib.hlsl 안에서 "float rvsGlobal"을 선언합니다.
    • rvsGlobal을 이용하도록 TransformStereoScreenSpaceTex를 재작업합니다.

    스크립트 측에서:

    • RenderViewportScale 값을 스크립트의 전역 프로퍼티로 바인딩합니다.
    • Shader.SetGlobalFloat를 사용합니다.

    Unity XR 그래픽스

스테레오 클램핑

셰이더에서 인접 스크린 공간을 샘플링하는 경우, 실수로 싱글 패스 더블 와이드에서 잘못된 눈을 샘플링하거나 유효한 RenderViewportScale 영역 밖에서 샘플링하지 않아야 합니다. 이를 위해 좌표 샘플을 텍스처의 올바른 부분에 간단히 고정해주는 UnityStereoClamp가 유용합니다.

Unity XR 그래픽스

사용법은 매우 간단하지만, 인접 샘플링을 찾으려면 수동으로 검사해야 합니다.

보간 생성된 텍스처 좌표에서 오프셋하는 경우 항상 Unity.StereoClamp()를 사용해야 할 수 있습니다.

  • 동일한 좌표에서 샘플링할 때 클램핑으로 인해 발생하는 영향에 유의하세요.
  • 버텍스 셰이더에는 사용하지 마세요.
마지막으로 몇 가지 팁을 더 알려 드립니다.
  • 사용하는 포스트 프로세싱 효과가 XR에 적합한지 고려하세요.
    • 시간적 효과에 블러와 지연을 추가할 것인지 고려합니다.
    • 대개의 경우 사람의 눈을 시뮬레이션하는 헤드셋에서 물리 카메라의 프로퍼티인 뎁스오브필드(피사계심도)가 멀미를 유발할 수 있으므로 뎁스오브필드를 시뮬레이션할 것인지 고려합니다.
  • 멀티 패스를 사용하며 이력 텍스처를 보관하는 경우, 각 눈마다 한 세트의 이력 텍스처가 필요합니다.
    • 하나의 이력 세트만 있을 경우 각 눈에서 이력을 공유할 수 있습니다.
    • 이렇게 되면 싱글 패스로 작업하도록 자동 설정됩니다.
  • 문제가 발생하는 경우 다른 스테레오 렌더링 모드를 사용해 보세요.
    • 아티팩트를 살펴보면 어떤 부분에서 문제가 발생했는지 알 수 있습니다.
리소스 더 보기
확인

유니티에서는 웹 사이트의 모든 기능을 최대로 이용할 수 있도록 쿠키를 사용합니다. 자세한 정보는 쿠키 정책 페이지를 참조하세요.