렌더링 파이프라인 기초
렌더링 파이프라인은 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 |
- 렌더링 파이프라인은 정점 셰이더(좌표 변환) → 래스터라이저(픽셀화) → 프래그먼트 셰이더(색상 계산) → 출력 병합(깊이/블렌딩) 순으로 처리된다.
- 정점 셰이더의 핵심은 MVP 행렬 변환이며, 각 좌표 공간의 의미를 정확히 이해해야 라이팅 계산이 올바르게 된다.
- 오버드로우(같은 픽셀을 여러 번 그리기)는 프래그먼트 셰이더 성능을 저하시키므로 불투명 오브젝트는 앞→뒤 정렬로 Early-Z를 활용한다.
- 알파 블렌딩은 깊이 쓰기를 비활성화하므로, 투명 오브젝트는 반드시 뒤→앞 순서로 렌더링해야 한다.