URP에서 HDRP로 셰이더 포팅 가이드

Mazeline·2025년 10월 21일
0
post-thumbnail

URP 에서만 동작하는 셰이더를 HDRP 로 전환 해야할 일이 생겼습니다. 몇 가지 부분을 기록하고자 합니다. 50살을 넘어간 후부터는 기억력이 정말 하루 하루 …

요약

URP에서 HDRP로 셰이더를 포팅하는 작업은 단순한 문법 수정이 아닌 근본적인 아키텍처 변경을 필요로 합니다. 가장 중요한 차이점은 깊이 텍스처 접근 방식(LoadCameraDepth vs SAMPLE_DEPTH_TEXTURE), include 파일 경로, 셰이더 패스 구조, 그리고 매크로 정의입니다. 이 가이드에서는 각 셰이더 타입별로 작동하는 예제와 함께 체계적인 마이그레이션 패턴을 제공합니다.

핵심 아키텍처 차이점

렌더링 파이프라인 철학

URP는 모바일 및 중급 사양에 최적화된 렌더러입니다. 반면 HDRP는 하이엔드 플랫폼을 위한 렌더러이며 기본적으로 레이트레이싱을 지원하고 있습니다.

깊이 버퍼 시스템

URP_CameraDepthTexture를 사용한 직접 텍스처 샘플링 방식을 사용합니다. 이와 달리 HDRPLoadCameraDepth() 또는 SampleCameraDepth() 함수를 필요로 하는 계층적 깊이 피라미드 구조를 채택하고 있습니다. HDRP에서는 깊이 텍스처를 절대 직접 샘플링하지 마세요.

Include 파일 마이그레이션 맵

// URP Include 구조
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

// HDRP 대응 구조
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl"

전체 Include 매핑 테이블

URP IncludeHDRP 대응목적
Core.hlslCommon.hlsl + ShaderVariables.hlsl기본 타입 및 행렬
Lighting.hlslLighting.hlsl (HDRP 버전)라이팅 계산
DeclareDepthTexture.hlslShaderVariables.hlsl깊이 텍스처 접근
DeclareNormalsTexture.hlslNormalBuffer.hlsl노말 버퍼 접근
ShaderVariablesFunctions.hlslShaderVariablesFunctions.hlsl (HDRP)헬퍼 함수
Shadows.hlslLightLoop/Shadow.hlsl그림자 샘플링
BRDF.hlslBSDF.hlsl머티리얼 응답 함수

깊이 텍스처 접근 마이그레이션

URP 깊이 패턴

// URP 깊이 접근
TEXTURE2D_X(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);

float GetDepth(float2 uv)
{
    float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, uv);
    float eyeDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
    return eyeDepth;
}

HDRP 마이그레이션

// HDRP 깊이 접근 - 텍스처 선언 불필요
float GetDepth(float2 uv)
{
    uint2 positionSS = uv * _ScreenSize.xy;
    float rawDepth = LoadCameraDepth(positionSS);
    float eyeDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
    return eyeDepth;
}

주요 차이점

HDRP에서는 명시적인 텍스처 선언이 불필요합니다. 픽셀 좌표에는 LoadCameraDepth()를, UV에는 SampleCameraDepth()를 사용합니다. 변환 함수에 _ZBufferParams를 명시적으로 전달해야 하며, HDRP는 단일 텍스처가 아닌 깊이 피라미드를 사용한다는 점을 기억하세요.

매크로 및 함수 마이그레이션

텍스처 선언

// URP
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);

// HDRP
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);  // 일반 텍스처는 동일

텍스처 샘플링

// URP
float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);

// HDRP
float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);  // 일반 텍스처는 동일
// 하지만 스크린 텍스처의 경우:
float4 color = LOAD_TEXTURE2D_X(_InputTexture, positionSS);  // 스테레오를 위한 _X 주목

변환 함수

// URP
float3 worldPos = TransformObjectToWorld(objectPos);
float4 clipPos = TransformWorldToHClip(worldPos);
float3 viewPos = TransformWorldToView(worldPos);

