작성일 :

머티리얼이란

Part 1에서 메쉬가 정점과 삼각형으로 오브젝트의 형태를 정의하는 구조를, Part 2에서 텍스처가 UV 좌표를 통해 표면에 색상과 디테일을 입히는 구조를 살펴보았습니다. 메쉬는 “어떤 모양인가”를 결정하고, 텍스처는 “표면에 입힐 이미지 데이터”를 담고 있습니다.


하지만 메쉬와 텍스처만으로는 렌더링을 완성할 수 없습니다.

같은 구(sphere) 메쉬에 같은 텍스처를 입히더라도, 표면이 빛을 금속처럼 반사할 수도 있고 천처럼 부드럽게 산란시킬 수도 있습니다. 메쉬의 형태 위에 텍스처를 어떤 방식으로 결합할 것인지, 빛이 표면에 닿았을 때 어떻게 반응할 것인지, 불투명하게 그릴 것인지 반투명하게 합성할 것인지 등을 결정하는 별도의 렌더링 규칙이 필요합니다.


이 렌더링 규칙을 정의하는 프로그램이 셰이더(Shader)이고, 특정 셰이더를 참조하면서 그 셰이더가 요구하는 파라미터 값들을 묶어 저장하는 데이터가 머티리얼(Material)입니다.

Part 1의 메쉬, Part 2의 텍스처, 그리고 이 글에서 다루는 머티리얼이 렌더링 파이프라인에 입력되는 세 가지 핵심 데이터입니다.

이 글에서는 머티리얼과 셰이더의 구조, 렌더 스테이트의 역할, 그리고 고정 파이프라인에서 프로그래머블 셰이더로의 발전 과정을 살펴봅니다.


머티리얼은 셰이더를 참조하면서, 셰이더가 요구하는 파라미터 값을 저장합니다.

셰이더 GPU에서 실행되는 프로그램 참조 머티리얼 텍스처들 색상 반사도 투명도 노멀 강도 기타


셰이더가 렌더링 규칙을 정의하고 머티리얼이 그 값을 저장하므로, 같은 메쉬에 서로 다른 머티리얼을 적용하면 동일한 형태의 오브젝트가 완전히 다른 외관을 갖게 됩니다.

예를 들어 구(sphere) 메쉬 하나에 금속 머티리얼을 적용하면 반짝이는 금속 구가 되고, 유리 머티리얼을 적용하면 투명한 유리 구가 됩니다.


같은 메쉬 + 같은 셰이더, 다른 파라미터 → 다른 외관 구 메쉬 (동일) + 셰이더: Standard Rendering Mode Opaque Metallic 0.9 Smoothness 0.8 색상 회색 반짝이는 금속 구 구 메쉬 (동일) + 셰이더: Standard Rendering Mode Transparent Metallic 0.1 Smoothness 1.0 색상 흰색 (Alpha 0.2) 투명한 유리 구 셰이더(렌더링 규칙)는 같지만 파라미터 값이 다르면 외관이 달라짐


머티리얼의 핵심은 셰이더이므로, 셰이더가 GPU 안에서 어떤 단계를 거쳐 동작하는지를 이해해야 렌더링 성능까지 파악할 수 있습니다.


셰이더의 역할

셰이더는 GPU에서 실행되는 프로그램입니다. CPU의 프로그램이 하나의 작업을 순차적으로 처리하는 것과 달리, GPU는 수천 개의 정점이나 수백만 개의 픽셀을 동시에 병렬 처리하도록 설계되어 있고, 셰이더는 이 병렬 구조 위에서 동작합니다.


셰이더는 역할에 따라 크게 두 종류로 나뉩니다. 하나는 3D 공간의 정점 좌표를 2D 화면 좌표로 변환하는 버텍스 셰이더(Vertex Shader), 다른 하나는 화면의 각 픽셀 색상을 결정하는 프래그먼트 셰이더(Fragment Shader) 입니다.

3D 정점 데이터 (위치, 법선, UV) 버텍스 셰이더 정점마다 한 번 실행 래스터라이저 (고정 기능) 프래그먼트 셰이더 픽셀마다 한 번 실행 2D 화면 픽셀 (최종 색상) 좌표 변환 법선 변환 정점 애니메이션 텍스처 샘플링 조명 계산 색상 결정

