Youngki Jeong | Game Dev Lab
HomeProjectsEvidenceJourneyAbout
SearchGitHub글쓰기

Youngki Jeong | Game Dev Lab

Unreal C++ 게임개발 포트폴리오 & 개발 기록

Projects·Evidence·Journey·About·GitHub Portfolio·GitHub

빠른 검색

프로젝트, Evidence, 디버깅 노트, 학습 기록을 검색합니다.

← 목록으로
← 목록으로
DX11-B02

Render Group에서 GBuffer와 최종 화면까지

2026년 9월 7일
·
JEONGYOUNGKI
Description

Render Group → Geometry Pass → GBuffer → Lighting → Deferred Composite로 이어지는 DirectX11 Rendering Pipeline을 정리한 글입니다.

Project

DX11-SpongeBob-BFBB

ArticleType

Architecture

SourceBoundary

Existing Framework

EvidenceLevel

Source

Priority

A

Tags

Rendering

ProofType

Source

Audience

Technical Interviewer

CareerTrack

Game Client

Render Group에서 GBuffer와 최종 화면까지
목차

DX11-B02 | DX11-SpongeBob-BFBB — Render Group에서 GBuffer와 최종 화면까지

한 줄 결론

화면의 최종 Pixel은 한 번의 Draw에서 바로 만들어지지 않습니다. Runtime Object가 Render Group → Shadow / Geometry → GBuffer / LightDepth → Lighting → LightAcc → Deferred Composite → UI / Present를 거치며 한 Frame이 조립됩니다.

Rendering Flow Snapshot

질문답
GameObject::Render() 한 번으로 최종 화면이 완성되는가?아니오. 여러 Pass와 Buffer의 결과가 순서대로 조립됩니다.
Geometry Pass가 남기는 것은?최종 조명색이 아니라 Diffuse / Normal / Depth / PickDepth 등의 GBuffer입니다.
Lighting 결과는 어디에 쌓이는가?MRT_LightAcc의 Shade / Specular에 쌓입니다.
Shadow용 Depth는 Camera Depth와 같은가?아니오. Target_LightDepth는 별도 Shadow Pass에서 기록됩니다.
화면이 이상하면 어디부터 보는가?Last Good Buffer → First Bad Buffer → 담당 Pass 순서로 좁힙니다.
Runtime Debug View가 Exact GPU Binding까지 증명하는가?아니오. 정확한 Draw Call / SRV Binding은 RenderDoc / PIX 영역입니다.

먼저 아래 흐름만 잡으면 뒤의 Source를 훨씬 쉽게 읽을 수 있습니다.

Text
Runtime Object
→ Render Group
→ Shadow / Geometry
→ GBuffer / LightDepth
→ Lighting
→ LightAcc
→ Deferred Composite
→ Blend / UI
→ Present

이 글의 핵심은 RenderTarget 이름을 외우는 것이 아니라, 어느 Pass가 무엇을 쓰고 다음 Pass가 무엇을 읽는지 Writer / Reader 관계로 연결하는 것입니다.

DX11-SpongeBob-BFBB의 Rendering을 따라가면서 가장 중요하게 본 것도 화면의 최종 Pixel이 한 번의 Draw에서 바로 만들어지는 것이 아니라는 점이었습니다.

대표적인 불투명 World Object는 다음 흐름을 거칩니다.

Text
GameObject
→ Late_Update
→ Render Group
→ Geometry Pass
→ GBuffer
→ Lighting
→ LightAcc
→ Deferred Composite
→ NonLight / Blend / UI / ImGui
→ Present

이 흐름을 나눠서 보기 시작한 뒤에는 화면이 이상할 때 “Shader 문제인가?”라고 한 번에 묶어서 보기보다 어느 Pass까지 정상이고, 어느 Buffer부터 값이 달라졌는지를 먼저 확인하게 됐습니다.


Runtime에서 Buffer 결과부터 확인했습니다

🎥 Video 1. GBuffer → LightAcc → LightDepth → Final을 Runtime에서 확인

파일: DX11-B02_Video_01_Runtime_Deferred_DebugView.mkv

같은 Scene에서 Camera를 크게 움직이지 않고 다음 순서로 확인합니다.

Text
Final Gameplay
→ MRT_GameObjects
   Diffuse / Normal / Depth / PickDepth
→ MRT_LightAcc
   Shade / Specular
→ MRT_ShadowObjects
   LightDepth
→ Final Gameplay

이 영상은 Source에 RenderTarget 이름이 있다는 사실을 보여주기 위한 자료가 아닙니다.

실제 Runtime에서 Geometry / Lighting / Shadow용 Buffer에 값이 들어오고, 그 결과가 Final 화면으로 이어지는지를 한 흐름에서 확인하기 위한 B02의 대표 Evidence입니다.


