

Unreal Engine의 빈 레벨에서도 프레임을 캡처하면 수백 개에서 수천 개에 이르는 이벤트가 기록된다. 처음 화면을 열었을 때는 Draw, Dispatch, Clear, Copy와 같은 명령이 복잡하게 나열되어 있어 어디서부터 분석해야 할지 판단하기 어렵다.
하지만 처음부터 모든 이벤트를 하나씩 분석할 필요는 없다. 먼저 프레임 전체를 구성하는 큰 렌더 패스를 구분하고, 각 패스의 입력과 출력이 어떻게 연결되는지 확인해야 한다.

Unreal Engine에서 한 프레임이 시작되는 부분을 살펴보면 SceneRender - ViewFamilies라는 이벤트를 확인할 수 있다.
여기서 View Family는 하나 이상의 View를 묶어 동일한 Scene과 Render Target에 렌더링하기 위한 단위이다.
Unreal Engine 소스 코드에서는 FSceneViewFamily의 역할을 다음과 같이 설명한다.

즉, 동일한 Scene에서 View Transform과 소유 Actor가 서로 다른 View들의 집합이 View Famliy인 것이다.
프레임이 시작될 때, SceneRender - ViewFamilies는 하나 이상의 View Family에 대한 Scene Rendering을 처리하는 상위 범위이다.
이후 RenderGraphExecute - /ViewFamilies에서는 View Family 렌더링을 위해 RDG에 등록된 패스들이 실제 GPU 명령으로 실행된다.
RenderGraphExecute - /ViewFamilies 내부로 들어가면 Scene 이벤트가 나타난다.

앞서 View Family가 어떤 Scene을 어떤 Render Target에 어떤 설정으로 렌더링할 것인지 정의했다면, Scene 구간에서는 그 정보를 기반으로 실제 GPU 작업이 실행된다.
아무것도 그려지지 않는 빈 화면에서도 Scene 내부에서는 다음과 같은 작업들이 이루어지고 있다.
FXSystemPreRender
ClearGPUMessageBuffer
GPUSceneUpdate
SceneCulling 데이터 업로드
NaniteSkinningUpdateViewData
SceneDepthZ 초기화
PrePass
HZB
ComputeLightGrid
GBufferClear
BasePass
Screen Space AO
Deferred Lighting
Translucency
PostProcessing
VirtualTextureUpdate
즉, 장면 렌더링에 필요한 GPU 데이터 준비부터 깊이 버퍼 생성, 가시성 판정, GBuffer 기록, 조명 계산, 후처리까지 하나의 View를 최종 화면으로 구성하는 전체 작업이 이루어지고 있는 것이다.
Scene 렌더링 초반에는 GPUSceneUpdate, Scene Culling 데이터 업로드, FX System 준비와 같은 작업이 수행된다.
이 단계에서는 화면에 직접 픽셀을 출력하기보다 이후 렌더 패스에서 사용할 Primitive, Instance, Light 등의 데이터를 GPU가 접근할 수 있는 형태로 준비한다.
Unreal Engine은 CPU에서 Draw Call마다 모든 오브젝트 정보를 개별적으로 전달하기보다, Scene 데이터를 GPU Buffer에 저장하고 여러 렌더 패스에서 공통으로 활용한다.
따라서 이 구간은 실제 렌더링을 시작하기 전 장면의 정보를 GPU에 정리하는 준비 단계로 볼 수 있다.

Scene 데이터 준비가 끝나면 SceneDepthZ를 초기화하고 PrePass가 실행된다.
PrePass는 장면의 불투명 오브젝트를 대상으로 색상을 기록하지 않고 깊이 정보만 먼저 생성하는 렌더 패스이다.
모든 프로젝트에서 동일한 형태의 PrePass가 실행되는 것은 아니다. Early Z Pass 설정, Nanite 사용 여부, 머티리얼 구성 등에 따라 처리되는 오브젝트의 범위가 달라진다.

HZB는 Hierarchical Z-Buffer의 약자로, 하드웨어 오클루전 쿼리처럼 작동하여 깊이 정보를 낮은 해상도로 단계적으로 축소하여 큰 오브젝트나 넓은 화면 영역의 가려짐 여부를 빠르게 판정할 수 있도록 한다.