버텍스 셰이더 (Vertex Shader)

버텍스 셰이더는 Part 1에서 다룬 메쉬의 정점(Vertex)마다 한 번씩 실행됩니다.


버텍스 셰이더의 핵심 역할은 좌표 변환입니다. 메쉬의 정점은 원래 오브젝트 자체를 기준으로 한 로컬 좌표계(Local Space)에서 정의되어 있습니다. 로컬 좌표계란, 오브젝트의 중심을 원점(0, 0, 0)으로 놓고 각 정점의 위치를 표현한 좌표입니다.

버텍스 셰이더는 이 로컬 좌표를 월드 좌표로, 월드 좌표를 카메라 기준 좌표(뷰 좌표)로, 뷰 좌표를 클립 좌표로 변환하여 출력하고, 이후 GPU의 고정 기능이 원근 나눗셈과 뷰포트 변환을 거쳐 최종 화면 좌표를 산출합니다.


* 로컬 좌표 로컬 좌표 (모델 기준) Model Matrix * 월드 좌표 월드 좌표 (씬 기준) View Matrix * 뷰 좌표 뷰 좌표 (카메라 기준) Projection Matrix * 클립 좌표 클립 좌표 (Projection 적용 후) 오브젝트 자체 기준 씬 안에서의 절대 위치 카메라에서 바라본 위치 버텍스 셰이더 최종 출력 GPU 고정 기능이 이어받음 원근 나눗셈 → 뷰포트 변환 → 화면 좌표


이 변환은 행렬 곱셈으로 수행됩니다.

Model 행렬은 오브젝트의 위치, 회전, 스케일을 반영하여 로컬 좌표를 월드 좌표로 바꾸고, View 행렬은 카메라의 위치와 방향을 반영하여 뷰 좌표로 바꾸며, Projection 행렬은 원근(또는 직교) 투영을 적용하여 클립 좌표로 바꿉니다.

Unity에서는 이 세 행렬을 셰이더에 자동으로 전달하며, 셰이더 안에서 이를 조합한 MVP(Model-View-Projection) 변환을 수행합니다.


버텍스 셰이더의 핵심은 좌표 변환이지만, 정점을 처리하는 과정에서 다른 작업도 함께 수행합니다. 법선 벡터 변환(조명 계산에 필요), UV 좌표 전달(텍스처 매핑에 필요), 정점 애니메이션(바람에 흔들리는 나뭇잎, 물결 효과 등)이 대표적입니다. 특히 정점 애니메이션은 CPU가 아닌 GPU에서 수행되므로, 대량의 오브젝트에 적용해도 CPU 부하를 줄일 수 있습니다.

프래그먼트 셰이더 (Fragment Shader)

정점의 좌표 변환이 끝나고 화면 좌표가 확정되면, GPU의 고정 기능 하드웨어인 래스터라이저(Rasterizer)가 삼각형 내부를 화면 픽셀로 채웁니다(Part 1에서 다룬 래스터화 단계).

이때 채워진 각 픽셀 후보를 프래그먼트(Fragment)라고 부르며, 프래그먼트 셰이더는 이 프래그먼트마다 한 번씩 실행되어 최종 색상을 결정합니다.


래스터라이저 입력 (화면 좌표의 세 정점) v0 v1 v2 세 정점의 화면 좌표 래스터라이저 출력 (프래그먼트들) 삼각형 내부를 채운 픽셀(프래그먼트) 각 사각형마다 프래그먼트 셰이더 실행


프래그먼트 셰이더가 수행하는 작업은 크게 세 단계입니다. 텍스처 샘플링에서 텍스처의 색상을 읽어오고, 조명 계산에서 빛과 표면의 상호작용을 계산한 뒤, 최종 색상 결합에서 이 둘을 합쳐 프래그먼트의 RGBA 색상을 출력합니다.


1단계 — 텍스처 샘플링 래스터라이저는 버텍스 셰이더가 출력한 값들(UV 좌표, 법선 등)을 프래그먼트 위치에 맞게 보간하여 프래그먼트 셰이더에 전달합니다. 프래그먼트 셰이더는 이 중 보간된 UV 좌표를 사용하여 텍스처에서 해당 텍셀(texel) 색상을 읽어옵니다.


