03. URP 아키텍처: Asset / Renderer / Feature / Pass
이 챕터는 URP에서 “어디에 코드를 넣어야 확장되는가?”를 구조적으로 정리합니다. Render Feature/Pass를 작성할 때 삽입 지점(Injection Point) 을 결정하는 감각이 목표입니다.
3.1 URP의 구성요소
UniversalRenderPipelineAsset(URP 전역 설정)ScriptableRenderer(카메라 렌더링을 실제로 구성/실행)ScriptableRendererFeature(사용자가 렌더러에 기능을 “장착”하는 확장 포인트)ScriptableRenderPass(한 번의 렌더 패스 단위)
일반적으로 개발자는 RendererFeature를 만들어 렌더러에 붙이고, 그 안에서 패스를 생성해 렌더링 파이프라인에 삽입합니다.
3.1.1 (중요) Feature의 생명주기: Create / AddRenderPasses / SetupRenderPasses / Dispose
URP는 ScriptableRendererFeature에 대해 대략 다음 순서로 호출합니다.
Create()- Feature가 로드되거나, 활성/비활성 토글되거나, 인스펙터 값이 바뀌는 등 “구성이 바뀌는 시점”에 호출
- 리소스(머티리얼/패스 인스턴스/버퍼 등) 생성은 여기서 하는 것이 원칙
AddRenderPasses(renderer, ref RenderingData)- 매 프레임, 카메라마다 1회 호출
renderer.EnqueuePass(pass)로 패스를 파이프라인에 “등록”- 주의: 이 함수는 호출 빈도가 매우 높으므로 할당/생성 작업을 하면 안 됩니다.
SetupRenderPasses(renderer, in RenderingData)- 카메라 타겟(컬러/뎁스)을 포함해, 패스 실행 전에 필요한 값을 “세팅”하는 단계
- Unity 문서에서도
cameraColorTarget같은 카메라 타겟 접근이 필요할 경우AddRenderPasses가 아니라SetupRenderPasses에서 하도록 가이드합니다.
Dispose(bool)- Feature가 소멸될 때 리소스 해제
실전 규칙
- Create: “생성/초기화(한 번)”
- AddRenderPasses: “어떤 카메라에 적용할지 판단 + Enqueue”
- SetupRenderPasses: “카메라 타겟/프레임별 파라미터 세팅”
- Dispose: “정리”
관련 공식 문서:
- Scriptable Renderer Feature 워크플로(커스텀 패스/주입 흐름): https://docs.unity3d.com/kr/6000.3/Manual/urp/inject-a-render-pass.html
- Unity 6(URP 17) 업그레이드 가이드(카메라 타겟 접근 위치 변경): https://docs.unity3d.com/jp/current/Manual/urp/upgrade-guide-unity-6.html
3.2 Pass는 어디에 끼우나? (RenderPassEvent)
URP는 패스가 실행될 타이밍을 이벤트 열거형으로 표현합니다(예: Opaque 전/후, Transparent 전/후, 포스트 전/후 등).
실무에서 가장 흔한 타이밍:
- “Opaque 이후, Transparent 이전”: 화면 기반 이펙트/깊이 기반 효과
- “포스트 직전/직후”: 컬러 그레이딩/블러/아웃라인 등
RenderGraph 경로에서는 이벤트만으로는 부족할 수 있으며, 실제 의존성은 “어떤 리소스를 읽고/쓰는지”가 결정합니다. (RenderGraph는 04. RenderGraph)
3.2.1 Injection Point를 고르는 실전 기준
Renderer Feature를 설계할 때, Injection Point(=패스 실행 타이밍)는 다음 질문으로 결정하면 실수가 줄어듭니다.
- 입력 텍스처는 무엇인가?
- Opaque만 반영된 컬러? Transparent까지 포함된 컬러?
- Depth/Normals가 필요한가?
- 출력은 어디로 갈 것인가?
- 카메라 컬러에 덮어쓰기?
- 임시 RT에 쓰고 이후에 합성?
- 후속 패스가 무엇을 기대하는가?
- 포스트프로세싱 전에 적용해야 하는가? 이후에 적용해야 하는가?
- XR/동적 해상도/다중 카메라에서 안전한가?
- 카메라마다 서로 다른 타겟/스케일을 쓰는지
가장 흔한 실수는 “이벤트만 보고” 타이밍을 잡는 것입니다.
RenderGraph에서는 이벤트보다 리소스 의존성(읽기/쓰기 선언) 이 실제 순서를 더 강하게 결정합니다.
3.2.2 Unity 6 프레임 라이프사이클로 주입 시점 다시 보기
RenderPassEvent는 "언제 실행할지"를 정하는 힌트이고, 실제로는 프레임 단계와 입력 리소스 준비 상태를 같이 봐야 안전합니다.
| 프레임 단계 | 일반 목적 | 추천 이벤트(예시) | 흔한 실수 |
|---|---|---|---|
| Setup/Culling 이후 | 프레임 입력 준비 확인 | BeforeRenderingPrePasses 계열 |
Depth/Normals가 없는 카메라에서 강행 |
| Depth Prepass 이후 | 깊이 기반 효과 | AfterRenderingPrePasses |
Raw depth 선형화 누락 |
| Opaque 이후 | 실루엣/마스크/왜곡 준비 | AfterRenderingOpaques |
이후 소비 패스 없이 출력만 생성 |
| Transparent 이후 | 화면 왜곡/합성 | AfterRenderingTransparents |
투명 정렬/소팅 가정 누락 |
| Post 전/후 | 최종 톤/화면 덮기 | Before/AfterRenderingPostProcessing |
UI/Overlay 카메라 영향 범위 미정의 |
3.3 Compatibility Mode vs RenderGraph
URP 커스텀 패스 작성에는 두 길이 있습니다.
- Compatibility Mode:
Execute()에서CommandBuffer로 직접 그리기(레거시 접근) - RenderGraph 경로:
RecordRenderGraph()에서 패스와 리소스 의존성을 “선언”하기(현행/권장)
URP 문서에서도 RenderGraph 기반 패스 작성이 중심이며, 비-RenderGraph 경로는 더 이상 적극 개발되지 않는다는 점을 강조합니다.
3.4 최소 동작 예제(스켈레톤)
아래는 구조만 보여주는 스켈레톤입니다. 실제 RenderGraph 패스 예제는 04. RenderGraph에서 다룹니다.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public sealed class ExampleFeature : ScriptableRendererFeature
{
sealed class ExamplePass : ScriptableRenderPass
{
// RenderGraph 경로(권장)
public override void RecordRenderGraph(UnityEngine.Rendering.RenderGraphModule.RenderGraph renderGraph,
ContextContainer frameData)
{
// TODO: renderGraph.AddRasterRenderPass(...) 등으로 패스 선언
}
// Compatibility Mode 경로(레거시)
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// TODO: CommandBufferPool.Get/Release, Blit, DrawRenderer 등
}
}
ExamplePass _pass;
public override void Create()
{
_pass = new ExamplePass();
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(_pass);
}
}
3.4.1 카메라 타입별 적용(실무 필수)
URP는 씬뷰/게임뷰/프리뷰/리플렉션 등 여러 카메라 타입을 렌더링합니다. 실무에서는 대개 “게임 카메라에만” 적용하거나, “씬뷰는 디버그용으로만” 적용합니다.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (renderingData.cameraData.cameraType != CameraType.Game)
return;
renderer.EnqueuePass(_pass);
}
3.5 Pass 구조를 ‘텍스처 요구사항’으로 설계하기
URP 패스는 “이 패스가 어떤 텍스처/입력이 필요하다”를 선언해야 합니다.
- Depth가 필요하면 DepthTexture 생성이 필요할 수 있음
- Normal이 필요하면 DepthNormals 패스가 선행되어야 할 수 있음
- Motion Vector/History가 필요하면 별도 요구가 생김
RenderGraph 경로에서는 이런 요구가 UniversalResourceData/Frame Data 형태로 구성되며, Full Screen Pass 같은 Feature에서는 “Requirements”로 이를 지정합니다.
관련 문서:
- Full Screen Pass Renderer Feature(Requirements 개념): https://docs.unity3d.com/Manual/urp/renderer-features/renderer-feature-full-screen-pass.html
3.6 RenderGraph 시대 Renderer Feature 설계 체크리스트
3.6.1 리소스 준비/수명
- Depth/Normals/MotionVectors가 모든 카메라에서 항상 준비된다고 가정하지 말 것
- 텍스처 포맷(sRGB/Linear), Render Scale, DRS를 입력 계약에 포함할 것
- 외부 RT를 가져오면(
ImportTexture) 생명주체(그래프/외부) 경계를 문서화할 것
3.6.2 Pass Culling 생존성
RenderGraph는 "출력이 최종 결과로 이어지지 않으면" 패스를 제거할 수 있습니다.
- 출력 핸들이 후속 패스에서 실제로 읽히는지 확인
- 디버그 패스라도 부작용(전역 바인딩 등)에만 의존하지 않도록 설계
- 마스크 생성 패스는 바로 다음 합성 패스와 묶어 검증
3.6.3 카메라 스택/멀티 카메라 안정성
- 전역 텍스처 키(
SetGlobalTexture)는 멀티 카메라 충돌 위험이 큼 - Base/Overlay 카메라 경로를 분리해 적용 조건을 명시
- 입력이 없는 카메라(예: Depth 미생성)에서는 early-out으로 안전 종료
3.7 샘플 기반 참고 노트
아래 샘플 문서의 개념을 이 장의 설계 기준에 반영했습니다.
samples/Unity6_Rendering_Bible/01_Architecture_and_Pipeline/02_URP_Lifecycle.mdsamples/Unity6_Rendering_Bible/01_Architecture_and_Pipeline/04_URP_Renderer_Features_Design.mdsamples/Unity6_Rendering_Bible/04_Render_Graph_System/03_RecordRenderGraph_Migration_Checklist.md
추가 읽을거리(공식/권위 자료)
- URP RenderGraph 패스 작성(Compatibility Mode): https://docs.unity3d.com/Manual/urp/render-graph-write-render-pass-compatibility.html
- Scriptable Renderer Feature로 패스 주입하기(튜토리얼): https://docs.unity3d.com/kr/6000.3/Manual/urp/inject-a-render-pass.html