본문 바로가기

내일 배움 캠프/뭐하지

렌더링 파이프라인

[렌더링 파이프라인]

■ 렌더링이란?

    ● 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