같은 Frame을 한 장으로 비교하면 구조가 더 빨리 보입니다

DX11-B02_Screenshot_01_Deferred_Rendering_Hero.png

📸 Screenshot 1. Final과 7개 RenderTarget Debug View 비교

파일: DX11-B02_Screenshot_01_Deferred_Rendering_Hero.png

같은 Frame의 Final 화면과 다음 7개 RenderTarget 결과를 한 장에서 비교합니다.

  • Target_Diffuse
  • Target_Normal
  • Target_Depth
  • Target_PickDepth
  • Target_Shade
  • Target_Specular
  • Target_LightDepth

이 Screenshot이 B02에서 가장 중요한 정지 이미지입니다.

단순히 작은 Texture 창이 많이 떠 있는 화면이 아니라, Geometry가 무엇을 쓰고 Lighting이 무엇을 만들며 Shadow용 Depth가 어디에서 따로 생기는지를 한 번에 읽을 수 있게 구성합니다.


이 Screenshot에서 7개 RenderTarget을 읽는 방법

현재 Debug View는 다음처럼 묶어서 보면 가장 빠릅니다.

화면 위치RenderTargetMRT Group역할
왼쪽 위Target_DiffuseMRT_GameObjects표면 기본 색
왼쪽 가운데Target_NormalMRT_GameObjectsLighting에 사용할 Normal
왼쪽 아래Target_DepthMRT_GameObjectsDeferred Lighting / Composite용 Depth
오른쪽 위 작은 창Target_PickDepthMRT_GameObjectsPixel Picking 보조 Depth
가운데 위Target_ShadeMRT_LightAccDiffuse Lighting / 명암 결과
가운데 아래Target_SpecularMRT_LightAccSpecular 결과
맨 오른쪽Target_LightDepthMRT_ShadowObjectsLight 기준 Shadow Depth

기억할 때는 다음 정도로 묶으면 충분합니다.

Text
왼쪽 3개
= Geometry / GBuffer 핵심 재료
 
가운데 2개
= Lighting 결과
 
오른쪽 위
= Picking
 
맨 오른쪽
= Shadow

이 위치는 사람이 Debug View를 빠르게 구분하기 위한 화면 배치입니다. Render Pass 실행 순서를 뜻하지 않습니다.

그리고 반드시 구분해야 할 점이 있습니다.

7개의 RenderTarget이 화면에 보인다 ≠ 한 Pass에서 7개를 동시에 MRT로 Bind한다

실제 Write는 Pass별로 4 + 2 + 1로 나뉩니다.


RenderTarget을 왜 작은 화면으로 다시 볼 수 있는가

RenderTarget도 GPU Texture Resource입니다.

같은 Resource를 어떤 용도로 보느냐에 따라 View가 달라집니다.

View역할
RTVGPU가 Render 결과를 쓰는 View
SRVShader가 Texture를 읽는 View

Debug View는 앞 Pass에서 RenderTarget에 기록한 Texture를 다시 SRV로 읽고 작은 Screen Quad에 출력하는 방식입니다.

Text
Pass에서 RTV로 Write
→ Pass 종료
→ 같은 Texture를 SRV로 Binding
→ Debug Shader에서 Sample
→ Screen Quad 출력

그래서 Final Scene만 보는 것보다 각 중간 Buffer를 직접 보면서 어느 Pass까지 값이 정상인지 더 빠르게 좁힐 수 있습니다.


Normal / Depth / LightDepth는 일반 색처럼 읽으면 안 됩니다

7개 Debug View 중 일부는 원래 “색” 데이터가 아닙니다.

Normal

Normal은 Material Color가 아니라 표면 방향 Vector입니다.

X / Y / Z 방향을 R / G / B로 시각화하기 때문에 파랑, 보라, 초록처럼 보이는 것이 자연스럽습니다.

Depth

Depth는 거리 값입니다.

값이 1.0 근처의 좁은 범위에 몰리면 Debug View에서는 거의 흰색이나 회색처럼 보일 수 있습니다.

LightDepth

Target_LightDepth도 마찬가지입니다.

LightDepth가 흰색으로 보인다고 해서 바로 정상이나 고장을 단정하지 않습니다.

먼저 다음을 나눠서 봅니다.

  • Shadow Caster가 Light Frustum에 들어왔는가?
  • Shadow Pass가 실제로 Write했는가?
  • Clear 값만 남아 있는가?
  • Depth 값이 1.0 근처에 몰려 있는가?
  • Debug Visualization의 대비가 너무 낮은가?