// HDRP - 동일한 이름, 다른 include
float3 worldPos = TransformObjectToWorld(objectPos);
float4 clipPos = TransformWorldToHClip(worldPos);
float3 viewPos = TransformWorldToView(worldPos);

깊이 변환 함수

// URP
float linear01 = Linear01Depth(rawDepth, _ZBufferParams);
float linearEye = LinearEyeDepth(rawDepth, _ZBufferParams);

// HDRP - 시그니처 완전히 동일
float linear01 = Linear01Depth(rawDepth, _ZBufferParams);
float linearEye = LinearEyeDepth(rawDepth, _ZBufferParams);

완전한 셰이더 마이그레이션 예제

원본 URP 셰이더

Shader "URP/DepthFog"
{
    Properties
    {
        _FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1)
        _FogDensity ("Fog Density", Range(0, 1)) = 0.5
        _FogStart ("Fog Start", Float) = 10
        _FogEnd ("Fog End", Float) = 50
    }

    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" }

        Pass
        {
            Name "DepthFog"

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 screenPos : TEXCOORD1;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _FogColor;
                float _FogDensity;
                float _FogStart;
                float _FogEnd;
            CBUFFER_END

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip([input.positionOS.xyz](http://input.positionOS.xyz));
                output.uv = input.uv;
                output.screenPos = ComputeScreenPos(output.positionCS);
                return output;
            }

            float4 frag(Varyings input) : SV_Target
            {
                float2 screenUV = input.screenPos.xy / input.screenPos.w;
                float rawDepth = SampleSceneDepth(screenUV);
                float eyeDepth = LinearEyeDepth(rawDepth, _ZBufferParams);

                float fogFactor = saturate((eyeDepth - _FogStart) / (_FogEnd - _FogStart));
                fogFactor = pow(fogFactor, _FogDensity);

                float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                color.rgb = lerp(color.rgb, _FogColor.rgb, fogFactor);

                return color;
            }
            ENDHLSL
        }
    }
}

마이그레이션된 HDRP 셰이더

Shader "HDRP/DepthFog"
{
    Properties
    {
        _FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1)
        _FogDensity ("Fog Density", Range(0, 1)) = 0.5
        _FogStart ("Fog Start", Float) = 10
        _FogEnd ("Fog End", Float) = 50
    }

    HLSLINCLUDE
    #pragma target 4.5
    #pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch

    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPass.cs.hlsl"

    struct Attributes
    {
        float4 positionOS : POSITION;
        float2 uv : TEXCOORD0;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };

    struct Varyings
    {
        float4 positionCS : SV_POSITION;
        float2 uv : TEXCOORD0;
        float4 screenPos : TEXCOORD1;
        UNITY_VERTEX_OUTPUT_STEREO
    };

    float4 _FogColor;
    float _FogDensity;
    float _FogStart;
    float _FogEnd;

    TEXTURE2D(_MainTex);
    SAMPLER(sampler_MainTex);
    ENDHLSL

    SubShader
    {
        Tags { "RenderPipeline"="HDRenderPipeline" "Queue"="Transparent" }

        Pass
        {
            Name "ForwardOnly"
            Tags { "LightMode" = "ForwardOnly" }

            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            Varyings vert(Attributes input)
            {
                Varyings output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

                output.positionCS = TransformObjectToHClip([input.positionOS.xyz](http://input.positionOS.xyz));
                output.uv = input.uv;
                output.screenPos = ComputeScreenPos(output.positionCS);

                return output;
            }

            float4 frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                // HDRP용 UV를 픽셀 좌표로 변환
                float2 screenUV = input.screenPos.xy / input.screenPos.w;
                uint2 positionSS = screenUV * _ScreenSize.xy;

                // HDRP 깊이 로딩 함수 사용
                float rawDepth = LoadCameraDepth(positionSS);
                float eyeDepth = LinearEyeDepth(rawDepth, _ZBufferParams);

                float fogFactor = saturate((eyeDepth - _FogStart) / (_FogEnd - _FogStart));
                fogFactor = pow(fogFactor, _FogDensity);

                float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                color.rgb = lerp(color.rgb, _FogColor.rgb, fogFactor);

                return color;
            }
            ENDHLSL
        }

        // HDRP의 깊이 프리패스를 위해 필요
        Pass
        {
            Name "DepthForwardOnly"
            Tags { "LightMode" = "DepthForwardOnly" }

            ColorMask 0
            ZWrite On

            HLSLPROGRAM
            #pragma vertex VertDepth
            #pragma fragment FragDepth

            Varyings VertDepth(Attributes input)
            {
                Varyings output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
                output.positionCS = TransformObjectToHClip([input.positionOS.xyz](http://input.positionOS.xyz));
                return output;
            }

            void FragDepth(Varyings input)
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
            }
            ENDHLSL
        }
    }
}

포스트 프로세싱 마이그레이션

URP 포스트 프로세싱 V2/V3

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[VolumeComponentMenu("Custom/URPEffect")]
public class URPPostEffect : VolumeComponent, IPostProcessComponent
{
    public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f);

    public bool IsActive() => intensity.value > 0f;
    public bool IsTileCompatible() => false;
}

