[렌더링 파이프라인]
■ 렌더링이란?
● 3D 모델 또는 장면에서 2D 이미지를 생성하는 과정
● 광원과 표면의 상호작용을 계산하여 픽셀의 색상을 결정하는 마지막 단계이다.
● 사실적(포토리얼리스틱) 또는 비사실적(툰, 셸 셰이딩 등) 이미지를 생성할 수 있다.
● 게임, 영화, 건축 시각화 등 다양한 분야에 적용된다.
■ 렌더링 파이프라인을 이해하면?
● 게임 성능 최적화의 핵심
| 파이프라인 이해 | 실전 예시 |
| 모델링 단계 | 메시가 복잡하면 Vertex 수 많아짐 → LOD 사용, Vertex 최적화 필요 |
| Rasterizer | 카메라 밖 오브젝트까지 렌더링? → Frustum Culling 도입 |
| Fragment Shader | 쉐이더 복잡도 문제 → Unlit 사용, 머터리얼 수 줄이기 등 선택 가능 |
● 쉐이더 작성 및 커스텀 연출 가능
| 연출 | 파이파라인 이해 X | 파이프라인 이해 O |
| 외곽선 효과 | 모델링을 다시 그려야 하나? | Normal을 이용해 Vertex Shader에서 외곽선 생성 |
| 모자이크 연출 | 그냥 이미지를 덮어? | PostProcessing에서 스크린 공간 조각 |
| 중심 뚫기 | 오브젝트 SetActive? | Fragment 단계에서 중심 픽셀 제거 처리 |
● 카메라, 투영, 클리핑 버그 디버깅 가능
| 현상 | 원인 |
| 카메라가 오브젝트 자름 | View Frustum의 near/far 범위 초과 |
| 화면 찌그러짐 | Projection Matrix 문제 or Aspect Ratio 오류 |
● 3D ↔ 스크린 공간 정확한 연산 가능
○ WorldToViewportPoint, ScreenToWorldPoint, Transform.InverseTransformPoint 등의 API 이해
○ UI ↔ 월드 오브젝트 간 위치 매핑 시 유용
● 엔진 독립적 사고 가능
○ Unity, Unreal, OpenGL, DirectX 등 렌더링 구조의 공통 논리 이해
○ 특정 엔진에 얽매이지 않고 그래픽 로직 설계 가능
● 아트/기획과의 커뮤니케이션 향상
○ "이건 쉐이더로 해결 가능해요", "여기선 Normal Map이 적용 안 돼요" 등의 정확한 피드백 가능
■ 큐브가 화면에 보이기까지: 좌표 변환 흐름
Local → World → View → Projection → Clip → NDC → Viewport → Screen
| 단계 | 설명 |
| Local | 모델 내부 좌표계 (자기 중심) "내 몸 안에서 오른손의 위치" |
| World | 씬 전체 기준 좌표계 Model Matrix를 통해 변환 "세상 속에서 내가 어디에 있는가?" |
| View | 카메라 기준 좌표계 View Matrix로 변환 카메라가 (0, 0, 0), 앞이 -Z축으로 설정됨 "카메라가 보는 세상 기준으로 위치" |
| Projection | 원근 or 직교 투영 적용 Projection Matrix로 변환 시야각, near/far, aspect ratio 반영 "카메라로 본 장면이 렌즈를 통해 왜곡됨" |
| Clip → NDC | 클리핑 후 정규화 ([-1, +1]) x, y는 뷰포트 정중앙이 0, 끝이 -1/+1 "렌더링 범위 안에 들어온 좌표들만 남음" |
| Viewport | 디바이스 뷰 해상도에 맞게 변환 "어느 카메라 뷰 안에서 어느 위치?" |
| Screen | 실제 화면의 픽셀 좌표 (예: 1920x1080) "모니터 상 어떤 픽셀에 찍히는가?" |

■ 렌더링 파이프라인 전체 구조
| 단계 | 설명 |
| Vertex/Index Buffer | 버텍스 데이터 및 삼각형 구성 인덱스 |
| Input Assembler | 버텍스 데이터를 정점 단위로 조립 |
| Vertex Shader | 모델 → 월드 → 뷰 → 클립 변환 (MVP 연산) 조명 예비 계산 등 |
| Rasterizer | 삼각형을 픽셀(Fragment) 단위로 분할 NDC → Viewport 변환 포함 |
| Fragement Shader | 픽셀당 조명, 색상, 텍스처 계산 |
| Framebuffer | 계산된 픽셀을 최종 화면 버퍼에 기록 |

