

개요
- Unity6 RenderGraph 호환하기 위한 ScriptableRenderPass 개발 간단 예제입니다.
- RenderGraph 미지원 & RenderGraph Unsafe & RasterPass + Framebufferfetch 기반으로 간단한 흑백 화면 효과 구현합니다.
- Unity 6000.1.12f1 버전 기준으로 개발되었습니다.
- 예제 코드에 대한 상세한 설명 대신, RenderGraph 호환 개발에 대한 핵심 포인트만 설명합니다.
샘플 코드
https://github.com/CatDarkGame/UnityRenderGraphSample
GitHub - CatDarkGame/UnityRenderGraphSample: Unity 6000.1.12f1
Unity 6000.1.12f1 . Contribute to CatDarkGame/UnityRenderGraphSample development by creating an account on GitHub.
github.com
RenderGraph 호환 개발 핵심 포인트
1. RenderGraph 활성화 상태에서 Execute 대신, RecordRenderGraph 함수 사용.
기존에 ScriptableRenderPass의 Execute 함수는 ScriptableRenderer.EnqueuePass에 의해서 호출되며 커맨드버퍼 명령 로직을 작성합니다. RenderGraph 활성화 상태에서는 Execute 대신 RecordRenderGraph 함수가 호출됩니다.
RenderGraph 활성화 & 비활성화 환경 둘다 대응하고 싶으면 두 함수 모두 구현해야합니다.
Execute 함수와 같이 RenderGraph 이전에 사용하던 함수에는 [Obsolete] 어트리뷰트를 추가합니다, 왜냐하면 deprecated 예정이라고 경고 메세지가 계속 발생하기 때문입니다.


2. RenderTexture는 RenderGraph 시스템에서 관리, RTHandle 사용하지 않음.
RenderGraph의 방향성은 RenderTexture 객체 데이터를 사용자가 관리하지 않고 RenderGraph 시스템에서 관리하는 것이며 사용자가 RTHandle과 같은 방법으로 RenderTexture 객체 메모리 관리하지 않습니다.
RTHandle 대신, TextureHandle 구조체를 사용해서 RenderGraph에서 관리하는 RenderTexture를 참조합니다.
또한 TextureHandle은 사용자 클래스에 직접 선언해서 관리하지 않고 PassData 컨테이너 클래스를 선언해서 내부에 선언합니다.

3. PassData 컨테이너 클래스
앞에서 설명한 PassData는 RenderGraph 시스템에 전달 & 참조하기 위한 컨테이너 클래스입니다.
TextureHandle & Material 등, 렌더링에 필요한 자료형을 선언합니다. PassData 또한 ScriptableRenderPass에 객체를 선언하지 않고 RecordRenderGraph 함수 내부에서 RenderGraph에 전달 & 참조만 합니다. RenderGraph에서는 PassData 클래스 내용물을 기준으로 메모리를 할당하여 실제 데이터는 RenderGraph 내부에서 관리하는 방식입니다.

4. UnsafePass와 RasterPass
RenderGraph의 SubPass는 UnsafePass와 RasterPass 2가지 방법으로 개발합니다.
UnsafePass는 기존 처럼 CommandBuffer API를 대부분 다 사용 가능합니다, 실제로 RenderGraph 호환 코드로 변경할때 가장 쉬운 선택지입니다.

RasterPass는 NativeRenderPass로 동작하는 SubPass입니다.
CommandBuffer 클래스가 아닌 RasterCommandBuffer 클래스를 활용해야하며, 기존 CommandBuffer API 사용이 안됩니다. 대표적으로 SetRenderTarget 대신, builder.SetRenderAttachment 함수를 이용해야합니다.

UnsafePass & RasterPass 차이점
RasterPass는 1 SubPass당 1 Drawcall만 가능합니다, 위 예제 처럼 2번 Drawcall해야한다면 SubPass 2개 선언해야합니다.
NativeRenderPass의 Pass 병합 혹은 FrameBufferFetch 사용할게 아니라면 그냥 UnsafePass로 개발해도 성능 차이 없습니다.
5. builder.SetRenderFunc static 람다 함수 호출 권장
builder.SetRenderFunc 함수를 통해 람다 함수를 호출하는 방식으로 구현합니다.
이때 호출하는 람다 함수는 static으로 선언해야 합니다. 왜냐하면 람다 함수가 자신의 외부 스코프에 있는 변수를 "캡처"하면, 컴파일러는 이 변수들을 저장하기 위한 보이지 않는 클래스를 만들고 이 클래스의 인스턴스를 힙(Heap) 메모리에 할당하게 됩니다.
쉽게 말해서 GC 발생 할 수 있다는 것 입니다.

6. FrameBufferFetch 렌더 호출 방법
FrameBufferFetch를 통해 샘플링하기 위해서 아래 조건이 충족되어야 합니다.
- GPU & Graphics API가 타일 렌더 기반으로 동작해야함. (Vulkan 지원 급 모바일은 대부분 OK, 그 외 DX 계열은 안됨)
- 이전 렌더링 화면 버퍼와 Target 버퍼와 해상도, 포맷 등 RenderTexture 속성이 일치해야함.
- UV offset 불가능, 렌더링하는 픽셀 위치의 픽셀만 샘플링 가능.
FrameBufferFetch Pass를 렌더링할때 Texture2D가 아닌 SetInputAttachment 함수를 통해 렌더타겟과 인덱스를 설정합니다.
그리고 Shader에서는 렌더텍스처 이름이 아닌, 인덱스로 바인드합니다.


FrameBufferFetch는 좋은가?
FrameBufferFetch는 메모리에 저장된 텍스처가 아닌 타일 메모리에 저장된 픽셀 정보를 바인드하기 때문에 대역폭 사용량이 없고 샘플링 속도가 빠릅니다.
그런데 아직 Unity6에서는 사용하기에는 불안정합니다, 가끔 렌더버퍼 바인드가 오류나는지 이유 없이 무한 튕김 증상이 일어날 때도 있고, Memoryless로 동작해야하는데 아직 iOS Metal API 외에 Vulkan 사용하는 안드로이드 계열에서 메모리에 RenderTexture를 저장하고 있습니다.

'Unity' 카테고리의 다른 글
| Unity URP에서 화면 효과를 최적화하는 새로운 접근법 (3) | 2025.07.22 |
|---|---|
| Unity PerObjectRenderTexture UI 시스템 개발 과정 (1) | 2025.07.15 |
| Unity6 Dynamic Resolution 소개 및 대응 방법 (1) | 2025.07.10 |
| Unity RenderBufferLoadAction & StoreAction 옵션 설명 (0) | 2025.06.11 |
| Unity Cubemap Shadow (0) | 2025.04.28 |
WRITTEN BY
- CatDarkGame
Technical Artist dhwlgn12@gmail.com


