05. 텍스처 자원과 “ID” 구조: RenderTexture / RTHandle / TextureHandle
이 챕터는 “렌더 텍스처를 주고받는 방식”과 흔히 말하는 “레퍼런스 ID”가 정확히 무엇인지 정리합니다.
선행: 04. RenderGraph
5.1 용어 정리
RenderTexture: Unity 엔진 레벨의 텍스처 리소스(렌더 타겟 가능)RTHandle: 동적 해상도/스케일을 고려한 렌더 타겟 핸들(URP/HDRP 공용)TextureHandle: RenderGraph가 리소스를 추적하기 위한 핸들(그래프 내부 표현)- “Shader Property ID”:
Shader.PropertyToID("_MyTex")같은 전역 프로퍼티 슬롯 식별자
“ID”가 섞여서 혼란이 생기는 이유
- 전역 슬롯 ID(정수): 셰이더 전역 텍스처/상수 설정 시 키로 사용
- 리소스 핸들(RTHandle/TextureHandle): 실제 GPU 리소스(또는 그 래핑)
- RenderTexture instance: C# 객체 레퍼런스
실무에서는 이 3개를 상황에 맞게 연결해야 합니다.
5.2 전역 텍스처 슬롯: Shader.PropertyToID
가장 오래된(그리고 여전히 흔한) 패턴은 다음입니다.
_MyColorTex같은 이름에 대해int id = Shader.PropertyToID("_MyColorTex")cmd.SetGlobalTexture(id, rt)혹은cmd.SetGlobalTexture("_MyColorTex", rt)
여기서 id는 “이름을 정수로 해시한 슬롯 키” 일 뿐이며, 텍스처 자체가 아닙니다.
5.2.1 “ID 슬롯”을 쓰는 이유와 함정
- 이유: 문자열 비교 비용을 줄이고, 프로퍼티 접근을 빠르게 함
- 함정: “ID가 곧 리소스”라고 착각하면 설계가 꼬입니다.
ID는 단지 (이름 → 슬롯) 변환 결과입니다.
- 리소스는
RenderTexture/RTHandle/TextureHandle - 슬롯은
int또는string
이고, 둘 사이를 cmd.SetGlobalTexture 같은 호출이 연결합니다.
5.3 RenderTexture를 주고받는 기본 패턴(Compatibility Mode)
가장 단순한 방식은:
- 임시 렌더 타겟을 만든다(
GetTemporaryRT또는 RTHandle 할당) - 어떤 패스에서 렌더한다(Blit/Draw)
- 전역 슬롯에 바인딩한다(
SetGlobalTexture) - 다른 패스/셰이더에서 샘플링한다
static readonly int MyTexId = Shader.PropertyToID("_MyTex");
var cmd = CommandBufferPool.Get("Example");
try
{
cmd.GetTemporaryRT(MyTexId, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBHalf);
cmd.SetRenderTarget(MyTexId);
// ... draw ...
cmd.SetGlobalTexture(MyTexId, MyTexId);
context.ExecuteCommandBuffer(cmd);
}
finally
{
cmd.ReleaseTemporaryRT(MyTexId);
CommandBufferPool.Release(cmd);
}
위 예제는 “개념” 설명용입니다. 실무에서는 RTHandle/RenderGraph로 넘어가는 것이 권장됩니다.
5.3 RTHandle: 왜 필요한가?
동적 해상도(Scale, XR, 카메라별 다른 크기)를 다루다 보면 RenderTexture를 직접 들고 다니는 방식이 누수/재할당 문제를 만들기 쉽습니다.
RTHandle은:
- 스케일/동적 해상도 대응
- 프레임 간 재사용과 수명 관리
- URP/HDRP 공용 유틸과 함께 사용
을 위해 도입된 “핸들 시스템”입니다.
5.3.1 RTHandle과 해상도 스케일(동적 해상도)
Forward+/포스트/업스케일링을 하다 보면 카메라 타겟이:
- 화면 크기와 다를 수 있고
- 렌더 스케일(Render Scale)을 적용받고
- XR에서 눈별로 다를 수 있습니다.
이때 RenderTexture를 “정확한 픽셀 크기”로 직접 생성/관리하면 매우 쉽게 오류가 납니다.
RTHandle은 이런 경우를 위해 “레퍼런스(핸들) + 실제 텍스처(백킹)”를 분리해 관리하는 방식입니다.
5.4 TextureHandle: RenderGraph 내부 “리소스 레퍼런스”
RenderGraph는 내부적으로 텍스처 리소스를 TextureHandle로 참조합니다.
- 그래프가 생성한 텍스처는
CreateTexture()로 얻은 handle로 참조 - 외부 RTHandle/Texture는
ImportTexture()로 handle로 가져와 참조
핵심은 “리소스 의존성(읽기/쓰기)”를 handle 단위로 선언한다는 점입니다.
5.5 RenderGraph에서 텍스처를 주고받는 기본 패턴(권장)
RenderGraph에서는 보통 다음 중 하나로 흐릅니다.
- URP가 제공하는 카메라 텍스처를 읽는다 (
UniversalResourceData.activeColorTexture등) - 그래프 내부에서 임시 텍스처를 만든다 (
CreateTexture) - 패스에서 읽고/쓰기를 선언한다 (
UseTexture/SetRenderAttachment) - 후속 패스가 참조할 리소스를 갱신한다 (예: activeColorTexture를 temp로 교체)
중요한 차이:
- Compatibility Mode는 “전역 슬롯”이 흐름의 중심이 되기 쉽고
- RenderGraph는 “핸들(의존성)”이 흐름의 중심이 됩니다.
5.5 주고받는 방식 패턴 3종(정리)
-
(레거시) CommandBuffer + GlobalTexture 슬롯
- 장점: 간단, 많은 예제가 있음
- 단점: 리소스 수명/의존성이 코드에 흩어짐(디버깅/최적화 어려움)
-
RTHandle 기반(URP 내부 스타일)
- 렌더러/패스가 RTHandle을 중심으로 리소스를 유지
-
RenderGraph(TextureHandle) 기반(현행/권장)
- 패스가 handle을 통해 읽기/쓰기 선언
- 수명/재사용은 그래프가 도와줌
5.6 “카메라 컬러/뎁스 텍스처는 어디서 오나?”를 답하는 방법
RenderGraph 경로에서 가장 먼저 할 일:
- Frame Data 문서를 읽고(개념),
- RenderGraph Viewer로 실제 그래프를 보고(현상),
- URP 설정(Requirements/Depth Texture/Normal Texture) 을 바꿔보는 것(원인).
관련 공식 문서:
- Frame Data 접근: https://docs.unity3d.com/kr/6000.3/Manual/urp/accessing-frame-data.html
- RenderGraph Viewer: https://docs.unity3d.com/Manual/urp/render-graph-view.html
추가 읽을거리(공식/권위 자료)
- Shader.PropertyToID: https://docs.unity3d.com/ScriptReference/Shader.PropertyToID.html
- RTHandle: https://docs.unity3d.com/Manual/urp/render-graph-rt-handle.html