// Render Feature 구현
public class URPPostProcessRenderFeature : ScriptableRendererFeature
{
    class CustomRenderPass : ScriptableRenderPass
    {
        public override void Execute(ScriptableRenderContext context,
                                    ref RenderingData renderingData)
        {
            // 렌더링 로직
        }
    }
}

HDRP 커스텀 포스트 프로세싱

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using System;

[Serializable, VolumeComponentMenu("Post-processing/Custom/HDRPEffect")]
public sealed class HDRPPostEffect : CustomPostProcessVolumeComponent, IPostProcessComponent
{
    public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f);

    Material m_Material;

    public bool IsActive() => m_Material != null && intensity.value > 0f;

    // 인젝션 포인트는 이펙트가 실행되는 시점을 결정합니다
    public override CustomPostProcessInjectionPoint injectionPoint =>
        CustomPostProcessInjectionPoint.AfterPostProcess;

    public override void Setup()
    {
        if (Shader.Find("Hidden/HDRP/PostEffect") != null)
            m_Material = new Material(Shader.Find("Hidden/HDRP/PostEffect"));
    }

    public override void Render(CommandBuffer cmd, HDCamera camera,
                                RTHandle source, RTHandle destination)
    {
        if (m_Material == null) return;

        m_Material.SetFloat("_Intensity", intensity.value);
        m_Material.SetTexture("_InputTexture", source);
        HDUtils.DrawFullScreen(cmd, m_Material, destination);
    }

    public override void Cleanup() => CoreUtils.Destroy(m_Material);
}

커스텀 패스 마이그레이션

URP Render Feature

public class URPCustomPass : ScriptableRendererFeature
{
    class Pass : ScriptableRenderPass
    {
        public override void Execute(ScriptableRenderContext context,
                                    ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get("URPCustomPass");

            // 카메라 텍스처 가져오기
            RenderTargetIdentifier source = renderingData.cameraData.renderer.cameraColorTarget;
            RenderTargetIdentifier depth = renderingData.cameraData.renderer.cameraDepthTarget;

            // 패스 실행
            cmd.Blit(source, destination, material);

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

    public override void AddRenderPasses(ScriptableRenderer renderer,
                                         ref RenderingData renderingData)
    {
        renderer.EnqueuePass(customPass);
    }
}

HDRP 커스텀 패스

public class HDRPCustomPass : CustomPass
{
    public LayerMask layerMask = ~0;
    public Material overrideMaterial;

    protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
    {
        // 일회성 설정
    }

    protected override void Execute(CustomPassContext ctx)
    {
        // HDRP는 사전 구성된 컨텍스트를 제공합니다
        if (overrideMaterial == null) return;

        // 오버라이드 머티리얼로 렌더러 그리기
        CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ctx.cameraDepthBuffer);
        CustomPassUtils.DrawRenderers(ctx, layerMask, overrideMaterial: overrideMaterial);

