GPU 렌더링 파이프라인 심화
GPU 렌더링 파이프라인은 3D 장면 데이터를 2D 픽셀로 변환하는 일련의 처리 단계입니다. 각 단계를 깊이 이해하면 셰이더 최적화, 드로우콜 감소, 메모리 대역폭 절약 등 실질적인 성능 향상을 이끌어낼 수 있습니다.
파이프라인 전체 흐름
섹션 제목: “파이프라인 전체 흐름”CPU GPU | | | DrawCall ──────────▶ | | Input Assembler | ↓ | Vertex Shader (프로그래머블) | ↓ | Hull Shader (테셀레이션, 선택) | ↓ | Domain Shader (선택) | ↓ | Geometry Shader (선택) | ↓ | Rasterizer (고정 기능) | ↓ | Pixel/Fragment Shader (프로그래머블) | ↓ | Output Merger (깊이 테스트, 블렌딩) | ↓ | Framebuffer → 화면 출력SIMT 실행 모델
섹션 제목: “SIMT 실행 모델”GPU는 SIMT(Single Instruction, Multiple Threads) 방식으로 동작합니다. NVIDIA에서는 32개 스레드 묶음을 워프(Warp), AMD에서는 64개를 **웨이브프론트(Wavefront)**라고 합니다.
워프 내 32개 스레드 → 동일한 명령어 동시 실행┌──────────────────────────────────────────┐│ Thread 0 | Thread 1 | ... | Thread 31 ││ 같은 셰이더 코드, 다른 데이터 │└──────────────────────────────────────────┘Warp Divergence (분기 발산)
섹션 제목: “Warp Divergence (분기 발산)”// 이 코드는 워프 내 스레드가 다른 경로를 실행해 성능 저하if (uv.x > 0.5) { color = texture(albedo, uv); // 절반 스레드 실행} else { color = vec4(1.0, 0.0, 0.0, 1.0); // 나머지 절반 실행}// 워프는 두 경로를 모두 순차 실행 → 실질적으로 절반 성능분기 발산을 줄이려면 mix(), step(), smoothstep() 같은 수학 함수로 분기를 없애거나, 텍스처 룩업으로 대체합니다.
버텍스 셰이더 단계
섹션 제목: “버텍스 셰이더 단계”// 기본 버텍스 셰이더layout(location = 0) in vec3 aPosition;layout(location = 1) in vec3 aNormal;layout(location = 2) in vec2 aTexCoord;
uniform mat4 uMVP; // Model-View-Projectionuniform mat4 uModel;uniform mat3 uNormalMatrix; // transpose(inverse(Model))의 3x3
out vec3 vWorldPos;out vec3 vNormal;out vec2 vTexCoord;
void main() { vWorldPos = vec3(uModel * vec4(aPosition, 1.0)); vNormal = normalize(uNormalMatrix * aNormal); vTexCoord = aTexCoord; gl_Position = uMVP * vec4(aPosition, 1.0);}주의: Normal을 변환할 때 Model 행렬이 아닌 transpose(inverse(Model))의 3x3을 사용해야 비균등 스케일에서도 올바릅니다.
래스터라이제이션
섹션 제목: “래스터라이제이션”고정 기능 하드웨어가 삼각형을 픽셀 그리드에 매핑합니다.
삼각형 버텍스 → 픽셀 커버리지 계산 → 보간 속성 생성
(0,0)──────────────(W,0) | ╱╲ | | ╱ ╲ 픽셀 | | ╱ ╲ 샘플링 | |╱──────╲ |(0,H) (W,H)Perspective-Correct Interpolation: UV, 법선 등 버텍스 속성을 W로 나눠 원근 보정을 적용합니다. noperspective 키워드로 선형 보간만 할 수도 있습니다.
픽셀 셰이더 최적화
섹션 제목: “픽셀 셰이더 최적화”텍스처 캐시와 접근 패턴
섹션 제목: “텍스처 캐시와 접근 패턴”// 좋음 — 연속된 UV, 캐시 히트 가능성 높음vec4 c = texture(sampler, vTexCoord);
// 나쁨 — 랜덤 UV 접근, 캐시 미스 다발vec2 randomUV = hash(vTexCoord) * vec2(1.0);vec4 c = texture(sampler, randomUV);Early-Z (조기 깊이 테스트)
섹션 제목: “Early-Z (조기 깊이 테스트)”기존: 픽셀 셰이더 실행 → 깊이 테스트 → 실패 시 결과 버림Early-Z: 깊이 테스트 먼저 → 통과한 픽셀만 셰이더 실행Early-Z가 활성화되려면 픽셀 셰이더에서 gl_FragDepth를 수정하거나 discard를 사용하지 않아야 합니다.
// Early-Z 비활성화 원인void main() { if (texture(albedo, vTexCoord).a < 0.5) discard; // Early-Z 비활성화! → 오버드로우 발생 // ...}
// 해결책: Alpha Test 전용 셰이더 분리 + Sort by depth출력 병합 (Output Merger)
섹션 제목: “출력 병합 (Output Merger)”픽셀 셰이더 출력 → 깊이/스텐실 테스트 → 블렌딩 → Framebuffer
블렌딩 공식:Result = Src * SrcFactor + Dst * DstFactor
알파 블렌딩:Result = Src * Src.a + Dst * (1 - Src.a)// OpenGL 블렌딩 설정 예시glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 반투명 오브젝트는 뒤에서 앞 순서(Painter's Algorithm)로 렌더링 필요Tile-Based Rendering (모바일 GPU)
섹션 제목: “Tile-Based Rendering (모바일 GPU)”모바일 GPU(Mali, Adreno, Apple GPU)는 화면을 타일(16×16~32×32 픽셀)로 나눠 처리합니다.
화면 → 타일 분할 → 각 타일을 온칩 메모리에서 처리 → 메인 메모리에 기록장점: 메인 메모리 대역폭 절약단점: Framebuffer Read(glBlendFunc 일부, 포스트 프로세싱) 시 타일 데이터 강제 flush모바일 최적화 원칙:
glInvalidateFramebuffer/MTLRenderPassDescriptor loadAction = .clear로 이전 타일 데이터 불필요 시 명시- 중간 렌더 타겟(render-to-texture)을 최소화
- AlphaTest 대신 AlphaBlend를 선호 (Early-Z 활용)
Mesh Shader (최신 GPU)
섹션 제목: “Mesh Shader (최신 GPU)”전통적인 Vertex/Geometry Shader를 대체하는 모던 파이프라인입니다.
전통: IA → VS → GS → RasterizerMesh: Task Shader → Mesh Shader → Rasterizer
장점:- Meshlet 기반 GPU-driven 렌더링- Culling을 GPU에서 직접 수행- 동적 LOD 전환 용이// HLSL Mesh Shader 개요 (DirectX 12)[numthreads(32, 1, 1)][outputtopology("triangle")]void MeshShader( in uint gid : SV_GroupID, in uint tid : SV_GroupThreadID, out vertices MyVertex verts[64], out indices uint3 tris[128]){ SetMeshOutputCounts(vertCount, triCount); // Meshlet 데이터로 verts, tris 채우기}파이프라인 스톨 원인과 해결
섹션 제목: “파이프라인 스톨 원인과 해결”| 원인 | 증상 | 해결책 |
|---|---|---|
| 오버드로우 | GPU 이용률 높고 프레임 낮음 | Occlusion Culling, Front-to-back 정렬 |
| 메모리 대역폭 부족 | 텍스처 많고 느림 | 밉맵, 텍스처 압축(BC7, ASTC) |
| Warp Divergence | 셰이더 복잡하고 분기 많음 | 분기를 math로 대체 |
| CPU-GPU 동기화 | GPU 대기 | 멀티 프레임 버퍼링, 비동기 컴퓨트 |
| State Change 과다 | 드로우콜 많음 | 인스턴싱, 배치 렌더링, GPU-Driven |
핵심 요약
섹션 제목: “핵심 요약”- SIMT: 32개(NVIDIA) 스레드가 동일 명령 실행 → 분기 최소화
- Early-Z:
discard/gl_FragDepth수정 시 비활성화 → 오버드로우 증가 - 모바일 Tile-Based: Framebuffer Read 최소화, 명시적 load/store action 설정
- Mesh Shader: GPU-Driven 렌더링의 핵심, Meshlet 단위 컬링
- 최적화 우선순위: 드로우콜 감소 → 오버드로우 감소 → 셰이더 단순화 → 대역폭 절약