Debug 화면을 보기 쉽게 하기 위한 Remap은 Preview에만 적용하고, Shadow 계산에 사용하는 원본 Resource의 의미는 바꾸지 않습니다.

Original Resource = unchanged

Debug Visualization = remapped


Contribution Boundary

기존 DirectX11 Custom Framework에는 Renderer / RenderTarget / Shader의 기본 구조가 이미 있었습니다.

따라서 Deferred Renderer 전체를 처음부터 직접 설계했다고 설명하지 않습니다.

이 글에서 보여주려는 것은 기존 Rendering 구조를 Source에서 따라가고, Client Object와 Runtime Debug View를 연결해 Pass / Buffer / Writer-Reader 관계를 설명하고 문제 범위를 좁힌 경험입니다.

구체적으로 다음 질문에 답할 수 있도록 구조를 따라갔습니다.

  • 어떤 Object가 어느 Render Group에 들어가는가?
  • Geometry Pass는 어떤 Target에 쓰는가?
  • Lighting Pass는 무엇을 읽는가?
  • Lighting 결과는 어디에 쌓이는가?
  • Shadow용 Depth는 어디서 기록되는가?
  • Deferred Composite는 무엇을 다시 읽는가?
  • World와 UI는 어느 지점에서 갈리는가?

Gameplay Object, Boss, UI, Map Presentation을 확장하면서 CPU Runtime State가 최종 Pixel까지 어떤 경로를 타는지를 계속 확인했습니다.


한 Frame은 CRenderer::Draw() 안에서 여러 Pass로 나뉩니다

Source 기준 큰 순서는 다음과 같습니다.

Text
Render_Priority
→ Render_ShadowObj
→ Render_NonBlend
→ Render_Lights
→ Render_Deferred
→ Render_NonLight
→ Render_Blend
→ Render_UI
→ Render_Debug

Application 전체에서는 이후 Font / Debug, ImGui, Present가 이어집니다.

B02에서 핵심으로 보는 단계는 네 가지입니다.

Pass역할
Render_ShadowObjShadow용 LightDepth 기록
Render_NonBlendGeometry / GBuffer
Render_LightsLighting / LightAcc
Render_Deferred앞 결과를 다시 읽어 World Composite

즉 GameObject의 Render()가 호출됐다고 그 순간 최종 조명 결과가 BackBuffer에 완성되는 구조는 아닙니다.

DX11-B02_Screenshot_03_Source_CRenderer_Draw_PassOrder.png

🔡 Source 1. CRenderer::Draw() — 한 Frame의 Pass Order

파일: DX11-B02_Screenshot_03_Source_CRenderer_Draw_PassOrder.png

전체 함수 구현을 길게 보여주는 것이 아니라, 다음 호출 순서를 한눈에 확인하는 Architecture Evidence입니다.

Text
Render_Priority
→ Render_ShadowObj
→ Render_NonBlend
→ Render_Lights
→ Render_Deferred
→ Render_NonLight
→ Render_Blend
→ Render_UI

이 Source Screenshot이 담당하는 질문은 단순합니다.

“최종 Frame이 한 번의 Draw가 아니라 어떤 순서의 Pass로 조립되는가?”

특히 B02에서 핵심으로 보는 경계는:

Text
Render_ShadowObj
→ Render_NonBlend
→ Render_Lights
→ Render_Deferred

입니다.

이 화면 하나로 각 Pass 내부의 모든 Resource Binding까지 증명한다고 설명하지 않습니다. 여기서는 순서와 책임 경계만 확인합니다.


Render Group은 이번 Frame의 Draw 대상입니다

B01에서 정리했듯 Object가 Layer에 존재한다고 자동으로 화면에 그려지는 것은 아닙니다.

대표 GameObject는 Late_Update에서 Renderer로 자신을 넘깁니다.

Text
GameObject::Late_Update
→ CGameInstance::Add_RenderObject
→ CRenderer::Add_RenderObject
→ m_RenderObjects[RenderGroup]
→ CRenderer::Draw

Render Group은 영구 Object List가 아닙니다.

Object가 매 Frame 다시 등록되고 Renderer가 그 Frame의 Queue를 소비합니다.

따라서 Runtime Object가 존재하는 것과 이번 Frame에 Render되는 것은 다른 단계입니다.

UI도 전부 같은 Render Group을 사용한다고 단순화하지 않고, 실제 Object가 등록되는 Group과 Pass를 Source에서 확인합니다.


Geometry Pass에서는 최종 색이 아니라 GBuffer를 만듭니다

대표적인 불투명 World Object는 Render_NonBlend에서 MRT_GameObjects로 들어갑니다.

Text
Render_NonBlend
→ Begin_MRT(MRT_GameObjects)
→ RG_NONBLEND Object Render
→ End_MRT