        // 또는 풀스크린 패스
        CoreUtils.DrawFullScreen(ctx.cmd, overrideMaterial, ctx.cameraColorBuffer);
    }
}

라이팅 및 머티리얼 함수

URP 라이팅

// URP 메인 라이트
Light GetMainLight()
{
    Light light;
    light.direction = _[MainLightPosition.xyz](http://MainLightPosition.xyz);
    light.color = _MainLightColor.rgb;
    light.shadowAttenuation = MainLightRealtimeShadow(shadowCoord);
    return light;
}

// URP 추가 라이트
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0; lightIndex < pixelLightCount; ++lightIndex)
{
    Light light = GetAdditionalLight(lightIndex, worldPos);
    // 라이트 처리
}

HDRP 라이팅

// HDRP는 LightLoop include와 다른 구조가 필요합니다
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl"

// HDRP 디렉셔널 라이트 (단순화)
DirectionalLightData light = _DirectionalLightDatas[0];
float3 lightDirection = -light.forward;
float3 lightColor = light.color;

// HDRP는 주로 디퍼드 라이팅을 사용합니다
// 직접 라이팅 접근은 특정 패스와 BSDFData를 필요로 합니다

서피스 셰이더 마이그레이션

URP Lit Shader Graph 프로퍼티

URP Lit Shader Graph는 Base Map, Normal Map, Metallic/Smoothness, Occlusion, Emission 프로퍼티를 제공합니다.

HDRP Lit Shader Graph 프로퍼티

HDRP Lit Shader Graph는 Base Color Map(Base Map이 아님), Normal Map(Object 또는 Tangent Space), Mask Map(RGBA: Metallic, Occlusion, Detail, Smoothness), Detail Map, Emission을 제공합니다. 추가로 Coat Mask, Thickness, Subsurface 등의 고급 프로퍼티도 사용할 수 있습니다.

주요 차이점

HDRP는 별도 텍스처 대신 패킹된 Mask Map을 사용합니다. 또한 서브서피스 스캐터링, 투과(transmission), 코팅(coat) 등 더 많은 머티리얼 기능을 제공합니다. 텍스처 패킹 규칙도 URP와 다르므로 주의가 필요합니다.

일반적인 마이그레이션 이슈 및 해결책

중요 주의사항

Screen Space 좌표 변환 주의

HDRP에서 가장 흔한 실수 중 하나는 Screen Space UV와 픽셀 좌표를 혼동하는 것입니다. LoadCameraDepth()는 픽셀 좌표(uint2)를 받지만, SampleCameraDepth()는 UV 좌표(float2)를 받습니다. 잘못된 좌표계를 사용하면 깊이 값이 완전히 잘못 읽히므로 반드시 확인하세요.

// 잘못된 예 - UV를 Load에 전달
float depth = LoadCameraDepth(uv); // 컴파일 에러 또는 잘못된 값

// 올바른 예
uint2 pixelCoord = uv * _ScreenSize.xy;
float depth = LoadCameraDepth(pixelCoord);

// 또는 SampleCameraDepth 사용
float depth = SampleCameraDepth(uv);

CBUFFER와 전역 변수 선언

HDRP에서는 CBUFFER_START/END를 사용하지 않고 전역으로 머티리얼 프로퍼티를 선언하는 경우가 많습니다. HLSLINCLUDE 블록에서 선언하면 모든 패스에서 공유할 수 있습니다. SRP Batcher 호환성을 위해서는 UnityPerMaterial CBUFFER를 사용하는 것이 좋지만, 커스텀 셰이더에서는 전역 선언도 가능합니다.

// HDRP 스타일 - HLSLINCLUDE에서 전역 선언
HLSLINCLUDE
float4 _Color;
float _Intensity;
ENDHLSL

// 또는 SRP Batcher 호환
HLSLINCLUDE
CBUFFER_START(UnityPerMaterial)
    float4 _Color;
    float _Intensity;
CBUFFER_END
ENDHLSL

Alpha Clipping 구현

HDRP에서 Alpha Clipping을 사용하는 경우, 모든 패스에서 동일하게 클리핑을 적용해야 합니다. 특히 DepthForwardOnly와 ShadowCaster 패스에서 클리핑을 누락하면 그림자와 깊이가 불일치하여 시각적 아티팩트가 발생합니다. 이 부분은 URP 도 동일하게 주의 해야 합니다.

// Fragment 함수에서 클리핑
void frag(Varyings input)
{
    float alpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).a;
    clip(alpha - _Cutoff); // 모든 패스에 필수
}

