콘텐츠로 이동

렌더링 파이프라인 기초

렌더링 파이프라인은 3D 메시 데이터를 최종 2D 픽셀 이미지로 변환하는 일련의 GPU 처리 단계입니다. 현대 게임 엔진(Unity URP, UE5 Nanite/Lumen)의 모든 셰이더와 렌더링 최적화는 이 파이프라인 위에서 동작합니다.


CPU GPU
────────────────────────────────────────────────────
[메시 데이터]
정점 버퍼 (VBO) → 입력 어셈블러 (IA)
인덱스 버퍼 (IBO) ↓
정점 셰이더 (VS) ← 프로그래머블
(헐 셰이더 → 테셀레이터 → 도메인 셰이더) 선택적
지오메트리 셰이더 선택적
래스터라이저 (RS) 고정 기능
프래그먼트/픽셀 셰이더 ← 프로그래머블
출력 병합 (OM) 고정 기능
[프레임버퍼 → 화면]

각 정점에 대해 한 번씩 실행됩니다. 주된 역할은 오브젝트 공간 좌표를 클립 공간(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 Transform
Screen Space (픽셀 좌표)

정점 셰이더의 클립 공간 삼각형을 픽셀(프래그먼트)로 변환합니다. 이 단계는 고정 기능으로 프로그래머가 직접 제어하지 않습니다.

주요 처리:

  • 클리핑: 뷰 프러스텀 밖의 기하를 잘라냄
  • 배면 제거(Backface Culling): 카메라 반대를 향한 삼각형 제거
  • 보간: 정점 속성(UV, 노멀, 색상)을 삼각형 내부에 선형 보간
  • 깊이값 생성: 각 프래그먼트의 Z값 계산

각 픽셀(프래그먼트)에 대해 최종 색상을 계산합니다.

// 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);
}

프래그먼트 셰이더의 결과를 프레임버퍼에 기록합니다.

현재 픽셀 Z값 < 버퍼의 Z값? → 더 가까움 → 기록
현재 픽셀 Z값 ≥ 버퍼의 Z값? → 더 멀음 → 버림
// Unity 셰이더 블렌딩 설정
Blend SrcAlpha OneMinusSrcAlpha // 일반 투명도
// 결과 = Src.rgb × Src.a + Dst.rgb × (1 - Src.a)
Blend One One // 가산 블렌딩 (파티클, 불꽃)
// 결과 = Src.rgb + Dst.rgb

증상가능한 병목
드로우콜 수 과다CPU → GPU 커맨드 제출 병목
정점 수 과다정점 셰이더 병목
오버드로우 심함프래그먼트 셰이더 병목
텍스처 샘플링 느림메모리 대역폭 병목
Fillrate 초과해상도 또는 알파 블렌딩 과다

파이프라인 단계Unity URPUE5
정점 셰이더vert() in HLSLMaterial Vertex Shader
프래그먼트 셰이더frag() in HLSLMaterial Pixel Shader
커스텀 패스 삽입ScriptableRendererFeatureCustom Render Pass
후처리Volume + URP Post ProcessPost Process Volume
전역 셰이더 파라미터Global Shader PropertyMaterial Parameter Collection

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단계: 깊이 테스트 결과로 실제 픽셀만 셰이더 처리
-> 오버드로우가 많은 씬에서 효과적

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으로 처리

렌더링 파이프라인과 독립적으로 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에서 수행)

드로우콜(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)
- 거리에 따라 폴리곤 수가 적은 메시로 자동 전환
- 화면 픽셀 수 적은 오브젝트에 비싼 셰이더 낭비 방지

렌더링 파이프라인이란? 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은 동일 메시의 대량 렌더링을 최적화한다.