현재 주요 Target은 다음 네 개입니다.

RenderTarget역할
Target_Diffuse표면 기본 색
Target_NormalLighting에 사용할 Normal
Target_DepthDeferred Lighting / Composite용 Depth
Target_PickDepthPixel Picking 보조 Depth

Geometry Pass에서 Mesh를 그렸다고 Lighting까지 끝난 것이 아닙니다.

뒤 Pass가 읽을 데이터를 먼저 기록하는 단계입니다.


7개의 RenderTarget을 하나의 MRT로 쓰는 구조는 아닙니다

현재 주요 RenderTarget은 7개지만 한 개의 거대한 7-target MRT로 동시에 쓰지 않습니다.

MRT GroupTargets
MRT_GameObjectsDiffuse / Normal / Depth / PickDepth
MRT_LightAccShade / Specular
MRT_ShadowObjectsLightDepth

CTarget_Manager는 RenderTarget과 MRT Group을 관리합니다.

큰 흐름은:

Text
RenderTarget 준비
→ MRT Group 구성
→ Begin_MRT
→ 해당 Pass에서 Write
→ End_MRT
→ 다음 Pass에서 필요하면 SRV로 Read

입니다.

그래서 RenderTarget이 생성되어 있다는 것, 현재 Pass에 실제로 Bind되었다는 것, 그 안의 Pixel이 정상이라는 것은 각각 다른 확인 대상입니다.


LightDepth는 Shadow Pass에서 따로 기록합니다

Target_LightDepth는 MRT_GameObjects에 포함된 Camera Depth와 다른 Resource입니다.

Shadow Pass에서는:

Text
Render_ShadowObj
→ Begin_MRT(MRT_ShadowObjects)
→ Shadow Caster Render_Shadow
→ Target_LightDepth Write
→ End_MRT

흐름으로 Light 기준 Depth를 기록합니다.

여기서 중요한 점은 Shadow용 DSV와 Target_LightDepth Color RenderTarget도 같은 것이 아니라는 점입니다.

Resource역할
Shadow DSVShadow Pass의 Depth Test / Depth Buffer
Target_LightDepth이후 Shader가 읽을 Light 기준 Depth 값

즉 DSV에 Depth가 기록됐다는 사실만으로 Target_LightDepth에 Shader가 읽을 값이 자동으로 채워지는 것은 아닙니다.


SandMan은 Shadow와 일반 World Rendering에 각각 참여합니다

SandMan은 같은 Object지만 Frame 안에서는 서로 다른 책임으로 두 번 참여합니다.

Text
Shadow
→ RG_SHADOWOBJ
→ Render_Shadow
→ LightDepth
 
World
→ RG_NONBLEND
→ Render
→ GBuffer

이 구조를 통해 같은 World Object가:

  • Shadow Pass에서는 LightDepth Writer
  • Geometry Pass에서는 GBuffer Writer

역할을 각각 가질 수 있습니다.

이 사례는 Deferred Renderer 전체를 증명하기 위한 별도 시스템이 아니라, 기존 Rendering Pipeline에 실제 Client Object를 연결한 Integration 사례로 사용합니다.


Camera Occlusion은 Shadow와 별개의 문제로 처리했습니다

SandMan이 Camera와 Player 사이에서 시야를 가릴 때 Fade되는 기능과, LightDepth를 사용하는 Shadow는 서로 다른 책임입니다.

Text
Camera Occlusion
= Camera → Player 가시성 문제
 
LightDepth Shadow
= Light → Surface 깊이 비교 문제

따라서 SandMan이 Fade되는 것을 보고 Deferred Shadow가 증명됐다고 설명하지 않습니다.

반대로 Shadow가 정상이라고 Camera Occlusion 판정까지 정상이라는 뜻도 아닙니다.

Camera Occlusion의 판정 정밀도는 Deferred Shadow와 별도 책임으로 관리합니다.

따라서 B02에서는 Occlusion 알고리즘 자체를 Shadow 증거로 사용하지 않고, Opaque / Dither Fade라는 Player-visible Integration 결과만 보여줍니다.

Occlusion 판정 방식이 이후 보강되더라도 그 Regression 결과와 이 글의 GBuffer / LightDepth Claim은 서로 섞지 않습니다.

DX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_Before_NoAlpha.png

📸 Screenshot 2A~2B. SandMan Opaque / Dither Fade Runtime 비교

SandMan의 Player-visible 상태는 Opaque / Dither Fade 두 장을 비교해서 봅니다.

Screenshot 2A — Opaque 상태

파일: DX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_Before_NoAlpha.png

SpongeBob과 Camera의 관계에서 SandMan이 일반 World Object처럼 Opaque 상태로 보이는 장면입니다.