Normal Buffer 인코딩 차이

HDRP는 URP와 다른 Normal 인코딩 방식을 사용합니다. Normal Buffer를 직접 읽는 경우, NormalData.hlslDecodeFromNormalBuffer() 함수를 사용해야 합니다. 직접 디코딩하면 잘못된 결과가 나올 수 있습니다.

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"

// 올바른 Normal 읽기
NormalData normalData;
DecodeFromNormalBuffer(positionSS, normalData);
float3 worldNormal = normalData.normalWS;

RTHandle vs RenderTexture

HDRP는 동적 해상도 스케일링을 지원하기 위해 RTHandle 시스템을 사용합니다. 커스텀 패스나 포스트 프로세싱에서 일반 RenderTexture 대신 RTHandle을 사용해야 합니다. RenderTexture를 직접 사용하면 동적 해상도 변경 시 문제가 발생할 수 있습니다.

// 잘못된 예
RenderTexture rt = new RenderTexture(1920, 1080, 0);

// 올바른 예
RTHandle rt = RTHandles.Alloc([Vector2.one](http://Vector2.one), filterMode: FilterMode.Bilinear);

셰이더 키워드 관리

HDRP는 매우 많은 내부 키워드를 사용합니다. 커스텀 셰이더에서 multi_compile을 추가할 때는 신중해야 합니다. 불필요한 키워드는 베리언트 폭발을 일으켜 빌드 시간과 메모리를 크게 증가시킵니다. shader_feature_local을 사용하여 머티리얼별로만 활성화되는 키워드로 제한하세요.

// 베리언트 폭발 위험
#pragma multi_compile _ FEATURE_A FEATURE_B FEATURE_C

// 더 안전한 방법
#pragma shader_feature_local _FEATURE_A
#pragma shader_feature_local _FEATURE_B

Color Space 변환

HDRP는 항상 Linear Color Space에서 작동합니다. 텍스처를 sRGB로 임포트했는지 확인하고, 셰이더에서 색상 계산 시 Gamma 변환이 필요한지 검토하세요. 특히 UI 텍스처나 라이트맵을 다룰 때 주의가 필요합니다.

Shader Graph 마이그레이션 함정

Shader Graph로 작성된 URP 셰이더는 HDRP로 단순히 변환할 수 없습니다. Target을 HDRP로 변경하면 많은 노드가 작동하지 않거나 다르게 동작합니다. 특히 Scene Depth, Scene Color 노드는 완전히 다시 설정해야 합니다. 가능하면 코드 셰이더로 먼저 포팅한 후 Shader Graph로 재작성하는 것을 권장합니다.

패스 순서와 렌더 큐

HDRP는 엄격한 렌더 큐 시스템을 사용합니다. Transparent 오브젝트는 반드시 "Transparent" 큐를 사용해야 하며, Opaque와 혼용하면 렌더링 순서가 꼬일 수 있습니다. 커스텀 패스의 인젝션 포인트도 신중하게 선택해야 합니다.

// Transparent 셰이더는 반드시 올바른 큐 사용
Tags { "RenderPipeline"="HDRenderPipeline" "Queue"="Transparent" "RenderType"="Transparent" }

디버깅 팁

HDRP 셰이더 디버깅 시 Rendering Debugger(Window > Analysis > Rendering Debugger)를 적극 활용하세요. Material, Lighting, Rendering 탭에서 깊이, 노말, 모션 벡터 등을 시각화할 수 있습니다. 또한 Frame Debugger로 각 패스의 실행을 확인하세요.

프로젝트 설정 확인

HDRP Asset 설정에서 Depth Pyramid, Normal Buffer, Motion Vectors가 활성화되어 있는지 확인하세요. 이들이 비활성화되어 있으면 해당 기능을 사용하는 셰이더가 제대로 작동하지 않습니다. 또한 Quality Level별로 설정이 다를 수 있으므로 모든 Quality Level을 확인하세요.

버전 호환성

HDRP는 Unity 버전에 따라 API가 자주 변경됩니다. 특정 버전용으로 작성된 셰이더는 다른 버전에서 컴파일 에러가 발생할 수 있습니다. 버전 전환 시에는 HDRP 패키지 문서를 반드시 확인하고, 필요하면 #if UNITY_VERSION 매크로로 버전별 코드를 분기하세요.

#if UNITY_VERSION >= 202200 // Unity 2022 이상
    // 새 API 사용
#else
    // 구 API 사용
#endif

일반적인 마이그레이션 이슈 및 해결책

이슈 1: 셰이더가 컴파일되지 않음

증상: "Cannot find include file" 에러가 발생합니다.

해결책: 모든 include 경로를 HDRP 대응 경로로 업데이트하세요.

// 잘못된 방법
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

// 올바른 방법
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"

이슈 2: 깊이가 잘못된 값을 반환함

증상: 깊이 기반 이펙트에서 아티팩트나 잘못된 거리가 표시됩니다.

해결책: HDRP 깊이 함수를 사용하세요.

// 잘못된 방법 - URP 패턴
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, uv);

// 올바른 방법 - HDRP 패턴
float depth = LoadCameraDepth(positionSS);

이슈 3: 오브젝트가 씬에서 보이지 않음

증상: 셰이더가 검은색으로 렌더링되거나 보이지 않습니다.

해결책: 필요한 HDRP 패스를 추가하세요. 깊이 프리패스를 위한 DepthForwardOnly 패스, 그림자를 위한 ShadowCaster 패스, 라이트맵 베이킹을 위한 META 패스를 추가해야 합니다.

이슈 4: 포스트 프로세싱이 작동하지 않음

증상: 커스텀 포스트 프로세스가 나타나지 않습니다.

해결책: 먼저 셰이더를 Resources 폴더에 배치하세요. 그런 다음 HDRP Settings의 Custom Post Process Orders에 추가합니다. 마지막으로 Volume에 컴포넌트가 활성화되어 있는지 확인하세요.

이슈 5: 머티리얼 외형이 다름

증상: 동일한 텍스처가 HDRP에서 다르게 보입니다.

해결책: HDRP 셰이더로 머티리얼을 재생성하세요. Mask Map 형식에 맞게 텍스처를 리패킹하고, HDRP 라이팅 모델에 맞게 머티리얼 파라미터를 조정해야 합니다.

성능 고려사항

메모리 사용량

HDRP 깊이 피라미드는 URP 깊이 텍스처보다 약 33% 더 많은 메모리를 사용합니다. 또한 HDRP는 모션 벡터, 노말 버퍼 등 추가 버퍼를 필요로 합니다. 따라서 URP 대비 2-3배의 메모리 사용량을 계획하는 것이 좋습니다.

드로우 콜

HDRP 깊이 프리패스는 추가적인 드로우 콜을 발생시킵니다. 잘못된 인젝션 포인트의 커스텀 패스는 중복 작업을 유발할 수 있으므로, 가능한 한 커스텀 패스를 배치 처리하세요.

셰이더 베리언트

HDRP는 디퍼드, 포워드, 깊이, 모션 등 더 많은 베리언트를 생성합니다. 베리언트를 줄이려면 shader_feature_local을 사용하고, 프로덕션 빌드에서 사용하지 않는 패스를 제거하세요.

마이그레이션 체크리스트

마이그레이션 전

마이그레이션을 시작하기 전에 모든 셰이더와 머티리얼을 백업하세요. 사용된 커스텀 URP 기능을 문서화하고, 모든 깊이 텍스처 사용을 식별하며, 모든 포스트 프로세싱 이펙트를 나열해야 합니다.

셰이더 파일

pragma target을 최소 4.5로 업데이트하세요. CGPROGRAM을 HLSLPROGRAM으로 교체하고, 모든 include 경로를 업데이트합니다. 스테레오 렌더링 매크로를 추가하고, 깊이 접근 패턴을 변환하며, 필요한 HDRP 패스를 추가해야 합니다.

머티리얼

HDRP 셰이더로 머티리얼을 재생성하세요. Mask Map을 위해 텍스처를 리패킹하고, HDRP 범위에 맞게 파라미터를 조정해야 합니다.

포스트 프로세싱

CustomPostProcessVolumeComponent로 변환하세요. 셰이더를 Resources에 배치하고, HDRP Settings에 등록하며, 모든 인젝션 포인트를 테스트해야 합니다.

테스트

깊이 이펙트가 올바르게 작동하는지 확인하세요. VR/스테레오 렌더링을 확인하고, 성능 영향을 프로파일링하며, 타겟 플랫폼에서 검증해야 합니다.

플랫폼별 참고사항

콘솔 최적화

가능한 곳에 half precision을 사용하세요. 깊이 텍스처 읽기는 콘솔에서 비용이 높으므로 최소화해야 합니다. 비용이 높은 이펙트에는 낮은 해상도를 고려하세요.

모바일 폴백

HDRP는 모바일에서 지원되지 않습니다. 따라서 모바일 타겟을 위해 URP 버전을 유지하고, 조건부 컴파일을 위해 define 심볼을 사용하세요.

VR 고려사항

항상 _X 텍스처 매크로를 사용하세요. 모든 셰이더에 스테레오 설정을 포함하고, 싱글 패스와 멀티 패스 스테레오를 모두 테스트해야 합니다.

빠른 참조 카드

필수 HDRP 함수

// 깊이 접근
LoadCameraDepth(uint2 positionSS)
SampleCameraDepth(float2 uv)
LinearEyeDepth(depth, _ZBufferParams)
Linear01Depth(depth, _ZBufferParams)

// 위치 재구성
GetPositionInput(positionSS, invScreenSize, depth, invVP, viewMatrix)
GetAbsolutePositionWS(positionWS)

// 커스텀 패스 헬퍼
CustomPassLoadCameraColor(positionSS, lod)
CustomPassSampleCameraColor(uv, lod)
LoadCustomDepth(positionSS)
SampleCustomDepth(uv)

필수 Define

#pragma target 4.5
#pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch
UNITY_SETUP_INSTANCE_ID(input)
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output)
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)

