콘텐츠로 이동

렌더링 파이프라인 기초

렌더링 파이프라인은 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

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