2단계 — 조명 계산 표면의 법선 벡터, 광원의 위치와 강도, 카메라의 위치 등을 종합하여 빛이 표면에 어떻게 반사되는지를 계산합니다. 대표적인 조명 성분은 확산 반사(Diffuse), 정반사(Specular), 주변광(Ambient) 세 가지입니다.

고급 조명 모델에서는 자체 발광(Emissive), 프레넬(Fresnel), 서브서피스 스캐터링(Subsurface Scattering) 등의 성분도 사용됩니다.

확산 반사(Diffuse)는 빛이 표면에 닿은 뒤 모든 방향으로 균일하게 퍼지는 반사입니다. Part 1에서 다룬 법선 벡터와 광원 방향 사이의 각도(cos θ)가 이 값을 결정하며, 오브젝트의 기본적인 밝고 어두운 면을 만들어 냅니다.

정반사(Specular)는 빛이 특정 방향으로 집중 반사되어 표면에 밝은 하이라이트를 만드는 성분입니다. 확산 반사는 법선과 광원 방향만으로 계산되므로 카메라 위치와 무관하지만, 정반사는 반사된 빛이 카메라를 향할 때만 밝게 보이므로 카메라가 이동하면 하이라이트 위치도 함께 달라집니다(시점 의존, View-Dependent).

주변광(Ambient)은 직접 광원 없이도 환경에서 간접적으로 도달하는 빛입니다. 직사광선이 닿지 않는 그림자 영역이 완전히 검게 보이지 않는 이유가 이 주변광 때문입니다.


3단계 — 최종 색상 결합 프래그먼트 셰이더는 세 조명 성분을 합산한 조명 값에 텍스처 색상을 결합하고, 머티리얼에 설정된 색상이나 기타 파라미터를 반영하여 프래그먼트의 RGBA 색상을 출력합니다.


1) 텍스처 샘플링 UV 좌표 텍스처에서 텍셀 읽기 텍스처 색상 2) 조명 계산 법선 벡터 광원 정보 카메라 위치 확산 + 정반사 + 주변광 조명 값 3) 최종 색상 결정 텍스처 색상 조명 값 머티리얼 파라미터 (색상, 반사도 등) 결합 최종 RGBA 출력


프래그먼트 셰이더가 더 비싼 이유

버텍스 셰이더와 프래그먼트 셰이더를 비교하면, 대부분의 장면에서 프래그먼트 셰이더가 GPU 시간을 더 많이 소비합니다. 가장 큰 원인은 실행 횟수의 차이입니다.

삼각형 하나를 예로 들면, 정점이 3개이므로 버텍스 셰이더는 3번 실행됩니다. 같은 삼각형이 화면에서 1,000픽셀을 차지한다면, 프래그먼트 셰이더는 1,000번 실행됩니다.


일반적인 3D 오브젝트에서 화면에 그려지는 프래그먼트 수는 정점 수보다 수십~수백 배 많습니다. 1080p 해상도(1920x1080) 기준으로 화면 전체를 덮는 오브젝트는 약 207만 개의 프래그먼트를 생성합니다.

여기에 프래그먼트 셰이더는 텍스처 샘플링과 조명 계산 등 버텍스 셰이더의 행렬 곱셈보다 복잡한 연산을 수행하는 경우가 많으므로, 실행 횟수와 연산 복잡도가 함께 작용하여 비용이 커집니다.

프래그먼트 셰이더에 연산 하나를 추가하면 그 연산이 207만 번 반복되는 셈이므로, 프래그먼트 셰이더의 복잡도를 줄이는 것은 렌더링 성능 최적화에서 가장 효과가 큰 영역 중 하나입니다.


렌더 스테이트 (Render State)

셰이더는 정점의 좌표를 변환하고 프래그먼트의 색상을 계산하지만, GPU가 오브젝트를 그리려면 뒷면 삼각형의 제거, 겹치는 오브젝트 간의 깊이 비교, 투명 오브젝트의 색상 합성 같은 추가 처리도 필요합니다.