Text
Player-visible Result
→ SandMan Opaque
DX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_After_Alpha.png

Screenshot 2B — Dither Fade 상태

파일: DX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_After_Alpha.png.png

Camera / Player 관계에 따라 SandMan에 Fade 표현이 적용된 Runtime 장면입니다.

파일명에는 Alpha가 들어가지만, 실제 표현 방식은 일반 Alpha Blend가 아니라 Ordered Dither Fade입니다.

Text
Player-visible Result
→ SandMan Dither Fade

Notion에서는 두 Screenshot을 2-Column 비교로 나란히 배치하면 상태 차이가 가장 빠르게 읽힙니다.

LEFTRIGHT
OpaqueDither Fade
Before_NoAlphaAfter_Alpha

이 비교가 담당하는 Claim은 다음 정도로 제한합니다.

같은 SandMan World Object가 Camera / Player 관계에 따라 Opaque 상태와 Dither Fade 상태로 달라지는 Runtime Integration 결과

이 화면만으로 다음 내용을 증명한다고 말하지 않습니다.

Text
Deferred Shadow 전체가 정상이다
X
 
Occlusion 판정이 모든 Camera 각도에서 완벽하다
X
 
Exact GPU Binding을 확인했다
X

Shadow / LightDepth는 별도 Buffer와 Source Flow로, Occlusion 정밀도는 별도 Regression으로 확인합니다.


Fade는 일반 Alpha Transparency가 아니라 Dither 방식입니다

현재 Fade는 매끈한 Alpha Blend가 아니라 ordered dither fade입니다.

가까이 보면 Pixel Pattern이 보일 수 있습니다.

그래서 공개 글에서도 반투명이라고 뭉뚱그리지 않고 Dither Fade라고 표현합니다.

이 Fade는 Camera Occlusion 표현이고 Shadow 책임과 분리되어 있습니다.


Lighting Pass는 GBuffer를 읽고 LightAcc를 만듭니다

Geometry Pass가 끝나면 Render_Lights로 넘어갑니다.

Text
Render_Lights
→ Begin_MRT(MRT_LightAcc)
→ Normal / Depth Read
→ Light 계산
→ Target_Shade / Target_Specular
→ End_MRT

Lighting은 앞 단계의 GBuffer를 입력으로 사용합니다.

따라서 Lighting 결과가 이상할 때는 Light 값부터 바로 고치기보다:

  1. Diffuse가 정상인가?
  2. Normal이 정상인가?
  3. Depth가 정상인가?
  4. 그 다음 Shade / Specular가 정상인가?

순서로 확인하는 편이 안전했습니다.

특히 Normal은 Lighting 결과에 직접 영향을 주기 때문에 Target_Normal을 먼저 봅니다.


Target_Depth와 Target_PickDepth는 목적이 다릅니다

둘 다 Depth 계열이지만 같은 Reader를 위한 Resource가 아닙니다.

ResourceReader / 목적
Target_DepthDeferred Lighting / Composite
Target_PickDepthPixel Picking 보조

Picking 문제가 생겼을 때도 Deferred Depth 문제와 PickDepth 기록 / Picking Reader 문제를 나눠서 볼 수 있습니다.


Deferred Composite에서 앞의 Buffer를 다시 읽습니다

Geometry와 Lighting이 끝난 뒤 Render_Deferred에서 앞에서 만든 결과를 다시 Shader Resource로 읽습니다.

현재 주요 입력 흐름은:

Text
Target_Diffuse
+ Target_Shade
+ Target_Specular
+ Target_Depth
+ Target_LightDepth
→ Shader_Deferred
→ Fullscreen Composite
→ BackBuffer

앞 Pass에서는 RenderTarget을 RTV Output으로 사용하고, Composite에서는 그 결과를 SRV Input으로 읽습니다.

그래서 GBuffer와 LightAcc가 정상인데 Final World만 이상하다면 Geometry부터 다시 의심하기보다 Render_Deferred → SRV Binding → Shader Variable → Fullscreen Composite 쪽으로 범위를 좁힐 수 있습니다.

🔡 Source 2A~2E. Shadow / Geometry / Lighting Writer → Deferred Reader

Writer / Reader 흐름은 Shadow → Geometry → Lighting → Deferred Input → Composite의 다섯 단계로 나눠 읽습니다.

전체 관계부터 보면:

Text
Shadow Writer
+
GBuffer Writer
+
LightAcc Writer
 
→ Deferred Inputs
→ Deferred Composite

입니다.

DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow0_Render_ShadowObj.png

Source 2A — Shadow Writer

파일: DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow0_Render_ShadowObj.png

