book/04-render-graph-fundamentals.md

04. RenderGraph 핵심 개념과 API (URP 기준)

RenderGraph 패스/리소스 의존성 선언, Frame Data 접근, Import/History 텍스처, Blit/최적화까지 URP 기준으로 정리한다

04. RenderGraph 핵심 개념과 API (URP 기준)

이 챕터의 목표는 “RenderGraph에서 패스를 어떻게 만들고, 텍스처/버퍼를 어떻게 주고받는가?”를 실전 코드 기준으로 익히는 것입니다.

선행: 02. 프레임 루프/데이터 흐름, 03. URP 아키텍처

4.1 RenderGraph의 핵심 철학: “의존성을 선언한다”

RenderGraph는 “명령을 바로 실행”하기보다, 패스 + 리소스 읽기/쓰기를 먼저 기록합니다.

  • 패스 A가 텍스처 T를 쓴다(write)
  • 패스 B가 텍스처 T를 읽는다(read)

이 선언으로 실행 순서가 결정되고, 필요하다면 리소스가 자동으로 생성/해제/재사용됩니다.

4.1.1 Record → Compile → Execute를 분리해서 이해하기

Unity 6 RenderGraph는 한 프레임을 아래 3단계로 다룹니다.

  1. Record: 패스와 리소스 읽기/쓰기 계약을 선언
  2. Compile: 그래프 전체 의존성을 보고 패스 culling/리소스 alias/배리어를 최적화
  3. Execute: 컴파일된 실행 계획을 GPU 명령으로 기록/실행

이 모델을 머리에 두면, "코드는 있는데 패스가 안 돈다" 같은 현상을 의존성/소비 경로 문제로 빠르게 좁힐 수 있습니다.

4.2 URP의 RecordRenderGraph 진입점

URP에서 RenderGraph 기반 커스텀 패스는 ScriptableRenderPass.RecordRenderGraph(RenderGraph, ContextContainer)를 오버라이드하여 작성합니다.

이때 frameData에는 카메라/리소스/렌더링 설정 등 URP 내부 데이터가 컨테이너 형태로 들어있습니다.

4.3 가장 흔한 패스 타입: Raster Pass

URP의 커스텀 패스는 대부분 “래스터 패스”입니다.

  • 렌더 타겟(컬러/뎁스)을 설정하고
  • 풀스크린 삼각형 또는 특정 렌더러를 그리거나
  • 블릿(복사/필터)을 수행합니다.

4.4 예제: 카메라 컬러 텍스처에 풀스크린 머티리얼 적용

아래 코드는 “카메라 컬러”를 읽고, 같은 타겟에 결과를 쓰는 전형적 패턴을 보여줍니다. (정확한 타입/이름은 URP 패키지 버전에 따라 조금씩 다를 수 있습니다.)

C#
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>()로 접근하는 패턴은 모든 커스텀 패스의 기본입니다.

C#
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 경로에서 히스토리 텍스처를 다루는 문서를 제공합니다.

  • 핵심 포인트: 히스토리 텍스처는 “카메라별”이며, 리셋 조건(카메라 컷/해상도 변경 등)을 고려해야 합니다.

관련 공식 문서:

4.9 Blit/Copy는 RenderGraph에서 “명시적으로” 다뤄라

URP 문서는 RenderGraph에서 Blit을 수행하는 방법과, 효과적으로 최적화하는 방법(불필요한 복사 방지)을 별도로 설명합니다.

  • 원칙: “정말 필요할 때만 Blit”
  • 이유: Copy/Blit는 대역폭을 먹고, 모바일/타일 기반 GPU에서 특히 비쌉니다.

관련 공식 문서:

4.10 Pass Culling: “내 패스가 사라지는” 대표 원인과 대응

RenderGraph의 정상 동작 중 하나가 pass culling입니다. 출력이 최종 결과에 기여하지 않으면 패스가 제거됩니다.

대표 원인:

  • 출력 텍스처를 아무도 읽지 않음
  • 카메라 컬러를 교체했지만 이후에 교체본을 소비하지 않음
  • 전역 바인딩 부작용만 있고 그래프상 소비 경로가 없음

대응 체크리스트:

  • 출력 핸들이 다음 패스 입력으로 연결되는지 확인
  • 디버그 패스라도 소비 패스를 붙여 일시적으로 생존성 검증
  • RenderGraph Viewer에서 패스 존재 여부와 resource lifetime을 함께 확인

4.11 Legacy ExecuteRecordRenderGraph 이전 체크리스트

이전할 때 아래 질문 5개를 먼저 고정하면 실패 확률이 줄어듭니다.

  1. 입력 리소스는 무엇인가? (Color/Depth/Normals/MotionVectors/History)
  2. 출력 리소스는 무엇인가? (카메라 컬러 교체/별도 마스크/버퍼)
  3. 부작용이 있는가? (SetGlobalTexture, 외부 버퍼 쓰기 등)
  4. 조건부 실행 규칙이 있는가? (카메라 타입/플랫폼/품질 등급)
  5. 다른 패스와 동기화가 필요한가? (Compute 결과를 Raster에서 읽는 흐름)

하지 말아야 할 것:

  • RecordRenderGraph 단계에서 즉시 GPU 호출 시도
  • GetTemporaryRT/ReleaseTemporaryRT 패턴을 그대로 가져오기
  • 전역 상태를 카메라 스코프 없이 공유하기

4.12 샘플 기반 보강 포인트

이 장은 아래 샘플 문서의 실무 포인트를 반영해 보강했습니다.

  • samples/Unity6_Rendering_Bible/04_Render_Graph_System/01_RenderGraph_Basics.md
  • samples/Unity6_Rendering_Bible/04_Render_Graph_System/02_Writing_Custom_Pass.md
  • samples/Unity6_Rendering_Bible/04_Render_Graph_System/03_RecordRenderGraph_Migration_Checklist.md
  • samples/Unity6_Rendering_Bible/04_Render_Graph_System/04_RenderGraph_Debugging_and_Culling.md

추가 읽을거리(공식/권위 자료)