렌더 스테이트(Render State)는 이러한 처리를 GPU 파이프라인이 어떻게 수행할지를 제어하는 설정입니다. 블렌딩, 깊이 테스트, 컬링, 스텐실 테스트 등이 렌더 스테이트에 포함됩니다.

블렌딩 (Blending)

GPU가 새 프래그먼트를 그릴 때, 해당 픽셀의 컬러 버퍼에는 이미 다른 색상이 기록되어 있을 수 있습니다. 불투명 오브젝트는 기존 색상을 완전히 덮어쓰면 되지만, 반투명 유리나 연기 같은 오브젝트는 새 색상과 기존 색상을 섞어야 합니다. 이때 새 색상(source)과 기존 색상(destination)을 합성하는 연산이 블렌딩입니다.

알파 블렌딩의 기본 공식은 결과 = source × α + destination × (1 - α) 입니다. α가 1이면 새 색상만 남고(불투명), 0이면 기존 색상만 남습니다(완전 투명).


불투명 (Blend Off) dst 파란 배경 + src 빨간 오브젝트 빨강 완전 덮어쓰기 반투명 (α = 0.5) dst 파란 배경 + src 빨간 유리 보라 src × 0.5 + dst × 0.5


깊이 테스트 (Depth Test / Z-Buffer)

깊이 테스트는 같은 픽셀 위치에 여러 오브젝트가 겹칠 때, 어떤 오브젝트가 앞에 있는지를 판별하는 처리입니다.

GPU는 Z-buffer(깊이 버퍼)라는 별도의 버퍼를 유지하며, 각 픽셀마다 현재까지 그려진 가장 가까운 프래그먼트의 깊이 값을 저장합니다. 깊이 값은 카메라로부터의 거리를 나타내며, 값이 작을수록 카메라에 가깝습니다.

새 프래그먼트가 도달하면 GPU는 해당 픽셀의 Z-buffer 값과 비교합니다. 더 가까우면(깊이 값이 더 작으면) 컬러 버퍼에 기록하고 Z-buffer를 갱신합니다. 더 멀면 이미 앞에 다른 오브젝트가 있으므로 폐기합니다.

한 픽셀(P)에 세 오브젝트가 겹치는 경우:

단계 오브젝트 깊이 비교 결과 컬러 버퍼[P] Z-buffer[P]
초기 상태 비어 있음
빨간 상자 (깊이 7) 7 < ∞ 통과 빨강 7
파란 구 (깊이 3) 3 < 7 통과 (더 가까움) 파랑 3
초록 벽 (깊이 9) 9 < 3 실패 (더 멀음) 파랑 (변경 없음) 3

최종 결과: 파란 구의 색상이 화면에 표시됨 (깊이 값이 가장 작은 오브젝트)


컬링 (Culling)

컬링은 렌더링할 필요가 없는 요소를 미리 제외하는 처리입니다. 렌더 스테이트에서 제어하는 컬링은 백 페이스 컬링(Back-face Culling)으로, 카메라를 향하지 않는 삼각형의 뒷면을 그리지 않는 설정입니다.

삼각형은 평면이므로 바라보는 방향에 따라 두 면이 존재합니다. 같은 삼각형이라도 한쪽에서 보면 정점이 시계 방향으로 나열되고, 반대쪽에서 보면 반시계 방향으로 나열됩니다. GPU는 이 나열 순서로 앞뒤를 구분합니다.

Unity의 기본 규칙은 카메라에서 바라볼 때 정점이 시계 방향(CW)이면 앞면, 반시계 방향(CCW)이면 뒷면입니다.


카메라에서 바라본 정점 나열 순서로 앞면/뒷면을 구분 v0 v2 v1 시계 방향(CW) = 앞면 렌더링 v0 v2 v1 반시계 방향(CCW) = 뒷면 컬링(제외) 닫힌 오브젝트(구, 큐브)에 적용 카메라 앞면 (≈ 50%) 렌더링 뒷면 (≈ 50%) 컬링(제외) 렌더링 대상 삼각형 약 50% 감소


큐브나 구처럼 표면이 닫힌 오브젝트는 뒷면이 카메라에 보이지 않습니다. 백 페이스 컬링을 활성화하면 이 뒷면을 구성하는 삼각형을 건너뛰므로, 렌더링 대상이 대략 절반으로 줄어듭니다. 다만, 종이처럼 양면이 모두 보여야 하는 오브젝트에서는 컬링을 비활성화해야 합니다.