Render_ShadowObj에서 Shadow Pass용 MRT를 열고 Light 기준 Depth 결과를 작성하는 경계를 보여줍니다.

Text
Render_ShadowObj
→ MRT_ShadowObjects
→ Target_LightDepth

이 장의 핵심 질문:

“LightDepth는 어디에서 Writer를 가지는가?”

DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow1_Render_NonBlend.png

Source 2B — Geometry / GBuffer Writer

파일: DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow1_Render_NonBlend.png

대표적인 불투명 World Object가 Geometry Pass에서 GBuffer 쪽으로 쓰이는 경계를 보여줍니다.

Text
Render_NonBlend
→ MRT_GameObjects
→ Diffuse / Normal / Depth / PickDepth

이 장의 핵심 질문:

“최종 조명색 이전에 Geometry Pass가 어떤 재료를 남기는가?”

DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow2_Render_Lights.png

Source 2C — Lighting / LightAcc Writer

파일: DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow2_Render_Lights.png

Lighting Pass가 GBuffer를 이용해 Lighting 결과를 MRT_LightAcc에 쌓는 경계를 보여줍니다.

Text
Render_Lights
→ MRT_LightAcc
→ Shade / Specular

이 장의 핵심 질문:

“Lighting 결과는 어디에 기록되는가?”

DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3A_Render_Deferred_Inputs.png

Source 2D — Deferred Inputs

파일: DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3A_Render_Deferred_Inputs.png

앞 Pass에서 작성한 Buffer들이 Deferred Composite의 입력으로 다시 모이는 구간입니다.

Text
Diffuse
+ Shade
+ Specular
+ Depth
+ LightDepth
→ Deferred Input

이 장은 앞에서 Writer였던 Resource들이 이제 Reader 입력으로 바뀌는 경계를 보여줍니다.

DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3B_Render_Deferred_Composite.png

Source 2E — Deferred Composite

파일: DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3B_Render_Deferred_Composite.png

모인 입력을 이용해 Fullscreen Deferred 결과를 최종 World 화면 쪽으로 조립하는 구간입니다.

Text
Deferred Inputs
→ Shader_Deferred
→ Fullscreen Composite
→ BackBuffer / 이후 Frame 단계

다섯 장을 순서대로 읽으면 다음 한 문장으로 정리할 수 있습니다.

각 Pass는 서로 다른 Buffer에 결과를 쓰고, Render_Deferred는 그 결과를 다시 읽어서 최종 World를 조립합니다.

이 Source Flow는 Owner / Writer / Reader 관계를 보여주는 Evidence입니다. 특정 Draw Call의 실제 SRV Slot과 GPU Pipeline State까지 증명하는 자료는 아니며, 그 질문은 RenderDoc / PIX 영역으로 분리합니다.


Runtime Debug View와 실제 GPU Binding은 같은 증거가 아닙니다

Runtime Debug View에서 Buffer Pixel이 보이는 것은 실제 결과를 확인하는 데 매우 좋은 Evidence입니다.

하지만 특정 Draw Call에서 어떤 SRV Slot에 무엇이 Bind됐는지까지 직접 증명하는 자료는 아닙니다.

확인 대상더 적합한 Evidence
Buffer Pixel ResultRuntime Debug View
Pass / Owner / ResponsibilitySource
Actual Draw Call BindingRenderDoc / PIX

공개 블로그에서는 Source + Runtime Debug View만으로 B02의 핵심 흐름을 충분히 설명할 수 있기 때문에 RenderDoc / PIX를 필수 Evidence로 넣지 않습니다.

면접에서 실제 GPU Binding 질문이 들어오면 Backup으로 사용하는 편이 글의 피로도가 낮습니다.


Deferred World 결과와 Application Final도 다릅니다

Render_Deferred가 끝났다고 Application Frame 전체가 끝나는 것은 아닙니다.

Text
Deferred Composite
→ Render_NonLight
→ Render_Blend
→ Render_UI
→ Render_Debug
→ ImGui
→ Present

따라서 Deferred World가 정상인데 UI만 이상하다면 GBuffer부터 다시 볼 필요가 없습니다.

반대로 UI는 정상이고 World만 이상하다면 World Rendering Path를 먼저 확인합니다.

한 화면에 같이 보인다고 모두 같은 Pass에서 만들어지는 것은 아닙니다.


화면이 이상할 때는 Pass 순서대로 확인합니다

Deferred Rendering을 정리하면서 가장 도움이 된 것은 확인 순서를 만들 수 있었다는 점입니다.