패스 LightMode

HDRP에서 사용되는 주요 패스 LightMode는 다음과 같습니다. ForwardOnly는 투명 포워드 렌더링에 사용됩니다. DepthForwardOnly는 깊이 프리패스에 사용되며, ShadowCaster는 그림자 맵 생성에 사용됩니다. META 패스는 라이트맵 베이킹에 필요하고, SceneSelectionPass는 에디터 선택 기능에 사용됩니다. MotionVectors는 템포럴 이펙트를 위한 모션 벡터 생성에 사용됩니다.

결론

URP에서 HDRP로 마이그레이션하려면 특히 깊이 처리, include 구조, 렌더링 패스에서의 근본적인 아키텍처 차이를 이해해야 합니다. 가장 중요한 변경사항은 깊이 텍스처 접근입니다. 직접 텍스처 샘플링 대신 항상 LoadCameraDepth() 또는 SampleCameraDepth()를 사용하세요.

성공은 이 가이드의 패턴을 따르는 체계적인 마이그레이션, 타겟 플랫폼에서의 철저한 테스트, 그리고 HDRP의 복잡성이 메모리 사용량 증가와 플랫폼 호환성 감소라는 비용을 치르고 더 높은 비주얼 품질을 가능하게 한다는 이해에 달려 있습니다.

profile
테크아트 컨설팅 전문 회사 "메이즈라인" 입니다.

0개의 댓글