스텐실 테스트 (Stencil Test)

스텐실(Stencil)은 형판을 뜻합니다. 형판을 대고 칠하면 뚫린 부분만 색이 입혀지듯, 스텐실 테스트는 화면에서 렌더링할 영역과 제외할 영역을 픽셀 단위로 지정합니다.

이를 위해 GPU는 스텐실 버퍼(Stencil Buffer)에 각 픽셀의 정수 값(보통 0~255)을 관리합니다. 먼저 특정 오브젝트를 그리면서 해당 픽셀에 값을 기록해 두고, 이후 다른 오브젝트를 그릴 때 이 값을 조건으로 통과 여부를 결정하는 방식입니다.

포털 효과를 예로 들면, 먼저 포털 형태의 메쉬를 그리면서 해당 픽셀의 스텐실 값을 1로 표시합니다. 이후 다른 씬을 렌더링할 때 “스텐실 값이 1인 픽셀에만 그려라”는 조건을 설정하면, 포털 영역 안에서만 다른 씬이 보입니다.

1) 포털 메쉬를 그리며 스텐실 버퍼에 값 기록 스텐실 버퍼 상태 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 포털 메쉬 영역 2) 스텐실 = 1인 픽셀만 렌더링 화면 결과 일반 씬 일반 씬 다른 씬이 포털 안에만 보임 스텐실 = 1 영역만 렌더링

렌더 스테이트 변경의 비용

GPU는 현재 렌더 스테이트에 맞춰 파이프라인을 구성합니다. 스테이트가 바뀌면 파이프라인을 재구성해야 하므로, 이 스테이트 변경(State Change)이 잦을수록 렌더링 성능이 떨어집니다.


비효율적인 순서 오브젝트 A: 불투명 변경 오브젝트 B: 투명 변경 오브젝트 C: 불투명 변경 오브젝트 D: 투명 스테이트 변경: 3회 효율적인 순서 불투명 오브젝트 모아서 그리기 오브젝트 A: 불투명 오브젝트 C: 불투명 변경 1회 투명 오브젝트 모아서 그리기 오브젝트 B: 투명 오브젝트 D: 투명 스테이트 변경: 1회


같은 렌더 스테이트를 사용하는 오브젝트끼리 모아서 그리면 스테이트 변경 횟수를 줄일 수 있습니다.


이에 따라 대부분의 렌더링 엔진은 불투명 오브젝트를 먼저 모두 그린 뒤, 투명 오브젝트를 그립니다.

불투명 오브젝트는 깊이 테스트만으로 앞뒤가 결정되므로 어떤 순서로 그려도 시각적 결과는 같습니다. 이 점을 활용하여 카메라에서 가까운 순서(front-to-back)로 정렬하면, 가까운 오브젝트가 먼저 깊이 버퍼를 채워 뒤에 가려지는 프래그먼트를 조기에 폐기할 수 있어 불필요한 셰이딩이 줄어듭니다.

투명 오브젝트는 반대로 먼 순서(back-to-front)로 그립니다. 알파 블렌딩은 컬러 버퍼에 이미 기록된 색상 위에 새 색상을 합성하므로, 뒤에 있는 오브젝트의 색상이 먼저 기록되어 있어야 올바른 결과가 나옵니다.


고정 파이프라인에서 프로그래머블 셰이더로

지금까지 설명한 버텍스 셰이더와 프래그먼트 셰이더는 조명 모델, 정점 변형, 시각 효과 등을 개발자가 직접 프로그래밍할 수 있는 구조입니다.

하지만 초기 GPU는 조명이나 텍스처 처리 방식이 하드웨어에 고정되어 있어, 정해진 방식 외의 렌더링은 불가능했습니다. 이 제약을 넘어서며 등장한 것이 프로그래머블 셰이더입니다.


고정 기능 파이프라인 (Fixed Function Pipeline)

초기 GPU(1990년대)의 고정 기능 파이프라인에서 개발자가 제어할 수 있는 것은 광원 수, 텍스처 모드 같은 몇 가지 파라미터뿐이었습니다. 조명 계산이나 텍스처 합성의 알고리즘 자체를 바꿀 수는 없었습니다.