증상먼저 확인할 경계
World Object가 거의 안 보임Render Group → Render_NonBlend → MRT_GameObjects
색이 이상함Target_Diffuse → Texture / Material
조명이 이상함Target_Normal / Target_Depth → Light Data
Lighting 결과가 없음GBuffer → MRT_LightAcc → Shade / Specular
Shadow가 이상함Shadow Caster → MRT_ShadowObjects → LightDepth
GBuffer / LightAcc 정상, Final만 이상Render_Deferred → SRV / Shader / Composite
Picking만 이상Target_PickDepth → Picking Reader

실제 Debug 순서는 다음처럼 생각합니다.

Text
Final
→ Diffuse
→ Normal
→ Depth
→ Shade
→ Specular
→ LightDepth
→ Deferred Composite
→ Final 재확인

PickDepth는 Final Lighting의 핵심 입력이라기보다 Picking용 Buffer이므로 Picking 문제가 있을 때 별도로 확인합니다.

결국 목적은 모든 Buffer를 외우는 것이 아니라:

Last Good Buffer → First Bad Buffer → 담당 Pass

를 찾는 것입니다.


Frustum Culling은 GBuffer보다 앞단입니다

Frustum Culling은 이 글에서 성능 수치를 주장하기 위한 기능으로 사용하지 않습니다.

구조적 위치는:

Text
Camera / Frustum
→ Object Bounds
→ Visible Candidate
→ Render Group
→ GBuffer

정도로 봅니다.

즉 GBuffer에 들어가기 전에 Render Candidate를 줄이는 단계입니다.

Frustum Culling으로 FPS가 N% 향상됐다 같은 수치는 측정하지 않았기 때문에 사용하지 않습니다.


Shader도 Runtime Resource입니다

HLSL 파일은 C++ Source와 별개로 실제 실행 환경에 존재해야 하는 Runtime Resource입니다.

이 프로젝트의 배포 흐름에서는:

Text
Engine/Bin/ShaderFiles
→ UpdateLib.bat
→ Client/Bin/ShaderFiles
→ Client.exe

경로를 확인합니다.

Engine 쪽 Renderer / Shader를 수정한 뒤 Runtime을 다시 확인할 때는:

Text
Engine Debug x64 Build
→ UpdateLib.bat
→ Client Debug x64 Build
→ Client.exe Runtime Verify

순서를 사용합니다.

즉 C++ Build PASS가 Runtime Shader Resource 정상까지 자동으로 보장하는 것은 아닙니다.


Source와 Runtime Evidence의 역할도 나눴습니다

B02에서 각 자료가 담당하는 역할은 다릅니다.

확인 대상Evidence
Pass Order / ResponsibilitySource
Buffer Pixel ResultRuntime Debug View
Player-visible IntegrationRuntime Screenshot / Video
Actual GPU BindingRenderDoc / PIX
Final Gameplay ResultRuntime Gameplay

그래서 Source에서 Target 이름을 찾았다고 GBuffer Runtime Verified라고 쓰지 않습니다.

반대로 Debug View 화면 하나만 보고 내부 Owner / Binding 전체를 단정하지도 않습니다.


B02 Evidence Map — 각 자료가 맡는 질문

B02의 Evidence는 역할 기준으로 다섯 묶음입니다.

Text
Runtime Deferred Flow
Deferred Hero
SandMan Runtime Integration
Pass Order
Writer / Reader Flow

실제 파일은 Source와 비교 화면을 읽기 좋은 크기로 나눠 총 10개입니다.

역할실제 파일이 자료가 닫는 질문
Runtime Deferred FlowDX11-B02_Video_01_Runtime_Deferred_DebugView.mkvGBuffer / LightAcc / LightDepth 결과가 실제 Runtime에서 Final까지 이어지는가?
Deferred HeroDX11-B02_Screenshot_01_Deferred_Rendering_Hero.pngFinal과 7개 RenderTarget을 한 Frame에서 어떻게 읽는가?
SandMan OpaqueDX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_Before_NoAlpha.pngSandMan의 일반 Opaque Runtime 상태는 어떤가?
SandMan Dither FadeDX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_After_Alpha.pngCamera Occlusion 표현이 적용된 Player-visible 결과는 어떤가?
Pass OrderDX11-B02_Screenshot_03_Source_CRenderer_Draw_PassOrder.png한 Frame의 주요 Render Pass는 어떤 순서로 호출되는가?
Shadow WriterDX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow0_Render_ShadowObj.pngLightDepth는 어느 Pass에서 작성되는가?
GBuffer WriterDX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow1_Render_NonBlend.pngGeometry Pass는 어느 MRT에 무엇을 쓰는가?
LightAcc WriterDX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow2_Render_Lights.pngLighting 결과는 어디에 쌓이는가?
Deferred InputsDX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3A_Render_Deferred_Inputs.pngDeferred Composite가 어떤 Buffer를 다시 읽는가?
Deferred CompositeDX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3B_Render_Deferred_Composite.png입력 Buffer가 어떻게 최종 World Composite로 이어지는가?

