렌더링 파이프라인 기초
렌더링 파이프라인은 3D 메시 데이터를 최종 2D 픽셀 이미지로 변환하는 일련의 GPU 처리 단계입니다. 현대 게임 엔진(Unity URP, UE5 Nanite/Lumen)의 모든 셰이더와 렌더링 최적화는 이 파이프라인 위에서 동작합니다.
1. 파이프라인 전체 흐름
섹션 제목: “1. 파이프라인 전체 흐름”CPU GPU────────────────────────────────────────────────────[메시 데이터] 정점 버퍼 (VBO) → 입력 어셈블러 (IA) 인덱스 버퍼 (IBO) ↓ 정점 셰이더 (VS) ← 프로그래머블 ↓ (헐 셰이더 → 테셀레이터 → 도메인 셰이더) 선택적 ↓ 지오메트리 셰이더 선택적 ↓ 래스터라이저 (RS) 고정 기능 ↓ 프래그먼트/픽셀 셰이더 ← 프로그래머블 ↓ 출력 병합 (OM) 고정 기능 ↓ [프레임버퍼 → 화면]2. 정점 셰이더 (Vertex Shader)
섹션 제목: “2. 정점 셰이더 (Vertex Shader)”각 정점에 대해 한 번씩 실행됩니다. 주된 역할은 오브젝트 공간 좌표를 클립 공간(Clip Space)으로 변환하는 것입니다.
// HLSL 정점 셰이더 (Unity URP)struct Attributes{ float4 positionOS : POSITION; // Object Space 위치 float3 normalOS : NORMAL; float2 uv : TEXCOORD0;};
struct Varyings{ float4 positionCS : SV_POSITION; // Clip Space 위치 (필수 출력) float3 normalWS : TEXCOORD0; float2 uv : TEXCOORD1;};
Varyings vert(Attributes IN){ Varyings OUT;
// MVP 변환: Object → World → View → Clip // URP 내장 함수 사용 OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
// Normal: Object → World (역전치 행렬 필요) OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS);
OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex); return OUT;}좌표 공간 변환 정리
섹션 제목: “좌표 공간 변환 정리”Object Space (모델 로컬 좌표) ↓ Model Matrix (TRS)World Space (월드 좌표) ↓ View Matrix (카메라 역변환)View/Camera Space ↓ Projection Matrix (원근 투영)Clip Space (w로 나누기 전, -1~1 정규화 준비) ↓ Perspective Divide (w로 나눔)NDC (Normalized Device Coordinates, -1~1) ↓ Viewport TransformScreen Space (픽셀 좌표)3. 래스터라이저 (Rasterizer)
섹션 제목: “3. 래스터라이저 (Rasterizer)”정점 셰이더의 클립 공간 삼각형을 픽셀(프래그먼트)로 변환합니다. 이 단계는 고정 기능으로 프로그래머가 직접 제어하지 않습니다.
주요 처리:
- 클리핑: 뷰 프러스텀 밖의 기하를 잘라냄
- 배면 제거(Backface Culling): 카메라 반대를 향한 삼각형 제거
- 보간: 정점 속성(UV, 노멀, 색상)을 삼각형 내부에 선형 보간
- 깊이값 생성: 각 프래그먼트의 Z값 계산
4. 프래그먼트/픽셀 셰이더
섹션 제목: “4. 프래그먼트/픽셀 셰이더”각 픽셀(프래그먼트)에 대해 최종 색상을 계산합니다.
// HLSL 픽셀 셰이더 (Blinn-Phong 라이팅)half4 frag(Varyings IN) : SV_Target{ // 텍스처 샘플링 half4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
// 노멀 정규화 (보간 후 크기가 1이 아닐 수 있음) float3 N = normalize(IN.normalWS);
// 라이팅 계산 (URP Light 구조체 활용) Light mainLight = GetMainLight(); float3 L = normalize(mainLight.direction); float3 V = normalize(_WorldSpaceCameraPos - IN.posWS); float3 H = normalize(L + V); // Half Vector
float diffuse = max(0, dot(N, L)); float specular = pow(max(0, dot(N, H)), _Shininess);
float3 color = albedo.rgb * (mainLight.color * diffuse) + mainLight.color * specular * _SpecColor.rgb;
return half4(color, albedo.a);}5. 출력 병합 (Output Merger)
섹션 제목: “5. 출력 병합 (Output Merger)”프래그먼트 셰이더의 결과를 프레임버퍼에 기록합니다.
깊이 테스트 (Z-Test)
섹션 제목: “깊이 테스트 (Z-Test)”현재 픽셀 Z값 < 버퍼의 Z값? → 더 가까움 → 기록현재 픽셀 Z값 ≥ 버퍼의 Z값? → 더 멀음 → 버림알파 블렌딩
섹션 제목: “알파 블렌딩”// Unity 셰이더 블렌딩 설정Blend SrcAlpha OneMinusSrcAlpha // 일반 투명도// 결과 = Src.rgb × Src.a + Dst.rgb × (1 - Src.a)
Blend One One // 가산 블렌딩 (파티클, 불꽃)// 결과 = Src.rgb + Dst.rgb6. 파이프라인 병목 진단
섹션 제목: “6. 파이프라인 병목 진단”| 증상 | 가능한 병목 |
|---|---|
| 드로우콜 수 과다 | CPU → GPU 커맨드 제출 병목 |
| 정점 수 과다 | 정점 셰이더 병목 |
| 오버드로우 심함 | 프래그먼트 셰이더 병목 |
| 텍스처 샘플링 느림 | 메모리 대역폭 병목 |
| Fillrate 초과 | 해상도 또는 알파 블렌딩 과다 |
7. Unity URP / UE5 연결 지점
섹션 제목: “7. Unity URP / UE5 연결 지점”| 파이프라인 단계 | Unity URP | UE5 |
|---|---|---|
| 정점 셰이더 | vert() in HLSL | Material Vertex Shader |
| 프래그먼트 셰이더 | frag() in HLSL | Material Pixel Shader |
| 커스텀 패스 삽입 | ScriptableRendererFeature | Custom Render Pass |
| 후처리 | Volume + URP Post Process | Post Process Volume |
| 전역 셰이더 파라미터 | Global Shader Property | Material Parameter Collection |
8. Early-Z와 오버드로우 최적화
섹션 제목: “8. Early-Z와 오버드로우 최적화”Early-Z (조기 깊이 테스트):- 일반 파이프라인: 프래그먼트 셰이더 실행 후 깊이 테스트 -> 셰이더를 실행했지만 나중에 버려지는 픽셀 = 낭비
- Early-Z: 래스터라이저 직후, 셰이더 실행 전에 깊이 테스트 -> 가려진 픽셀은 셰이더 실행 없이 버려짐 = 성능 향상
Early-Z 활성화 조건:1. 셰이더에서 깊이값을 변경하지 않아야 함 (SV_Depth 미사용)2. 알파 테스트/클립 미사용3. 불투명 오브젝트를 앞→뒤(Front-to-Back) 순서로 렌더링
오버드로우 측정 (Unity):Window > Analysis > Frame Debugger에서 Overdraw 모드 확인// 같은 픽셀이 여러 번 그려지는 곳이 빨갛게 표시됨
Depth Prepass (Z Prepass):1단계: 깊이 정보만 렌더링 (Color Write 없음, 매우 빠름)2단계: 깊이 테스트 결과로 실제 픽셀만 셰이더 처리-> 오버드로우가 많은 씬에서 효과적9. Deferred Rendering (지연 렌더링)
섹션 제목: “9. Deferred Rendering (지연 렌더링)”Forward Rendering (기본):각 오브젝트 렌더링 시 모든 광원을 계산오브젝트 수 × 광원 수 = 연산량 (광원이 많으면 급격히 느려짐)
Deferred Rendering:1단계 - Geometry Pass: G-Buffer에 기하 정보 저장 - Albedo (색상) - Normal (법선 벡터) - Depth (깊이) - Metallic/Roughness (PBR 파라미터)
2단계 - Lighting Pass: G-Buffer를 이용해 광원 계산 - 실제로 보이는 픽셀에 대해서만 광원 계산 - 수백 개의 동적 광원도 효율적으로 처리 가능
장점: 광원 수에 선형적으로 비례하지 않는 성능단점: G-Buffer 메모리 대역폭 증가, 투명 오브젝트 처리 어려움
// Unity URP: Deferred Rendering Path 설정// URP Asset > Rendering > Rendering Path = Deferred
// UE5: 기본이 Deferred Rendering// 투명 오브젝트는 별도로 Forward Rendering으로 처리10. 컴퓨트 셰이더 (Compute Shader)
섹션 제목: “10. 컴퓨트 셰이더 (Compute Shader)”렌더링 파이프라인과 독립적으로 GPU에서 범용 병렬 연산을 수행합니다.
// HLSL 컴퓨트 셰이더 예시: 파티클 위치 업데이트// [numthreads(X, Y, Z)]: 스레드 그룹 크기[numthreads(64, 1, 1)]void UpdateParticles(uint3 id : SV_DispatchThreadID){ // id.x = 파티클 인덱스 if (id.x >= ParticleCount) return;
Particle p = Particles[id.x];
// 물리 업데이트 (GPU에서 병렬 처리) p.Velocity += float3(0, -9.8f, 0) * DeltaTime; // 중력 p.Position += p.Velocity * DeltaTime;
// 바닥 충돌 if (p.Position.y < 0) { p.Position.y = 0; p.Velocity.y = -p.Velocity.y * 0.5f; // 반발 }
p.Lifetime -= DeltaTime; Particles[id.x] = p;}// Unity에서 컴퓨트 셰이더 실행ComputeShader cs = Resources.Load<ComputeShader>("UpdateParticles");int kernel = cs.FindKernel("UpdateParticles");
cs.SetBuffer(kernel, "Particles", particleBuffer);cs.SetInt("ParticleCount", particleCount);cs.SetFloat("DeltaTime", Time.deltaTime);
// 64 스레드/그룹 * 그룹 수 >= 파티클 수int groups = Mathf.CeilToInt(particleCount / 64.0f);cs.Dispatch(kernel, groups, 1, 1);컴퓨트 셰이더 활용 사례:
- 파티클 시스템 (수만 개 파티클 병렬 업데이트)
- GPU Skinning (골격 애니메이션을 CPU 대신 GPU에서)
- Post-Processing 효과 (블룸, 안개, SSAO)
- Culling (Occlusion Culling을 GPU에서 수행)
11. 드로우콜 최적화
섹션 제목: “11. 드로우콜 최적화”드로우콜(Draw Call): CPU가 GPU에 "이 메시를 그려라"고 명령하는 것드로우콜 자체의 CPU 오버헤드: 수~수십 마이크로초수천 개의 드로우콜 = 수십 밀리초 오버헤드 = 프레임 예산 초과
최적화 기법:
1. Static Batching (정적 배칭) - 움직이지 않는 오브젝트들을 하나의 메시로 합침 - 드로우콜 1개로 여러 오브젝트 렌더링 - 단점: 메모리 증가, 이동 불가
2. Dynamic Batching (동적 배칭) - 같은 머티리얼을 사용하는 소형 메시를 런타임에 합침 - 메시가 크면 합치는 비용 > 드로우콜 절감 효과
3. GPU Instancing (GPU 인스턴싱) - 같은 메시를 수천 개 렌더링할 때 (풀, 나무, 군중) - 드로우콜 1개로 위치/색상이 다른 수천 개 인스턴스 렌더링 // Unity에서 GPU Instancing 활성화 // Material > Enable GPU Instancing 체크
4. Texture Atlas (텍스처 아틀라스) - 여러 텍스처를 하나의 큰 텍스처로 합침 - 머티리얼 수 감소 -> 드로우콜 감소
5. LOD (Level of Detail) - 거리에 따라 폴리곤 수가 적은 메시로 자동 전환 - 화면 픽셀 수 적은 오브젝트에 비싼 셰이더 낭비 방지12. 면접 핵심 정리
섹션 제목: “12. 면접 핵심 정리”렌더링 파이프라인이란? 3D 메시 데이터를 최종 2D 픽셀 이미지로 변환하는 GPU 처리 단계입니다. 정점 셰이더(좌표 변환) → 래스터라이저(픽셀화) → 프래그먼트 셰이더(색상 계산) → 출력 병합(깊이/블렌딩) 순으로 처리됩니다.
MVP 변환이란? 3D 오브젝트의 정점을 Model(로컬→월드) → View(월드→카메라) → Projection(카메라→클립 공간) 행렬 곱으로 화면 좌표로 변환하는 과정입니다.
Early-Z란? 프래그먼트 셰이더 실행 전에 깊이 테스트를 수행해 가려진 픽셀의 셰이더 연산을 생략하는 최적화입니다. 불투명 오브젝트를 앞→뒤 순서로 정렬할 때 가장 효과적입니다.
Forward vs Deferred Rendering 차이는? Forward는 각 오브젝트 렌더링 시 모든 광원을 계산하고, Deferred는 G-Buffer에 기하 정보를 먼저 저장 후 보이는 픽셀에 대해서만 광원을 계산합니다. 광원이 많은 씬에서는 Deferred가 유리합니다.
드로우콜이란? CPU가 GPU에 메시 렌더링을 요청하는 명령으로, 각 드로우콜마다 CPU 오버헤드가 발생합니다. Static/Dynamic Batching, GPU Instancing으로 드로우콜 수를 줄여 성능을 개선합니다.
- 렌더링 파이프라인은 정점 셰이더(좌표 변환) → 래스터라이저(픽셀화) → 프래그먼트 셰이더(색상 계산) → 출력 병합(깊이/블렌딩) 순으로 처리된다.
- 정점 셰이더의 핵심은 MVP 행렬 변환이며, 각 좌표 공간의 의미를 정확히 이해해야 라이팅 계산이 올바르게 된다.
- 오버드로우(같은 픽셀을 여러 번 그리기)는 프래그먼트 셰이더 성능을 저하시키므로 불투명 오브젝트는 앞→뒤 정렬로 Early-Z를 활용한다.
- 알파 블렌딩은 깊이 쓰기를 비활성화하므로, 투명 오브젝트는 반드시 뒤→앞 순서로 렌더링해야 한다.
- Deferred Rendering은 동적 광원이 많은 씬에서 성능 이점을 제공하고, GPU Instancing은 동일 메시의 대량 렌더링을 최적화한다.