김하연 튜터님 강의 - ‘GPU와 렌더링 파이프라인의 기초’
GPU와 렌더링 파이프라인의 기초에 대하여 알아보자
김하연 튜터님의 Notion 자료를 바탕으로 강의를 들으며
수정 및 재작성한 블로깅용 글
-
여담 : Unreal은 자신만의 스킬 하나를 ‘깊게’ 다루는 것을 선호하는 편?
-
그래픽스는 언리얼 이전에 ‘자체’를 공부해야 함
1. 화면에 그림이 나오기까지 🖌️
1-1. 픽셀과 GPU의 필요성
📍 픽셀(Pixel)의 이해
- 정의: Picture Element의 줄임말, 화면을 구성하는 가장 작은 점
- 색상 표현: RGB (빨강, 초록, 파랑) 3색의 조합
- 각 색상: 0~255 단계 (8비트, 256단계)
- 예시: 빨강 100% + 초록 100% = 노란색
- 각 색상: 0~255 단계 (8비트, 256단계)
📍 해상도와 픽셀 수
| 해상도 | 픽셀 수 | 계산 |
|---|---|---|
| FHD (1920×1080) | 약 207만 개 | 1920 × 1080 |
| 4K (3840×2160) | 약 830만 개 | 3840 × 2160 |
📍 FPS와 초당 계산량
- 60fps 게임의 경우
- FHD: 207만 × 60 = 약 1.2억 픽셀/초
- 4K: 830만 × 60 = 약 5억 픽셀/초
- FHD: 207만 × 60 = 약 1.2억 픽셀/초
- 프레임이 높아야 게임은 할만함!
📍 CPU의 한계
- 문제점
- 코어 수 제한 (일반적으로 8~16코어)
- 순차 처리에 최적화
- 픽셀 처리 외 다른 작업도 처리해야 함
- 코어 수 제한 (일반적으로 8~16코어)
- 예시: 8코어 CPU로 5억 픽셀 처리 시 → 프레임당 수 시간 소요
아주 똑똑하지만
수가 별로 없다!
📍 GPU의 등장
- 특징
- 수천~수만 개의 단순한 코어 (RTX 4090: 16,384개 CUDA 코어)
- 병렬 처리에 최적화 (SIMD 구조)
- 단순 반복 작업에 특화
- 수천~수만 개의 단순한 코어 (RTX 4090: 16,384개 CUDA 코어)
CPU에게 잡일(?)을 시키지 않기 위해
탄생한 장치!
1-2. 렌더링 파이프라인
📍 파이프라인 개념
공장의 조립 라인처럼 각 단계가 순서대로 진행되며,
여러 데이터를 동시에 처리
📍 5단계 렌더링 파이프라인
DX/OpenGL의 대표적인 파이프라인들
(물론 세부적으로는 이거보다 훨씬 복잡하다)
(ex : 테슬레이션 등등)
1️⃣ Input Assembly (입력 조립)
-
3D 모델 데이터 준비
- 구성 요소:
- 버텍스(꼭짓점) 위치
- UV 좌표 (텍스처 매핑)
- 노말 (표면 방향)
- 인덱스 (삼각형 구성)
- 버텍스(꼭짓점) 위치
- RAM → VRAM으로 데이터 복사
(CPU -> GPU 복사)
(그래서 중간에 개입하려면 DX 등에선 mmap 등으로
공유해달라고 요청했어야 하였음)
2️⃣ Vertex Shader (버텍스 셰이더)
- 3D → 2D 변환
- 다들 이 연극 무대에 서있어(World 변환)
- 카메라 이쪽에 있어 (뷰 변환)
- 자 이제 찍는다! (Proj 변환)
- 다들 이 연극 무대에 서있어(World 변환)
- 변환 과정
- 월드 변환: 오브젝트를 게임 세계에 배치
- 뷰 변환: 카메라 시점 적용
- 프로젝션 변환: 원근법 적용
- 월드 변환: 오브젝트를 게임 세계에 배치
- 각 버텍스 독립적으로 처리 (병렬 처리)
(Vertex Shader를 만들어서 각 정점(버텍스)마다 처리!)
3️⃣ Rasterization (래스터화)
-
삼각형 → 픽셀 변환
깊이테스트: 가까운 오브젝트만 그리기
- 건물 뒤에 있는걸 왜 그려??
Z-Buffer를 통해 ‘깊이’를 비교
- 건물 뒤에 있는걸 왜 그려??
보간: 버텍스 사이 값 계산
- ex) a,b 의 노말값은 각각 이정도니까, 그 사이의 녀석들은 이렇게 주면 되겠군…
- ex) a,b 의 노말값은 각각 이정도니까, 그 사이의 녀석들은 이렇게 주면 되겠군…
4️⃣ Pixel Shader (픽셀 셰이더)
-
각 픽셀 색상 결정
(실제 색을 칠하는 용도) - 주요 작업
- 텍스처 샘플링
- 노말 매핑
- 조명 계산
- 그림자 계산
- PBR (물리 기반 렌더링)
- 텍스처 샘플링
- GPU의 성능을 잡아먹는 녀석…
- 물리적인 공식들을 매우 다양하게, 많이 사용함
- 물리적인 공식들을 매우 다양하게, 많이 사용함
5️⃣ Output Merger (출력 병합)
-
최종 이미지 생성
- 알파 블렌딩 (투명도)
(유리창과 같은 특수 처리용도)
- 이런 녀석들은 나중에 다시 가져다 쓰기도
(VTR,STR…)
- 이런 녀석들은 나중에 다시 가져다 쓰기도
-
안티앨리어싱 (계단 현상 제거)
- 프레임버퍼에 저장
(스왑 체인!)
이러한 파이프라인 중 어디에 문제가 있는지를 파악해야 최적화 가능!
1-3. 왜 삼각형인가?
- 평면성 보장: 3개 점은 항상 하나의 평면 구성
- 계산 단순성: 점이 삼각형 내부인지 판단 용이
- 하드웨어 최적화: GPU가 삼각형 처리에 특화
- 역사적 표준: 1970년대부터 사용된 업계 표준
- 사각형은 3각형 2개로 표현이 가능하기도 하고…
2. CPU에서 GPU로 명령 전달 💫
GPU는 수동적?
- 결국 CPU가 명령을 내려주어야 그제서야 일을 함
2-1. CPU와 GPU의 협업
📍 CPU의 역할
- 게임 로직 처리
- 입력 처리 (키보드, 마우스)
- 게임 규칙 관리
- AI 계산
- 물리 연산
- 입력 처리 (키보드, 마우스)
- 렌더링 결정
- 카메라 시야 내 오브젝트 판단
(컬링 - 그릴 것과 안 그릴 것 판단) - 거리별 LOD 결정
- 오클루전 컬링
- 카메라 시야 내 오브젝트 판단
📍 Draw Call 이해하기
-
정의: CPU가 GPU에게 보내는 그리기 명령 패키지
- 구성 요소
- 메시 정보 (버텍스, 인덱스 위치)
- 변환 정보 (위치, 회전, 크기)
- 머티리얼과 텍스처
- 렌더링 설정
- 메시 정보 (버텍스, 인덱스 위치)
- Draw Call은 매우 많은 내용이 담겨 있음
2-2. Draw Call의 비용
📍 처리 과정과 시간
| 단계 | 소요 시간 | 설명 |
|---|---|---|
| CPU 준비 | 2-3ms | 오브젝트 정보 수집, 컬링 확인 |
| 명령 버퍼 생성 | 1-2ms | GPU 언어로 번역 |
| CPU→GPU 전송 | 1-2ms | PCIe 버스 통과 |
| GPU 준비 | 1-2ms | 파이프라인 플러시, 상태 변경 |
| 실제 그리기 | 0.01ms | 렌더링 수행 |
문제점: 준비 시간(5-7ms) vs 실제 작업(0.01ms) = 500~700배 차이
즉, 그리는 것보다 (GPU의 일)
Draw Call 자체가 문제!(CPU -> GPU 전송)
-
그래픽 드라이버가 추가적인 검사 (주소, 컴파일 체크) 가능함…
-
CPU도 다른 일을 해야하기에 추가적인 병목 발생 가능함
(CPU는 게임 로직 뿐 아니라 매우 다양한 일을 진행)
📍 성능 영향
- 60fps 유지 조건: 프레임당 16.67ms 이내
- 시간 배분 예시
- CPU 게임 로직: 5ms
- Draw Call 준비: 5ms
- GPU 렌더링: 5ms
- 여유분: 1.67ms
- CPU 게임 로직: 5ms
- 권장 Draw Call 수
- PC: 200-300개 이하
- 모바일: 50-100개
- PC: 200-300개 이하
넉넉잡으면 1000(PC) ~ 500 (Mobile)
로 잡을수는 있을지도?
2-3. 최적화 기법
📍 배칭(Batching)
- 스태틱 배칭
- 정적 오브젝트 미리 합치기
- 장점: Draw Call 대폭 감소
- 단점: 메모리 사용 증가, 개별 제어 불가
- 정적 오브젝트 미리 합치기
- 다이나믹 배칭
- 실시간으로 작은 메시 합치기
- 조건: 300개 이하 버텍스
- 단점: CPU 부하 발생
- 실시간으로 작은 메시 합치기
하나의 ‘덩어리’로 만들어서 Draw Call을 줄이는 방식
- Merge Actor 같은 언리얼 기능이 존재(스태틱 배칭)
- 언리얼은 추가로 ‘다이나믹 배칭’을 고려함(자체적 기능)
📍 인스턴싱(Instancing)
-
동일 메시를 여러 위치에 그리기
-
예시: 나무 1000그루 = Draw Call 1개
-
제약: 같은 메시, 같은 머티리얼 필수
‘똑같이 생긴 녀석’들을 ‘위치’만 다르게 하여
하나의 Draw Call로 보냄
(그래도 Scale, Rotate 정도는 건들수 있음)
-
Transform 데이터를 수정은 가능함
-
‘텍스쳐’도 고정된다는 점을 유의하자
📍 GPU 드리븐 렌더링
- GPU가 직접 렌더링 결정
- 예시: 언리얼 5
Nanite - 장점: Draw Call 걱정 없이 수백만 폴리곤 사용
나나이트를 통해
- GPU가 Compute Shader를 돌려서 ‘그릴 요소’를 결정함
-
Draw Call을 호출 안함…!!
-
언리얼 최적화를 공부할 때 필수가 되는 이유
- 다만 이 기술만 ‘넣고 보는 방식’은 최적화가 아니므로 주의할 것!
(나나이트만 믿고 최적화가 제대로 되지 않는 경우도 많음…)
3. GPU가 실제로 하는 일 😶🌫️
- 쉐이더
- 텍스쳐
- 라이팅(+ 그림자)
3-1. 셰이더 시스템
HLSL로 작성
📍 셰이더 종류와 역할
- 버텍스 셰이더
- 역할: 3D 꼭짓점 → 2D 화면 좌표 변환
- 특징
- 각 버텍스 독립 처리
- 버텍스 추가/삭제 불가
- 애니메이션 처리
(마치 흔들흔들 하는 느낌이라던가)
- 각 버텍스 독립 처리
- 역할: 3D 꼭짓점 → 2D 화면 좌표 변환
- 픽셀 셰이더
- 역할: 각 픽셀 색상 결정
- 주요 작업
- 텍스처 샘플링 (디퓨즈, 노말, 러프니스 맵)
- 라이팅 계산
- PBR 계산
- 그림자 처리
- 텍스처 샘플링 (디퓨즈, 노말, 러프니스 맵)
- 역할: 각 픽셀 색상 결정
- 컴퓨트 셰이더
- 역할: 범용 GPU 계산
(자유로운 계산 작업 가능) - 활용: 물리 시뮬레이션, 파티클, AI 계산
- 역할: 범용 GPU 계산
3-2. 텍스처 시스템
📍 핵심 개념
- UV 매핑
- 3D 표면 ↔ 2D 텍스처 대응
- U(가로), V(세로): 0~1 범위
- 타일링: 1 초과 값으로 반복
- 3D 표면 ↔ 2D 텍스처 대응
- 밉맵(Mipmap)
- 텍스처를 절반씩 축소한 세트
- 장점
- 메모리 대역폭 절약
- 앨리어싱 방지
- 메모리 대역폭 절약
- 예시: 2048×2048 → 1024×1024 → … → 1×1
- 텍스쳐 계의 LOD!
(용량은 33% 정도 늘어남 -> 자동 생성)
(그래도 성능상 큰 이득)
- 텍스처를 절반씩 축소한 세트
- 텍스처 필터링
| 필터링 종류 | 특징 | 용도 |
|---|---|---|
| 포인트 | 가장 가까운 텍셀 선택 | 픽셀 아트 |
| 바이리니어 | 4개 텍셀 혼합 | 일반적인 경우 |
| 트라이리니어 | 밉맵 레벨 간 혼합 | 고품질 |
| 이방성(AF) | 비스듬한 표면 선명화 | 바닥, 벽 |
텍스쳐들 역시 하나의 픽셀
컬링 등으로 잘리게 되는 경우, 어떻게 선택해야 할까?
싶을 때, 필터링을 진행하여 선택함
- 텍스처 압축
- BC 포맷: 4×4 블록 압축
- 압축률: 1/4 ~ 1/6
- 특징: GPU가 압축 상태로 직접 읽기
(OpenWorld의 일부 텍스쳐는 매~우 무거움
그런만큼 VRam에 일일이 다올리기는 매우 부담스러움)
- ‘스트리밍’ 시스템이 갑자기 텍스쳐를 불러오다가 종종 텍스쳐가 깨지거나 이상하게 보일 수 있음
- ‘스트리밍’ 시스템이 갑자기 텍스쳐를 불러오다가 종종 텍스쳐가 깨지거나 이상하게 보일 수 있음
- BC 포맷: 4×4 블록 압축
3-3. 라이팅과 그림자
빛을 어떻게 표현할 수 있을까?
가 라이팅의 역사…
📍 조명 모델 진화
- Phong 모델 (고전적)
- Diffuse (확산광): 표면에 고르게 퍼지는 빛
- Specular (반사광): 반짝이는 하이라이트
- Ambient (주변광): 간접광 단순 표현
- Diffuse (확산광): 표면에 고르게 퍼지는 빛
물리 법칙을 다소 무시하는 경우가 존재
(‘그럴듯하게’ 가성비 좋게 표현)
(그렇기에 종종 ‘플라스틱’ 같은 느낌이…)
- PBR (물리 기반 렌더링)
- 핵심 파라미터
- Base Color: 고유 색상
- Metallic: 금속성 (0~1)
- Roughness: 거칠기 (0~1)
- Base Color: 고유 색상
- 특징: 물리 법칙 준수, 에너지 보존
- 핵심 파라미터
📍 그림자 구현 기법
| 기법 | 원리 | 특징 |
|---|---|---|
| 섀도우 매핑 | 광원 시점 깊이 저장 | 가장 일반적 |
| 캐스케이드 섀도우맵 | 거리별 해상도 차등 | 효율적 |
| 디스턴스 필드 | 복셀 기반 거리 정보 | 부드러운 그림자 |
| 레이트레이싱 | 광선 추적 | 정확하지만 비용 높음 |
그림자 연산은 매~우 비쌈
- Ambient Occlusion 같은 ‘전역 조명’ 연산 비용 절약과는 살짝 다를지도?
📍 간접 조명
- 라이트맵: 정적 간접광 미리 계산
- 라이트 프로브: 공간별 빛 정보 캡처
- SSGI: 화면 정보로 간접광 계산
- Lumen: 언리얼 5의 복합 기술(레이 트레이싱을 일부만 사용)
실제 현실은 ‘조명’으로부터
물체 표면의 ‘반사율’에 따라 튕기는 쪽으로 구현되나
실제로 그러면 ‘매~우’ 무거움
그렇기에 미리 계산하거나
화면에 보이는 녀석들만 레이 트레이싱을 적용하는 등의 최적화 기법 사용
4. 성능과 한계 이해하기 👽
4-1. 해상도와 성능
📍 해상도별 픽셀 수와 부하
| 해상도 | 픽셀 수 | FHD 대비 |
|---|---|---|
| HD (720p) | 92만 | 0.44x |
| FHD (1080p) | 207만 | 1x |
| QHD (1440p) | 369만 | 1.78x |
| 4K (2160p) | 830만 | 4x |
📍 픽셀 필레이트와 실제 성능
- 이론 vs 현실
- RTX 4090: 443 GPixels/s
- 4K 60fps 필요량: 0.5 GPixels/s
- 하지만 실제로는 겨우 달성
- RTX 4090: 443 GPixels/s
- 성능 저하 요인
- 오버드로우: 같은 픽셀 여러 번 그리기(해상도가 높아질수록 더 낭비가 심해짐!)
- 셰이더 복잡도: 픽셀당 수십~수백 명령어(가벼운 명령이라면 괜찮지만…)
- 메모리 대역폭: 데이터 전송 한계(4K는 초당 1TB의 메모리 전송으로도 부족할 수 있음…)
- 오버드로우: 같은 픽셀 여러 번 그리기(해상도가 높아질수록 더 낭비가 심해짐!)
- 해상도가 줄어든다고 성능이 비율만큼 늘어나진 않음!
- 해상도와 상관없는 고정 비용 등도 존재함
- 해상도와 상관없는 고정 비용 등도 존재함
4-2. GPU 병목 종류
📍 병목 유형과 진단
- 지오메트리 병목
- 원인: 버텍스 과다, 복잡한 버텍스 셰이더
- 진단
- 해상도 변경해도 fps 동일
- 와이어프레임 모드에서 성능 향상
- 해상도 변경해도 fps 동일
- 해결: LOD 사용, 셰이더 단순화
- 원인: 버텍스 과다, 복잡한 버텍스 셰이더
- 픽셀 병목
- 원인: 복잡한 셰이더, 오버드로우, 높은 해상도
- 진단
- 해상도 절반 → 성능 2배
- 셰이더 복잡도 뷰에서 빨간색 영역
- 해상도 절반 → 성능 2배
- 해결: 셰이더 최적화, 투명도 제한
- 원인: 복잡한 셰이더, 오버드로우, 높은 해상도
- 메모리 대역폭 병목
- 원인: 큰 텍스처, G-버퍼, MSAA
- 진단
- GPU 프로파일러 메모리 처리량 80% 초과
- 텍스처 품질 낮추면 성능 개선
- GPU 프로파일러 메모리 처리량 80% 초과
- 해결: 텍스처 압축, 대안
AA사용
- 원인: 큰 텍스처, G-버퍼, MSAA
📍 병목 찾기 실전 가이드
- 해상도 테스트:
720p → 4K성능 변화 측정 - 뷰 거리 테스트: 가까이/멀리 성능 비교
- 품질 설정 테스트: 각 설정별 영향 확인
- 프로파일러 활용:
stat gpu,stat unit명령
댓글남기기