정점 데이터 좌표 변환 (고정) 위치, 회전, 스케일 값만 설정 가능 조명 계산 (고정) 광원 위치, 색상, 개수만 설정 가능 텍스처 합성 (고정) 블렌딩 모드, 안개(Fog)만 설정 가능 화면 모든 단계의 알고리즘이 하드웨어에 고정 파라미터 조절만 가능, 알고리즘 변경 불가


이 구조는 단순하고 하드웨어 최적화에 유리했지만, 표현의 한계가 분명했습니다. 모든 게임이 같은 조명 모델을 사용할 수밖에 없었으므로, 시각적 차별화가 어려웠습니다.

프로그래머블 셰이더의 등장

2000년 말 DirectX 8.0이 발표되고, 2001년 NVIDIA GeForce 3가 출시되면서 프로그래머블 셰이더가 도입되었습니다. 고정되어 있던 정점 처리와 픽셀 처리 단계를 개발자가 직접 프로그래밍할 수 있게 되었습니다.

정점 데이터 버텍스 셰이더 개발자가 작성하는 프로그램 (프로그래머블) 좌표 변환, 법선 처리, 정점 변형 등을 자유롭게 구현 래스터라이저 (고정 기능) 하드웨어 고정 (변경 불가) 프래그먼트 셰이더 개발자가 작성하는 프로그램 (프로그래머블) 조명 모델, 텍스처 합성, 색상 계산 등을 자유롭게 구현 화면 고정 파이프라인과의 차이: 파라미터 조절만 가능 → 알고리즘 자체를 교체 가능


이 변화 덕분에 만화 느낌의 툰 셰이딩(Toon Shading), 물리 법칙 기반의 사실적 표면을 표현하는 PBR(Physically Based Rendering), 복잡한 형상을 실시간으로 그리는 레이 마칭(Ray Marching) 같은 기법이 가능해졌습니다.

셰이더 언어의 발전

초기에는 어셈블리에 가까운 저수준 명령어로 셰이더를 작성했지만, 이후 C 언어와 유사한 문법의 고수준 셰이더 언어가 등장했습니다.

시기 언어 / 기술 특징
~2001 어셈블리 셰이더 레지스터 직접 조작
2002~ HLSL (DirectX) C 유사 문법
  Cg (NVIDIA) 크로스 API 대응
2004~ GLSL (OpenGL) C 유사 문법
2014~ Metal Shading Language Apple 플랫폼 전용
2016~ SPIR-V (Vulkan) 셰이더 바이트코드 표준


Unity의 셰이더 시스템도 이 흐름을 따라 발전했습니다.

Unity는 셰이더의 구조(패스, 렌더 스테이트, 프로퍼티 등)를 선언하는 ShaderLab이라는 고유의 기술 언어를 사용합니다.

ShaderLab 안에 셰이더 코드를 작성하는 방식은 시대에 따라 변화했습니다. 초기에는 고정 파이프라인 명령을 나열하는 고정 함수 셰이더 방식이었지만, 이후 CgHLSL로 버텍스/프래그먼트 셰이더를 직접 작성하는 방식으로 전환되었습니다.

현재는 HLSL이 표준이며, 노드 기반으로 셰이더를 구성하는 Shader Graph도 제공됩니다. Shader Graph도 내부적으로 HLSL 코드를 생성하므로, 최종 결과는 코드로 작성한 셰이더와 동일합니다.


고정 함수 셰이더 (레거시) 고정 파이프라인 시절 ShaderLab + Cg 프로그래머블 셰이더 초기 ShaderLab + HLSL (현재 표준) HLSL 코드 직접 작성 (코드 기반) Shader Graph (노드 기반, HLSL 자동 생성) 최종 결과는 모두 HLSL 셰이더

Unity의 머티리얼 시스템

앞서 다룬 머티리얼과 셰이더의 일반 구조가 Unity에서는 어떻게 구현되는지 살펴봅니다.


머티리얼과 셰이더의 관계

Unity에서 머티리얼은 하나의 셰이더를 참조하고, 그 셰이더가 정의한 프로퍼티 값을 저장합니다. Inspector 창에서 텍스처, 색상, 수치 등을 설정하면, 렌더링 시 이 값들이 셰이더에 전달됩니다.


