URP 에서만 동작하는 셰이더를 HDRP 로 전환 해야할 일이 생겼습니다. 몇 가지 부분을 기록하고자 합니다. 50살을 넘어간 후부터는 기억력이 정말 하루 하루 …
URP에서 HDRP로 셰이더를 포팅하는 작업은 단순한 문법 수정이 아닌 근본적인 아키텍처 변경을 필요로 합니다. 가장 중요한 차이점은 깊이 텍스처 접근 방식(LoadCameraDepth vs SAMPLE_DEPTH_TEXTURE), include 파일 경로, 셰이더 패스 구조, 그리고 매크로 정의입니다. 이 가이드에서는 각 셰이더 타입별로 작동하는 예제와 함께 체계적인 마이그레이션 패턴을 제공합니다.
URP는 모바일 및 중급 사양에 최적화된 렌더러입니다. 반면 HDRP는 하이엔드 플랫폼을 위한 렌더러이며 기본적으로 레이트레이싱을 지원하고 있습니다.
URP는 _CameraDepthTexture를 사용한 직접 텍스처 샘플링 방식을 사용합니다. 이와 달리 HDRP는 LoadCameraDepth() 또는 SampleCameraDepth() 함수를 필요로 하는 계층적 깊이 피라미드 구조를 채택하고 있습니다. HDRP에서는 깊이 텍스처를 절대 직접 샘플링하지 마세요.
// 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"
| URP Include | HDRP 대응 | 목적 |
|---|---|---|
| Core.hlsl | Common.hlsl + ShaderVariables.hlsl | 기본 타입 및 행렬 |
| Lighting.hlsl | Lighting.hlsl (HDRP 버전) | 라이팅 계산 |
| DeclareDepthTexture.hlsl | ShaderVariables.hlsl | 깊이 텍스처 접근 |
| DeclareNormalsTexture.hlsl | NormalBuffer.hlsl | 노말 버퍼 접근 |
| ShaderVariablesFunctions.hlsl | ShaderVariablesFunctions.hlsl (HDRP) | 헬퍼 함수 |
| Shadows.hlsl | LightLoop/Shadow.hlsl | 그림자 샘플링 |
| BRDF.hlsl | BSDF.hlsl | 머티리얼 응답 함수 |
// 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 깊이 접근 - 텍스처 선언 불필요
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);
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
}
}
}
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
}
}
}
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)
{
// 렌더링 로직
}
}
}
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);
}
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);
}
}
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 메인 라이트
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는 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는 Base Map, Normal Map, Metallic/Smoothness, Occlusion, Emission 프로퍼티를 제공합니다.
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와 다르므로 주의가 필요합니다.
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);
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
HDRP에서 Alpha Clipping을 사용하는 경우, 모든 패스에서 동일하게 클리핑을 적용해야 합니다. 특히 DepthForwardOnly와 ShadowCaster 패스에서 클리핑을 누락하면 그림자와 깊이가 불일치하여 시각적 아티팩트가 발생합니다. 이 부분은 URP 도 동일하게 주의 해야 합니다.
// Fragment 함수에서 클리핑
void frag(Varyings input)
{
float alpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).a;
clip(alpha - _Cutoff); // 모든 패스에 필수
}
HDRP는 URP와 다른 Normal 인코딩 방식을 사용합니다. Normal Buffer를 직접 읽는 경우, NormalData.hlsl의 DecodeFromNormalBuffer() 함수를 사용해야 합니다. 직접 디코딩하면 잘못된 결과가 나올 수 있습니다.
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"
// 올바른 Normal 읽기
NormalData normalData;
DecodeFromNormalBuffer(positionSS, normalData);
float3 worldNormal = normalData.normalWS;
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
HDRP는 항상 Linear Color Space에서 작동합니다. 텍스처를 sRGB로 임포트했는지 확인하고, 셰이더에서 색상 계산 시 Gamma 변환이 필요한지 검토하세요. 특히 UI 텍스처나 라이트맵을 다룰 때 주의가 필요합니다.
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
증상: "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"
증상: 깊이 기반 이펙트에서 아티팩트나 잘못된 거리가 표시됩니다.
해결책: HDRP 깊이 함수를 사용하세요.
// 잘못된 방법 - URP 패턴
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, uv);
// 올바른 방법 - HDRP 패턴
float depth = LoadCameraDepth(positionSS);
증상: 셰이더가 검은색으로 렌더링되거나 보이지 않습니다.
해결책: 필요한 HDRP 패스를 추가하세요. 깊이 프리패스를 위한 DepthForwardOnly 패스, 그림자를 위한 ShadowCaster 패스, 라이트맵 베이킹을 위한 META 패스를 추가해야 합니다.
증상: 커스텀 포스트 프로세스가 나타나지 않습니다.
해결책: 먼저 셰이더를 Resources 폴더에 배치하세요. 그런 다음 HDRP Settings의 Custom Post Process Orders에 추가합니다. 마지막으로 Volume에 컴포넌트가 활성화되어 있는지 확인하세요.
증상: 동일한 텍스처가 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 심볼을 사용하세요.
항상 _X 텍스처 매크로를 사용하세요. 모든 셰이더에 스테레오 설정을 포함하고, 싱글 패스와 멀티 패스 스테레오를 모두 테스트해야 합니다.
// 깊이 접근
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)
#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)
HDRP에서 사용되는 주요 패스 LightMode는 다음과 같습니다. ForwardOnly는 투명 포워드 렌더링에 사용됩니다. DepthForwardOnly는 깊이 프리패스에 사용되며, ShadowCaster는 그림자 맵 생성에 사용됩니다. META 패스는 라이트맵 베이킹에 필요하고, SceneSelectionPass는 에디터 선택 기능에 사용됩니다. MotionVectors는 템포럴 이펙트를 위한 모션 벡터 생성에 사용됩니다.
URP에서 HDRP로 마이그레이션하려면 특히 깊이 처리, include 구조, 렌더링 패스에서의 근본적인 아키텍처 차이를 이해해야 합니다. 가장 중요한 변경사항은 깊이 텍스처 접근입니다. 직접 텍스처 샘플링 대신 항상 LoadCameraDepth() 또는 SampleCameraDepth()를 사용하세요.
성공은 이 가이드의 패턴을 따르는 체계적인 마이그레이션, 타겟 플랫폼에서의 철저한 테스트, 그리고 HDRP의 복잡성이 메모리 사용량 증가와 플랫폼 호환성 감소라는 비용을 치르고 더 높은 비주얼 품질을 가능하게 한다는 이해에 달려 있습니다.