04. RenderGraph 핵심 개념과 API (URP 기준)
이 챕터의 목표는 “RenderGraph에서 패스를 어떻게 만들고, 텍스처/버퍼를 어떻게 주고받는가?”를 실전 코드 기준으로 익히는 것입니다.
4.1 RenderGraph의 핵심 철학: “의존성을 선언한다”
RenderGraph는 “명령을 바로 실행”하기보다, 패스 + 리소스 읽기/쓰기를 먼저 기록합니다.
- 패스 A가 텍스처 T를 쓴다(write)
- 패스 B가 텍스처 T를 읽는다(read)
이 선언으로 실행 순서가 결정되고, 필요하다면 리소스가 자동으로 생성/해제/재사용됩니다.
4.1.1 Record → Compile → Execute를 분리해서 이해하기
Unity 6 RenderGraph는 한 프레임을 아래 3단계로 다룹니다.
- Record: 패스와 리소스 읽기/쓰기 계약을 선언
- Compile: 그래프 전체 의존성을 보고 패스 culling/리소스 alias/배리어를 최적화
- Execute: 컴파일된 실행 계획을 GPU 명령으로 기록/실행
이 모델을 머리에 두면, "코드는 있는데 패스가 안 돈다" 같은 현상을 의존성/소비 경로 문제로 빠르게 좁힐 수 있습니다.
4.2 URP의 RecordRenderGraph 진입점
URP에서 RenderGraph 기반 커스텀 패스는 ScriptableRenderPass.RecordRenderGraph(RenderGraph, ContextContainer)를 오버라이드하여 작성합니다.
이때 frameData에는 카메라/리소스/렌더링 설정 등 URP 내부 데이터가 컨테이너 형태로 들어있습니다.
4.3 가장 흔한 패스 타입: Raster Pass
URP의 커스텀 패스는 대부분 “래스터 패스”입니다.
- 렌더 타겟(컬러/뎁스)을 설정하고
- 풀스크린 삼각형 또는 특정 렌더러를 그리거나
- 블릿(복사/필터)을 수행합니다.
4.4 예제: 카메라 컬러 텍스처에 풀스크린 머티리얼 적용
아래 코드는 “카메라 컬러”를 읽고, 같은 타겟에 결과를 쓰는 전형적 패턴을 보여줍니다. (정확한 타입/이름은 URP 패키지 버전에 따라 조금씩 다를 수 있습니다.)
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;
sealed class FullscreenMaterialPass : ScriptableRenderPass
{
public Material material;
struct PassData
{
public TextureHandle source;
public TextureHandle destination;
public Material material;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
// URP가 제공하는 리소스 데이터(예: 카메라 컬러)
var resources = frameData.Get<UniversalResourceData>();
// 원본(카메라 컬러)
var cameraColor = resources.activeColorTexture;
// 목적지(임시 텍스처를 만들어서 쓴 뒤, URP 리소스를 교체하는 패턴이 안전함)
// 여기서는 개념만: 실제로는 RenderGraphTextureDesc 설정 필요
var desc = renderGraph.GetTextureDesc(cameraColor);
desc.name = "FullscreenMaterial_Temp";
var temp = renderGraph.CreateTexture(desc);
using (var builder = renderGraph.AddRasterRenderPass<PassData>("FullscreenMaterial", out var passData))
{
passData.source = cameraColor;
passData.destination = temp;
passData.material = material;
builder.UseTexture(passData.source, AccessFlags.Read);
builder.SetRenderAttachment(passData.destination, 0, AccessFlags.Write);
builder.SetRenderFunc(static (PassData data, RasterGraphContext ctx) =>
{
// URP 제공 유틸(버전에 따라 Blitter/RenderingUtils 등을 사용)
Blitter.BlitTexture(ctx.cmd, data.source, new Vector4(1, 1, 0, 0), data.material, 0);
});
}
// URP의 “activeColorTexture”를 temp로 갱신하는 패턴(버전에 따라 API가 다름)
resources.activeColorTexture = temp;
}
}
핵심 포인트
- 읽는 텍스처는
UseTexture(..., Read)로 선언 - 쓰는 렌더 타겟은
SetRenderAttachment(..., Write)로 선언 - 결과를 URP 파이프라인에서 이어 쓰려면 “어떤 리소스를 후속 패스가 보게 할지”를 갱신해야 합니다.
4.4.1 SetRenderFunc는 static lambda를 기본값으로
SetRenderFunc에서 외부 변수를 캡처하면 프레임마다 closure 할당이 생길 수 있습니다.
가능하면 passData에 필요한 값을 모두 채우고, static 람다로 실행 코드를 고정하는 패턴이 안전합니다.
4.5 ImportTexture: 외부(RenderTexture/RTHandle) 자원을 그래프에 연결
RenderGraph는 원칙적으로 그래프가 생성한 리소스를 관리합니다. 하지만 이미 존재하는 RTHandle 또는 외부 텍스처를 그래프에 “가져와야” 할 때가 많습니다.
URP 문서에는 renderGraph.ImportTexture(rtHandle)로 RTHandle을 TextureHandle로 가져오는 예제가 있습니다.
4.6 “Pass는 함수 호출이 아니라 계약(Contract)”이다: 읽기/쓰기 선언 규칙
RenderGraph로 넘어오면서 가장 큰 사고방식 변화는 이겁니다.
- Compatibility Mode: “내가 cmd에 뭘 기록했는지”가 전부
- RenderGraph: “내 패스가 어떤 리소스를 읽고/쓰는지”를 선언해야 그래프가 올바르게 구성됨
4.6.1 자주 틀리는 선언
- 텍스처를 샘플링하면서
UseTexture(Read)를 선언하지 않음 - 뎁스 텍스처를 쓰는데 depth attachment를 설정하지 않음
- 같은 텍스처를 source/destination으로 동시에 지정(특히 Blit)
RenderGraph는 의존성/배리어/수명 관리를 “선언”을 기반으로 하기 때문에, 선언이 틀리면 검은 화면/0 샘플링/플리커/플랫폼별 차이 같은 문제가 발생합니다.
4.7 Frame Data 접근: UniversalCameraData / UniversalResourceData
URP RenderGraph 경로에서 “카메라 관련 데이터”와 “리소스(텍스처) 데이터”는 Frame Data로 제공합니다.
핵심 타입(URP 문서 기준):
UniversalCameraData: 카메라 정보(뷰/프로젝션, 카메라 타입 등)UniversalResourceData: 카메라 컬러/뎁스/히스토리 등 텍스처 리소스
ContextContainer에서 frameData.Get<T>()로 접근하는 패턴은 모든 커스텀 패스의 기본입니다.
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var cameraData = frameData.Get<UniversalCameraData>();
var resources = frameData.Get<UniversalResourceData>();
// 예시(정확한 필드명은 URP 버전에 따라 다를 수 있음)
var color = resources.activeColorTexture;
}
관련 공식 문서:
4.8 History Render Textures: 시간(Temporal) 기반 효과의 기반
TAA, 모션 블러, 시간 누적형 업스케일러 같은 효과는 “이전 프레임”의 텍스처가 필요합니다.
URP는 RenderGraph 경로에서 히스토리 텍스처를 다루는 문서를 제공합니다.
- 핵심 포인트: 히스토리 텍스처는 “카메라별”이며, 리셋 조건(카메라 컷/해상도 변경 등)을 고려해야 합니다.
관련 공식 문서:
- History Render Textures: https://docs.unity3d.com/Manual/urp/render-graph-history-render-textures.html
4.9 Blit/Copy는 RenderGraph에서 “명시적으로” 다뤄라
URP 문서는 RenderGraph에서 Blit을 수행하는 방법과, 효과적으로 최적화하는 방법(불필요한 복사 방지)을 별도로 설명합니다.
- 원칙: “정말 필요할 때만 Blit”
- 이유: Copy/Blit는 대역폭을 먹고, 모바일/타일 기반 GPU에서 특히 비쌉니다.
관련 공식 문서:
- RenderGraph에서 Blit: https://docs.unity3d.com/Manual/urp/render-graph-blit.html
- Blit 최적화: https://docs.unity3d.com/Manual/urp/blit-best-practices.html
4.10 Pass Culling: “내 패스가 사라지는” 대표 원인과 대응
RenderGraph의 정상 동작 중 하나가 pass culling입니다. 출력이 최종 결과에 기여하지 않으면 패스가 제거됩니다.
대표 원인:
- 출력 텍스처를 아무도 읽지 않음
- 카메라 컬러를 교체했지만 이후에 교체본을 소비하지 않음
- 전역 바인딩 부작용만 있고 그래프상 소비 경로가 없음
대응 체크리스트:
- 출력 핸들이 다음 패스 입력으로 연결되는지 확인
- 디버그 패스라도 소비 패스를 붙여 일시적으로 생존성 검증
- RenderGraph Viewer에서 패스 존재 여부와 resource lifetime을 함께 확인
4.11 Legacy Execute → RecordRenderGraph 이전 체크리스트
이전할 때 아래 질문 5개를 먼저 고정하면 실패 확률이 줄어듭니다.
- 입력 리소스는 무엇인가? (
Color/Depth/Normals/MotionVectors/History) - 출력 리소스는 무엇인가? (카메라 컬러 교체/별도 마스크/버퍼)
- 부작용이 있는가? (
SetGlobalTexture, 외부 버퍼 쓰기 등) - 조건부 실행 규칙이 있는가? (카메라 타입/플랫폼/품질 등급)
- 다른 패스와 동기화가 필요한가? (Compute 결과를 Raster에서 읽는 흐름)
하지 말아야 할 것:
RecordRenderGraph단계에서 즉시 GPU 호출 시도GetTemporaryRT/ReleaseTemporaryRT패턴을 그대로 가져오기- 전역 상태를 카메라 스코프 없이 공유하기
4.12 샘플 기반 보강 포인트
이 장은 아래 샘플 문서의 실무 포인트를 반영해 보강했습니다.
samples/Unity6_Rendering_Bible/04_Render_Graph_System/01_RenderGraph_Basics.mdsamples/Unity6_Rendering_Bible/04_Render_Graph_System/02_Writing_Custom_Pass.mdsamples/Unity6_Rendering_Bible/04_Render_Graph_System/03_RecordRenderGraph_Migration_Checklist.mdsamples/Unity6_Rendering_Bible/04_Render_Graph_System/04_RenderGraph_Debugging_and_Culling.md
추가 읽을거리(공식/권위 자료)
- RenderGraph 패스 작성(개요): https://docs.unity3d.com/Manual/urp/render-graph-write-render-pass.html
- RenderGraph ImportTexture: https://docs.unity3d.com/Manual/urp/render-graph-import-texture.html