■ Vertex Buffer에 포함될 수 있는 주요 요소
| 속성 이름 | 설명 | HLSL 변수 타입 예시 |
| Position | 버텍스의 3D 위치 | float3 position |
| Normal | 표면 방향 (조명 계산용) | float3 normal |
| Tangent | 법선과 함께 사용하는 방향 벡터 (Normal Mapping용) |
float4 tangent |
| UV (Texcoord) | 텍스처 좌표 | float2 uv 또는 float4 texcoord |
| Color | 정점 색상 | float4 color |
| Skin Weights / Bone Indices | 스키닝 애니메이션용 (본 가중치) | float4 weights, int4 boneIndices |
| Vertex ID / Custom Attribute | 사용자 정의 속성 | floatN myData (UV2, UV3 등으로 전달 가능) |
■ Rasterizer란?
● 삼각형을 화면의 픽셀 격자에 맞춰 나눔
● 어떤 픽셀들이 삼각형 내부에 있는지 계산
● 각 픽셀마다 z값, UV 등 보간
■ 쉐이더 코드(Vertex & Fragment)에서 건드릴 수 있는 부분
| 단계 | 설명 | 쉐이더에서 제어 가능 여부 |
| Local → World | 모델 매트릭스 (object-to-world) | UNITY_MATRIX_M 또는 사용자 정의 |
| World → View | 카메라 매트릭스 (world-to-view) | UNITY_MATRIX_V 또는 사용자 정의 |
| View → Projection | 원근 투영 매트릭스 | UNITY_MATRIX_P 또는 사용자 정의 |
| Proejction → NDC | 클립 공간 → 정규화 장치 좌표 | 자동 (GPU 내부 처리) |
| NDC → Viewport | NDC → 화면 비율에 따른 위치 변환 | 자동 (GPU 내부 처리) |
| Viewport → Screen | 화면 해상도 픽셀 위치 | 자동 (Rasterizer & Render Target 처리) |
■ GPU 렌더링 파이프라인 흐름 (세부 파이프라인 확장)
[CPU 영역]
Mesh + Transform + Material
↓
[Input Assembler]
Vertex / Index Buffer 조립
↓
[Vertex Shader]
Local → World → View → Projection
↓
[Clipping]
시야 밖 제거
↓
[Homogeneous Divide]
Clip Space → NDC (x, y, z ÷ w)
↓
[Viewport Transform]
NDC → Screen (픽셀 좌표로 변환)
↓
[Rasterizer]
삼각형 → 픽셀(프래그먼트)로
↓
[Fragment Shader]
픽셀 색상 계산 (텍스처, 조명 등)
↓
[Depth/Stencil/Blend Test]
Z-test, 투명도, 머티리얼 연산 등
↓
[Framebuffer]
최종 이미지 출력 (화면 렌더링)
■ 프로그래머블 파이프라인
● Vertex Shader
○ Local → World → View → Projection까지 직접 계산 가능
○ MVP 매트릭스 직접 곱하거나 수식으로 구현 가능
● Fragment Shader
○ Vertex Shader가 넘겨준 Interpolation 결과를 기반으로 조명/색상/텍스처 처리만 가능
○ 공간 좌표 보정 불가 (픽셀 위치 직접 변경 불가)
■ CPU 기반 렌더링
// === ManualTransform.cs ===
using UnityEngine;
public static class ManualTransform
{
public static Vector3 ApplyScale(Vector3 point, Vector3 scale)
{
return new Vector3(
point.x * scale.x,
point.y * scale.y,
point.z * scale.z
);
}
public static Vector3 ApplyRotation(Vector3 point, Vector3 euler)
{
float rx = euler.x * Mathf.Deg2Rad;
float ry = euler.y * Mathf.Deg2Rad;
float rz = euler.z * Mathf.Deg2Rad;
Vector3 xRot = new Vector3(
point.x,
point.y * Mathf.Cos(rx) - point.z * Mathf.Sin(rx),
point.y * Mathf.Sin(rx) + point.z * Mathf.Cos(rx)
);
Vector3 yRot = new Vector3(
xRot.x * Mathf.Cos(ry) + xRot.z * Mathf.Sin(ry),
xRot.y,
-xRot.x * Mathf.Sin(ry) + xRot.z * Mathf.Cos(ry)
);
Vector3 zRot = new Vector3(
yRot.x * Mathf.Cos(rz) - yRot.y * Mathf.Sin(rz),
yRot.x * Mathf.Sin(rz) + yRot.y * Mathf.Cos(rz),
yRot.z
);
return zRot;
}
public static Vector3 ApplyTranslation(Vector3 point, Vector3 translation)
{
return point + translation;
}
public static Vector4 PerspectiveProject(Vector3 pos, float fov, float near, float far)
{
float aspect = (float)Screen.width / Screen.height;
float f = 1f / Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
float x = pos.x * f / pos.z / aspect;
float y = pos.y * f / pos.z;
// z는 무시 가능 또는 단순값 사용
float w = 1f;
return new Vector4(x, y, 1f, w);
}
public static Vector3 ApplyViewTransform(Vector3 world, Vector3 camPos, Vector3 camEuler)
{
// 위치 보정
float dx = world.x - camPos.x;
float dy = world.y - camPos.y;
float dz = world.z - camPos.z;
// 카메라 회전 역방향 (ZYX 역순으로 역회전 적용)
float rx = -camEuler.x * Mathf.Deg2Rad;
float ry = -camEuler.y * Mathf.Deg2Rad;
float rz = -camEuler.z * Mathf.Deg2Rad;
// Z역
float x1 = dx * Mathf.Cos(rz) - dy * Mathf.Sin(rz);
float y1 = dx * Mathf.Sin(rz) + dy * Mathf.Cos(rz);
float z1 = dz;
// Y역
float x2 = x1 * Mathf.Cos(ry) + z1 * Mathf.Sin(ry);
float y2 = y1;
float z2 = -x1 * Mathf.Sin(ry) + z1 * Mathf.Cos(ry);
// X역
float x3 = x2;
float y3 = y2 * Mathf.Cos(rx) - z2 * Mathf.Sin(rx);
float z3 = y2 * Mathf.Sin(rx) + z2 * Mathf.Cos(rx);
return new Vector3(x3, y3, z3);
}
}
[쉐이더 간단 실습]
■ 대표 쉐이더 타입 및 효과
● Lambert
○ 디퓨즈(난반사) 조명을 계산하는 기본 조명 모델
○ 빛 밝기 = dot(노멀, 광원 방향)
○ 표면이 광원 정면일수록 밝음
● 씬 조명 Lambert (Unity 기준)
○ 씬에 배치된 Directional/Point Light 등을 기준으로 Lamber 조명 계산
○ 실제 조명 객체가 조명 방향과 색상 제공
○ WorldSpaceLightDir, _LightColor0 등으로 연산
● Toon Shading
○ 밝기를 계단식으로 나눠 만화 같은 느낌을 표현
○ 매끄럽지 않고 단계별로 끊기는 밝기
float brightness = dot(normal, lightDir);
brightness = brightness > 0.5 ? 1 : 0;
● Outline
○ 오브젝트 테두리에 외곽선을 그림
○ Backface 확대: 정반대 면을 확대한 뒤 검은색 렌더링
○ 스크린 스페이스: Depth/Normal Buffer 기반 테두리 감지
○ 경계를 강조해 셀 애니메이션 느낌 강화
■ 스크린 스페이스 아웃라인
● Depth와 Normal 텍스처를 기반으로 전체 화면에서 외곽선 추출
● 장점
○ 물체와 상관없이 겹침 없이 외곽선이 잘 보임
○ 반투명 오브젝트나 UI 겹침에도 강함
○ 여러 개체도 한 번에 처리 가능
■ Normal Map
● 표면의 디테일한 굴곡(노멀 방향 변화)을 가짜로 표현하는 텍스처
| 항목 | 설명 |
| 역할 | 픽셀마다 노멀 벡터를 덮어씌움 (메쉬 노멀을 덮는 역할) |
| 효과 | 표면이 울퉁불퉁해 보이게 조명 반응을 조작 |
| 장점 | 폴리곤 수를 늘리지 않고 디테일한 조명 표현 가능 (최적화) |
| 데이터 | RGB값으로 xyz 벡터 인코딩 (float3 normal) |

