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을 한 장으로 비교하면 구조가 더 빨리 보입니다

📸 Screenshot 1. Final과 7개 RenderTarget Debug View 비교
파일: DX11-B02_Screenshot_01_Deferred_Rendering_Hero.png
같은 Frame의 Final 화면과 다음 7개 RenderTarget 결과를 한 장에서 비교합니다.
Target_DiffuseTarget_NormalTarget_DepthTarget_PickDepthTarget_ShadeTarget_SpecularTarget_LightDepth
이 Screenshot이 B02에서 가장 중요한 정지 이미지입니다.
단순히 작은 Texture 창이 많이 떠 있는 화면이 아니라, Geometry가 무엇을 쓰고 Lighting이 무엇을 만들며 Shadow용 Depth가 어디에서 따로 생기는지를 한 번에 읽을 수 있게 구성합니다.
이 Screenshot에서 7개 RenderTarget을 읽는 방법
현재 Debug View는 다음처럼 묶어서 보면 가장 빠릅니다.
| 화면 위치 | RenderTarget | MRT Group | 역할 |
|---|---|---|---|
| 왼쪽 위 | Target_Diffuse | MRT_GameObjects | 표면 기본 색 |
| 왼쪽 가운데 | Target_Normal | MRT_GameObjects | Lighting에 사용할 Normal |
| 왼쪽 아래 | Target_Depth | MRT_GameObjects | Deferred Lighting / Composite용 Depth |
| 오른쪽 위 작은 창 | Target_PickDepth | MRT_GameObjects | Pixel Picking 보조 Depth |
| 가운데 위 | Target_Shade | MRT_LightAcc | Diffuse Lighting / 명암 결과 |
| 가운데 아래 | Target_Specular | MRT_LightAcc | Specular 결과 |
| 맨 오른쪽 | Target_LightDepth | MRT_ShadowObjects | Light 기준 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 | 역할 |
|---|---|
| RTV | GPU가 Render 결과를 쓰는 View |
| SRV | Shader가 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_DebugApplication 전체에서는 이후 Font / Debug, ImGui, Present가 이어집니다.
B02에서 핵심으로 보는 단계는 네 가지입니다.
| Pass | 역할 |
|---|---|
Render_ShadowObj | Shadow용 LightDepth 기록 |
Render_NonBlend | Geometry / GBuffer |
Render_Lights | Lighting / LightAcc |
Render_Deferred | 앞 결과를 다시 읽어 World Composite |
즉 GameObject의 Render()가 호출됐다고 그 순간 최종 조명 결과가 BackBuffer에 완성되는 구조는 아닙니다.

🔡 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::DrawRender 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_Normal | Lighting에 사용할 Normal |
Target_Depth | Deferred Lighting / Composite용 Depth |
Target_PickDepth | Pixel Picking 보조 Depth |
Geometry Pass에서 Mesh를 그렸다고 Lighting까지 끝난 것이 아닙니다.
뒤 Pass가 읽을 데이터를 먼저 기록하는 단계입니다.
7개의 RenderTarget을 하나의 MRT로 쓰는 구조는 아닙니다
현재 주요 RenderTarget은 7개지만 한 개의 거대한 7-target MRT로 동시에 쓰지 않습니다.
| MRT Group | Targets |
|---|---|
MRT_GameObjects | Diffuse / Normal / Depth / PickDepth |
MRT_LightAcc | Shade / Specular |
MRT_ShadowObjects | LightDepth |
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 DSV | Shadow 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은 서로 섞지 않습니다.

📸 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
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 FadeNotion에서는 두 Screenshot을 2-Column 비교로 나란히 배치하면 상태 차이가 가장 빠르게 읽힙니다.
| LEFT | RIGHT |
|---|---|
| Opaque | Dither Fade |
Before_NoAlpha | After_Alpha |
이 비교가 담당하는 Claim은 다음 정도로 제한합니다.
같은 SandMan World Object가 Camera / Player 관계에 따라 Opaque 상태와 Dither Fade 상태로 달라지는 Runtime Integration 결과
이 화면만으로 다음 내용을 증명한다고 말하지 않습니다.
Text
Deferred Shadow 전체가 정상이다
X
Occlusion 판정이 모든 Camera 각도에서 완벽하다
X
Exact GPU Binding을 확인했다
XShadow / 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_MRTLighting은 앞 단계의 GBuffer를 입력으로 사용합니다.
따라서 Lighting 결과가 이상할 때는 Light 값부터 바로 고치기보다:
- Diffuse가 정상인가?
- Normal이 정상인가?
- Depth가 정상인가?
- 그 다음 Shade / Specular가 정상인가?
순서로 확인하는 편이 안전했습니다.
특히 Normal은 Lighting 결과에 직접 영향을 주기 때문에 Target_Normal을 먼저 봅니다.
Target_Depth와 Target_PickDepth는 목적이 다릅니다
둘 다 Depth 계열이지만 같은 Reader를 위한 Resource가 아닙니다.
| Resource | Reader / 목적 |
|---|---|
Target_Depth | Deferred Lighting / Composite |
Target_PickDepth | Pixel 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입니다.

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를 가지는가?”

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가 어떤 재료를 남기는가?”

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 결과는 어디에 기록되는가?”

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 입력으로 바뀌는 경계를 보여줍니다.

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 Result | Runtime Debug View |
| Pass / Owner / Responsibility | Source |
| Actual Draw Call Binding | RenderDoc / 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 / Responsibility | Source |
| Buffer Pixel Result | Runtime Debug View |
| Player-visible Integration | Runtime Screenshot / Video |
| Actual GPU Binding | RenderDoc / PIX |
| Final Gameplay Result | Runtime 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 Flow | DX11-B02_Video_01_Runtime_Deferred_DebugView.mkv | GBuffer / LightAcc / LightDepth 결과가 실제 Runtime에서 Final까지 이어지는가? |
| Deferred Hero | DX11-B02_Screenshot_01_Deferred_Rendering_Hero.png | Final과 7개 RenderTarget을 한 Frame에서 어떻게 읽는가? |
| SandMan Opaque | DX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_Before_NoAlpha.png | SandMan의 일반 Opaque Runtime 상태는 어떤가? |
| SandMan Dither Fade | DX11-B02_Screenshot_02_Runtime_SandMan_Occlusion_Comparison_After_Alpha.png | Camera Occlusion 표현이 적용된 Player-visible 결과는 어떤가? |
| Pass Order | DX11-B02_Screenshot_03_Source_CRenderer_Draw_PassOrder.png | 한 Frame의 주요 Render Pass는 어떤 순서로 호출되는가? |
| Shadow Writer | DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow0_Render_ShadowObj.png | LightDepth는 어느 Pass에서 작성되는가? |
| GBuffer Writer | DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow1_Render_NonBlend.png | Geometry Pass는 어느 MRT에 무엇을 쓰는가? |
| LightAcc Writer | DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow2_Render_Lights.png | Lighting 결과는 어디에 쌓이는가? |
| Deferred Inputs | DX11-B02_Screenshot_04_Source_Deferred_Shadow_WriterReader_Flow3A_Render_Deferred_Inputs.png | Deferred Composite가 어떤 Buffer를 다시 읽는가? |
| Deferred Composite | DX11-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 흐름을 정리합니다.