가장 쉬운 읽기 순서는 다음입니다.

Text
① Runtime Video
전체 결과를 먼저 본다
 
→ ② Deferred Hero
Buffer 역할을 한눈에 잡는다
 
→ ③~④ SandMan 비교
Player-visible Integration을 본다
 
→ ⑤ CRenderer::Draw
Pass Order를 잡는다
 
→ ⑥ Shadow Writer
→ ⑦ GBuffer Writer
→ ⑧ LightAcc Writer
→ ⑨ Deferred Inputs
→ ⑩ Deferred Composite
Writer → Reader 흐름을 Source로 닫는다

이렇게 배치하면 Source를 먼저 읽지 않아도 독자가 “화면 결과 → Buffer → Pass → Writer / Reader” 순서로 자연스럽게 내려갈 수 있습니다.


게임 클라이언트 관점에서 이 흐름이 중요한 이유

Rendering 문제가 생겼을 때 최종 화면만 보면 원인 후보가 너무 많습니다.

Buffer와 Pass를 나누면 증상에 따라 확인 범위를 줄일 수 있습니다.

Text
Object 자체가 GBuffer에 없음
→ Render Group / Geometry Pass
 
Diffuse는 정상, Lighting이 이상함
→ Normal / Depth / LightAcc
 
GBuffer와 LightAcc는 정상, Final만 이상함
→ Deferred Composite
 
Shadow만 이상함
→ Shadow Caster / LightDepth
 
Picking만 이상함
→ PickDepth / Picking Reader

즉 B02에서 중요한 것은 Deferred Rendering 용어 자체보다 중간 결과를 관찰할 수 있고, 마지막 정상 Buffer 다음의 첫 비정상 Buffer를 찾아 담당 Pass로 내려갈 수 있다는 점입니다.


면접에서 30초로 설명한다면

“기존 Framework의 Deferred Rendering을 따라가 보면 Object가 Render Group에 들어온 뒤 Geometry Pass가 GBuffer를 만들고, Lighting Pass가 Normal / Depth를 읽어 Shade / Specular를 만든 다음, Deferred Composite가 Diffuse / Lighting / Depth / LightDepth를 다시 읽어 World 결과를 조립합니다. 저는 최종 화면만 보지 않고 Runtime Debug View로 각 Buffer 결과를 확인하고, Source에서는 Shadow / GBuffer / LightAcc Writer와 Deferred Reader 경계를 나눠서 확인했습니다. 문제가 생기면 Last Good Buffer 다음의 First Bad Buffer를 찾아 담당 Pass로 범위를 좁힙니다.”

이 답변의 핵심은 Deferred Rendering을 구현했다는 한 문장이 아니라:

Text
Object
→ Pass
→ Buffer
→ Reader
→ Final Result

를 실제 Source와 Runtime Evidence로 연결해서 설명할 수 있다는 점입니다.


정리

DX11-SpongeBob-BFBB의 Deferred Rendering을 가장 짧게 줄이면 다음과 같습니다.

Text
Runtime Object
→ Render Group
→ Shadow / Geometry
→ GBuffer / LightDepth
→ Lighting
→ LightAcc
→ Deferred Composite
→ Blend / UI
→ Present

제가 이 구조에서 가져간 핵심은 Deferred Rendering이라는 기술 이름 자체보다:

  • 어느 Object가 이번 Frame의 Render 대상이 되는가?
  • 어느 Pass가 어떤 Buffer를 쓰는가?
  • 그 Buffer를 다음에 누가 읽는가?
  • Runtime Debug View에서 실제 값이 들어오는가?
  • 최종 화면이 이상할 때 어느 Buffer / Pass부터 값이 달라졌는가?

를 Source와 실제 Runtime 결과를 연결해서 볼 수 있게 된 경험입니다.

특히 7개 RenderTarget을 단순한 작은 Texture 창으로 보는 것이 아니라, Geometry / Lighting / Picking / Shadow 결과로 나눠 읽고 Last Good → First Bad 방식으로 문제 Pass를 좁힐 수 있게 된 것이 B02에서 가장 실용적으로 가져간 부분입니다.

다음 B03에서는 Rendering에서 벗어나, Runtime Tool에서 실제 Object와 UI State를 수정하고 저장한 뒤 다시 Runtime에 복원하는 Tooling / Persistence 흐름을 정리합니다.

빠른 검색

프로젝트, Evidence, 디버깅 노트, 학습 기록을 검색합니다.

목차