● 빛의 밝기 = 노멀 벡터와 광선 방향의 내적
float3 normal = normalize(IN.normal); // 표면의 노멀
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 광원의 방향 (방향광 기준)
//밝기.....
float brightness = saturate(dot(normal, lightDir));
● dot(normal, lightDir) → 1에 가까울수록 정면, 0이면 수직, 음수면 뒷면

[행렬]
■ 행렬을 쓰는 이유
● 여러 변환을 하나로 묶을 수 있음
○ 회전, 이동, 스케일 등을 하나의 행렬로 결합 가능 (곱셈)
● 선형 변환을 수학적으로 간결하게 표현
○ 변환을 벡터 연산으로 간단하게 처리 가능 (v' = M x v)
● GPU가 행렬 연산에 최적화
○ 벡터/행렬 곱은 병렬 처리에 적합함 (그래픽 처리 친화적)
● 좌표계 간 변환을 통일된 방식으로 처리
○ 모델 → 월드 → 뷰 → 클립 공간 모드 행렬 곱으로 연속 처리
● 거꾸로 변환(역변환)도 쉬움
○ 역행렬로 쉽게 반대로 되돌릴 수 있음
■ 행렬 연산
● 모든 단계가 행렬 x 벡터 곱 하나로 처리 가능
v_model → [Model Matrix] → v_world
→ [View Matrix] → v_view
→ [Projection Matrix] → v_clip
→ [Perspective divide + Viewport] → v_screen
■ 결론
● 행렬은 행렬끼리 곱해서 하나의 변환 행렬로 합칠 수 있다.
● 3D 그래픽에서 공간 변환을 효율적으로 처리하는 이유이다.
■ 예시: 여러 공간 변환을 행렬 하나로 결합
model → world → view → projection
● 행렬로 표현
MVP = Projection * View * World
● 최종 변환
float4 clipPos = mul(MVP, float4(localPos, 1.0));
■ 행렬 합치기의 실제 의미
| 변환 | 행렬 | 설명 |
| 모델 → 월드 | M (Model Matrix) | 오브젝트의 위치/회전/스케일 |
| 월드 → 카메라 | V (View Matrix) | 카메라 기준으로 바꾸는 변환 |
| 카메라 → 클립 | P (Projection Matrix) | 원근 투영 적용 |
■ 행렬과 아닌 경우의 차이
● 행렬 곱은 GPU에 최적화된 연산
○ v' = M * v 같은 벡터-행렬 곱 연산은 하드웨어에서 매우 빠르게 처리된다.
○ GPU는 벡터와 행렬 연산에 특화된 SIMD 구조를 사용한다.
● 직접 구현하면 복잡한 수학 연산 수십 배 증가
○ 스케일 + 회전 + 이동을 직접 수식으로 풀면 sin, cos, mul, add 등 연산이 급증한다.
○ 수학적으로는 4x4 행렬 곱 16회 연산이지만 직접 계산 시 30~50회 이상의 연산 + 여러 번의 중간 결과 저장이 필요하다.
● 병렬화 성능 저하
○ GPU는 간단한 연산을 병렬로 많이 하는 구조에 최적화되어 있다.
○ 직접 구현한 로직은 조건문과 중간 변수가 많아 병렬화가 비효율적이다.
[PBR 텍스처]
빛에 물리적으로 정확하게 반응하도록 설계된 머터리얼 시스템에서 사용하는 다양한 속성 맵 (Texture Map)
표면의 색, 거칠기, 반사, 메탈 느낌, 노멀 정보 등을 픽셀마다 구체적으로 지정함.
■ 대표적인 PBR(Physical Based Rendering) 텍스처와 역할
| 텍스처 | 설명 | 시각적 효과 |
| Albedo | 기본 색상 정보 | 표면의 기본 색 결정 |
| Normal | 픽셀 단위 노멀 벡터 (광원 반응 표현) | 빛 반응이 입체적으로 보이게 함 |
| Metallic | 금속 여부 (0 = 비금속, 1 = 금속) | 금속 표면은 반사, 비금속은 확산 |
| Roughness | 표면 거칠기 (빛의 번짐 정도) | 반사 번짐 정도 제어 |
| AO(Ambient Occlusion) | 그림자 표현용 | 환경관 차단 효과, 음영 강조 |
| Emissive | 자발광 영역 표현 | 야광/불빛 효과 구현 |
| Height/Parallax | 표면 높낮이 표현 (선택적 사용) | 깊이감/볼륨감 (주로 VR/특수효과용) |

[포워드 vs 디퍼드 렌더링]
■ Forward Rendering
● Vertex → Fragment → 조명 처리
● 각 픽셀에서 모든 광원을 계산
● 장점
○ 구조 단순 / 모바일에 적합
○ 반투명, 커스텀 쉐이더 용이
● 단점
○ 광원이 많아질수록 성능 저하
○ 스크린 이펙트 구현 어려움
■ Deffered Rendering
● G-Buffer에 위치, 노멀, 알베도 등 저장 후 Lighting Pass 실행
● 장점
○ 다수 조명 최적화 (O(1) 수준)
○ SSAO, SSR, Outline 등 스크린 효과 최적
● 단점
○ 반투명 처리 어렵고 MSAA와 호환 불편
○ 모바일/저사양에서 부담
■ 언제 어떤 걸 써야 할까?
| 상황 | 추천 방식 |
| 모바일, 반투명, 단일 조명 | 포워드 |
| 고성능 PC, 많은 조명, 스크린 이펙트 | 디퍼드 |
| 복잡한 씬 + 반투명 필요 | 하이브리드 or Custom 셰이더 |
'내일 배움 캠프 > 뭐하지' 카테고리의 다른 글
| 커스텀 에디터 (0) | 2025.06.04 |
|---|---|
| Cinemachine (0) | 2025.06.04 |
| UI 구성법 (0) | 2025.05.30 |
| 프로젝트 초기 설정 (1) | 2025.05.30 |
| 기획 테이블 활용 (0) | 2025.05.23 |