HZB 오클루전을 기본 활성화하는 명령은 다음과 같다.
r.HZBOcclusion=1
해당 캡처에서도 PrePass 이후 BuildHZB가 실행되어, 여러 크기의 Mip이 생성되는 것을 볼 수 있다.
Base Pass는 장면에 존재하는 불투명 오브젝트의 기본 표면 정보를 기록하는 단계이다.
Deferred Rendering 경로에서는 한 번의 Base Pass에서 최종 조명 결과를 계산하지 않는다. 대신 이후 Lighting Pass에서 사용할 머티리얼 정보를 여러 개의 GBuffer에 저장한다.

해당 캡처는 빈 레벨이므로 일반적인 Scene Mesh를 그리는 Draw Call은 나타나지 않고, 에디터에서 사용하는 EditorPrimitives만 포함되어 있다.
그러나 실제 프로젝트에서 Mesh를 배치하면 대부분의 불투명 오브젝트가 이 Base Pass에서 처리되며, 머티리얼과 GBuffer의 관계를 분석하는 핵심 구간이 된다.

Base Pass에서 GBuffer가 완성되면 이를 입력으로 Lighting 단계가 수행된다.
해당 캡처에서는 직접 광원을 처리하는 작업보다 다음과 같은 간접 조명 관련 작업이 확인되었다.

이처럼 장면에 Light가 없더라도 Ambient Occlusion이나 Reflection처럼 화면 공간 정보를 이용하는 기능이 활성화되어 있으면 관련 패스가 실행될 수 있다.
불투명 오브젝트와 조명 계산이 끝나면 반투명 오브젝트를 처리하는 Translucency 단계가 실행된다.
반투명 오브젝트는 불투명 오브젝트와 달리 뒤에 있는 화면 결과와 혼합되어야 하므로 일반적인 GBuffer 기반 Deferred Rendering에 그대로 포함하기 어렵다.
따라서 불투명 Scene Color와 Scene Depth가 만들어진 이후 별도의 패스에서 처리한다.

Scene의 Geometry와 Lighting 처리가 끝나면 PostProcessing 단계가 실행된다.
Post Processing은 렌더링된 Scene Color를 입력으로 받아 최종 화면의 색상과 표현을 조정한다.


Post Processing에서는 다음과 같은 작업들이 이루어지고 있다.
특히 Post Processing은 빈 레벨에서도 많은 작업이 나타나는 구간이다.
장면에 Mesh나 Light가 거의 없어도 화면 전체를 대상으로 실행되는 Full Screen Pass는 그대로 수행될 수 있기 때문이다.
언리얼 엔진에서 빈 레벨의 한 프레임을 캡처해서 본 Scene Rendering의 전체 구조를 큰 흐름으로 정리하면 다음과 같다.
| 단계 | 주요 이벤트 | 역할 |
|---|---|---|
| Scene 준비 | GPUSceneUpdate, SceneCulling | Primitive와 Instance 데이터를 GPU에 준비한다. |
| 깊이 생성 | PrePass | 불투명 오브젝트의 Scene Depth를 먼저 생성한다. |
| 가시성 처리 | HZB, OcclusionTests | 깊이 정보를 계층화하고 가려진 오브젝트를 판별한다. |
| Light 분류 | ComputeLightGrid | 화면 영역별로 영향을 미치는 Light를 분류한다. |
| 표면 정보 생성 | GBufferClear, BasePass | 불투명 오브젝트의 머티리얼 정보를 GBuffer에 기록한다. |
| 조명 계산 | ScreenSpace AO, DeferredLighting | GBuffer와 Light 정보를 이용해 Scene Color를 계산한다. |
| 반투명 처리 | Translucency | 반투명 오브젝트를 별도로 렌더링하고 합성한다. |
| 후처리 | PostProcessing | 노출, Bloom, Motion Blur, Tonemap 등을 적용한다. |
| 후속 처리 | VirtualTextureUpdate | Virtual Texture Feedback을 정리한다. |
이 구조를 먼저 이해한 뒤 각 패스 내부의 Draw Call과 Shader를 단계적으로 분석해본다면 복잡한 이벤트 목록도 의미 있는 렌더링 흐름으로 읽을 수 있을 것이다.