머티리얼 A _MainTex 벽돌 텍스처 _Metallic 0.0 _Smoothness 0.3 _BumpMap 벽돌 노멀 맵 머티리얼 B _MainTex 금속 텍스처 _Metallic 0.9 _Smoothness 0.7 _BumpMap 금속 노멀 맵 Standard Shader 같은 셰이더, 다른 프로퍼티 → 별도 머티리얼


프로퍼티 값이 하나라도 다르면 별도의 머티리얼로 취급되어 드로우콜이 분리됩니다. 같은 셰이더와 프로퍼티를 사용하는 오브젝트라면 머티리얼을 통합하여 드로우콜을 줄일 수 있습니다.

머티리얼과 드로우콜

오브젝트를 렌더링할 때, 머티리얼이 바뀌면 CPU는 셰이더, 렌더 스테이트, 프로퍼티를 GPU에 새로 설정해야 합니다. 이 상태 전환을 그래픽스 API에서는 파이프라인 상태 변경(state change)이라 부르며, Unity에서는 셋 패스 콜(SetPass Call)이라는 고유 용어로 부릅니다.

상태가 설정된 후 CPU가 메쉬, 변환 행렬, 오브젝트별 라이팅 데이터(라이트 프로브 계수, 라이트맵 UV 등)를 지정하여 GPU에 그리라는 명령을 보내는 것이 드로우콜(Draw Call)입니다.


드로우콜마다 CPU는 오브젝트별 데이터를 설정하고 커맨드 버퍼(Command Buffer)에 명령을 기록해야 합니다. 오브젝트가 수백~수천 개로 늘어나면 드로우콜 수도 함께 증가하여 CPU 측 병목이 될 수 있습니다.


머티리얼이 모두 다를 때 오브젝트 1 : 머티리얼 A 드로우콜 1 오브젝트 2 : 머티리얼 B 드로우콜 2 오브젝트 3 : 머티리얼 C 드로우콜 3 오브젝트 4 : 머티리얼 D 드로우콜 4 합계: 드로우콜 4회 같은 머티리얼을 공유할 때 (배칭 가능) 오브젝트 1 : 머티리얼 A 오브젝트 2 : 머티리얼 A 오브젝트 3 : 머티리얼 A 드로우콜 1 (배칭) 오브젝트 4 : 머티리얼 B 드로우콜 2 합계: 드로우콜 2회


배칭(Batching)은 동일한 머티리얼을 사용하는 여러 오브젝트의 메쉬를 합쳐 더 적은 수의 드로우콜로 그리는 기법입니다.


머티리얼이 다르면 셋 패스 콜(상태 전환)이 늘고 배칭 기회도 줄어들므로, 텍스처 아틀라스(Texture Atlas)(여러 텍스처를 하나로 합치는 기법)를 사용하거나 유사한 오브젝트끼리 같은 머티리얼을 공유하도록 설계하여 머티리얼 수를 줄이는 것이 성능 최적화의 기본입니다.

셰이더 변형 (Shader Variants)

머티리얼마다 사용하는 기능 조합이 다를 수 있습니다. 예를 들어 노멀 맵을 쓰는 머티리얼은 노멀 맵 샘플링 코드가 포함된 셰이더가 필요하고, 쓰지 않는 머티리얼은 해당 코드가 빠진 셰이더가 필요합니다.

Unity는 이런 조합마다 별도의 셰이더 파일을 작성하는 대신, 하나의 셰이더 소스에 키워드를 정의하고 조합별로 다른 버전을 컴파일합니다. 이 각각의 컴파일 결과를 셰이더 변형(Shader Variant)이라 합니다.


Standard Shader _NORMALMAP ON + _METALLIC ON 변형 1 _NORMALMAP ON + _METALLIC OFF 변형 2 _NORMALMAP OFF + _METALLIC ON 변형 3 _NORMALMAP OFF + _METALLIC OFF 변형 4 키워드 2개 x 각 2가지 옵션 = 4개 변형 키워드가 N개이면 최대 2^N개 변형이 생성될 수 있음


위 예시에서는 키워드가 2개뿐이지만, 실제 셰이더는 그림자, 안개, 라이트맵 등 다양한 키워드를 포함하므로 변형 수가 수백~수천 개로 급증할 수 있습니다.

변형이 많아지면 빌드 시간과 메모리 사용량이 늘어나고, 빌드 시점에 미리 컴파일되지 않은 변형이 런타임에 처음 사용되면 그 자리에서 셰이더 컴파일이 발생하여 순간적인 끊김이 생길 수 있습니다.


이를 관리하기 위해 사용하지 않는 키워드를 제거하거나, 셰이더 변형 스트리핑으로 불필요한 변형을 빌드에서 제거하여 변형 수와 빌드 크기를 줄입니다. 런타임에 필요한 변형은 Shader Variant Collection으로 미리 컴파일(프리워밍)해 두면 끊김을 방지할 수 있습니다.


메쉬, 텍스처, 머티리얼의 관계 — 렌더링의 입력 데이터

Part 1부터 이 글까지 세 편에 걸쳐 렌더링의 입력 데이터를 구성하는 세 가지 요소를 살펴보았습니다.


오브젝트 메쉬 정점 인덱스 UV 법선 탄젠트 버텍스 컬러 ... 형태를 정의 머티리얼 셰이더 버텍스 프래그먼트 파라미터 텍스처 색상 반사도 ... 외관을 결정 GPU로 전달 렌더링 파이프라인


메쉬는 정점, 인덱스, UV, 법선, 탄젠트, 버텍스 컬러 등으로 오브젝트의 형태를 정의합니다.

텍스처는 표면의 색상, 노멀, 러프니스 등을 2D 이미지로 담는 데이터입니다.

머티리얼은 셰이더와 파라미터(텍스처 포함)를 묶어 메쉬 표면의 외관을 정의합니다.


렌더링 파이프라인에는 이 세 가지 외에도 트랜스폼, 카메라, 라이팅 데이터 등이 입력되지만, 이 세 가지가 오브젝트의 시각적 결과물을 구성하는 핵심 에셋입니다.


마무리

머티리얼은 셰이더와 파라미터를 묶어 메쉬 표면의 외관을 정의합니다. 이 글에서는 셰이더의 구조, 렌더 스테이트, 드로우콜의 관계를 살펴보았습니다.

이 글에서 다룬 핵심을 정리하면 다음과 같습니다.

  • 머티리얼은 셰이더(GPU 프로그램)와 파라미터(텍스처, 색상, 반사도 등)를 묶은 단위입니다
  • 셰이더는 버텍스 셰이더(정점 좌표 변환)와 프래그먼트 셰이더(픽셀 색상 계산)로 나뉘며, 프래그먼트 수가 정점 수보다 수십~수백 배 많아 프래그먼트 셰이더의 복잡도가 성능에 직접적인 영향을 줍니다
  • GPU는 셰이더 외에도 블렌딩, 깊이 테스트, 스텐실 테스트, 컬링 같은 렌더 스테이트 설정이 필요하며, 스테이트 변경이 잦을수록 성능이 떨어집니다
  • 셰이더는 고정 기능 파이프라인에서 프로그래머블 셰이더로 발전하여, 개발자가 조명 모델과 시각 효과를 직접 구현할 수 있게 되었습니다
  • 머티리얼이 바뀌면 셋 패스 콜(상태 전환 명령)이 발생하고, 각 오브젝트마다 드로우콜(그리기 명령)이 발생합니다
  • 같은 머티리얼을 공유하는 오브젝트끼리 배칭으로 묶으면 드로우콜을 줄일 수 있습니다
  • 셰이더 변형은 키워드 조합에 따라 급증할 수 있으므로, 불필요한 변형을 제거하고 필요한 변형을 프리워밍하여 관리합니다

세 편에 걸쳐 살펴본 메쉬, 텍스처, 머티리얼/셰이더는 렌더링 파이프라인에 입력되는 핵심 에셋입니다. 다음 시리즈에서는 이 데이터를 실제로 처리하는 GPU의 내부 구조를 살펴봅니다.



관련 글

시리즈

전체 시리즈

Tags: Unity, 머티리얼, 모바일, 셰이더, 최적화

Categories: ,