Shader Sub-Graph로 구축하는 범용 재질 라이브러리: 금속·유리·피부를 한 구조로 관리하기

Unity Shader Graph의 Sub-Graph를 활용해 금속, 유리, 피부 재질의 공통 로직과 전용 기능을 분리하는 방법을 정리한다. PBR 파라미터 설계, 성능 관리, 검증 절차까지 실무에 바로 적용할 수 있다.

✓ 복사 완료!
챕터셰이더 프로그래밍 (HLSL/ShaderGraph)
난이도중급
상태● 검토 완료
대상 엔진
Engine Agnostic
최종 업데이트
Shader Sub-Graph로 구축하는 범용 재질 라이브러리: 금속·유리·피부를 한 구조로 관리하기

핵심 요약

Shader Sub-Graph 기반 범용 재질 라이브러리는 금속, 유리, 피부에 반복되는 PBR 입력 처리·마스크·디테일 합성·품질 분기를 공통화하고 각 재질의 고유 광학 특성만 별도 Sub-Graph로 분리하는 방식이다. Unity Shader Graph에서는 Base Color, Metallic, Smoothness, Normal, Occlusion, Emission, Alpha 같은 표준 입력을 일관되게 조립해 두면 재질 품질과 유지보수성을 함께 확보할 수 있다.

왜 범용 재질 라이브러리가 필요한가?

프로젝트 초반에는 재질별 Shader Graph를 독립적으로 만드는 편이 빠르게 보인다. 그러나 재질 수가 늘면 같은 기능이 여러 그래프에 복제된다. 예를 들어 노멀 강도 조절, 마스크 기반 러프니스 변화, 색조 변형, 거리 기반 디테일 제거 같은 기능은 금속과 피부, 유리에도 반복해서 등장한다.

이 구조는 다음 문제를 만든다.

문제개별 그래프 방식Sub-Graph 라이브러리 방식
기능 수정모든 셰이더를 찾아 수정공통 Sub-Graph 한 곳 수정
파라미터 이름아티스트마다 달라지기 쉬움명명 규칙을 강제 가능
품질 옵션재질마다 분기가 달라짐공통 품질 정책 적용
디버깅그래프마다 원인을 추적입력·출력 계약을 기준으로 추적
성능 관리중복 연산을 놓치기 쉬움공통 비용을 한 번에 점검

다만 “범용”은 모든 재질을 하나의 거대한 그래프에 넣는다는 뜻이 아니다. 공통 기능은 재사용하고 물리적으로 다른 표현은 분리해야 한다.

Shader Sub-Graph 라이브러리는 어떻게 설계할까?

1. 공통 입력 계약부터 고정한다

먼저 모든 재질이 공유할 입력 이름과 값의 범위를 정한다. Shader Graph의 Blackboard 속성명과 C# Material.SetFloat() 호출에 쓰는 참조 이름도 이 규칙을 따른다.

입력권장 범위용도
BaseColor0~1 RGB기본 알베도 또는 틴트
Mask0~1마모, 오염, 영역 분리
NormalStrength0~2노멀맵 강도
Smoothness0~1표면의 정반사 선명도
DetailTiling양수디테일 맵 반복도
DetailStrength0~1디테일 합성 강도
QualityLevel0~2저·중·고 품질 분기

URP Lit 계열의 Smoothness는 보통 값이 높을수록 표면이 매끈하다. 반대로 거칠기 값을 직접 다루고 싶다면 변환 규칙을 하나로 통일해야 한다.

roughness=1−smoothnessroughness = 1 - smoothness

Roughness와 Smoothness를 섞어 쓰면 텍스처 작성과 머티리얼 튜닝에서 반전 실수가 반복된다. 프로젝트 전체에서는 한 표현을 기준값으로 정하고 필요한 변환만 Sub-Graph 내부에서 처리하는 편이 안전하다.

2. 공통 Sub-Graph를 작게 나눈다

권장하는 최소 구성은 다음과 같다.

SG_NormalIntensity
SG_MaskRemap
SG_DetailBlend
SG_SmoothnessFromMask
SG_DistanceFade
SG_FresnelMask

각 Sub-Graph는 한 가지 책임만 가져야 한다. 예를 들어 SG_NormalIntensity는 노멀맵 샘플링과 강도 보정만 담당하고 금속의 스크래치나 피부의 모공 판단까지 포함하지 않는다.

SG_MaskRemap은 마스크 입력값을 아티스트가 다루기 쉬운 범위로 재매핑하는 데 유용하다.

output=saturate(input×scale+bias)output = saturate(input \times scale + bias)

Saturate는 값을 0과 1 사이로 제한한다. 마스크가 예기치 않게 범위를 벗어나서 금속도가 1보다 커지거나 알파가 음수가 되는 문제를 줄일 수 있다.

3. 재질 전용 Sub-Graph는 광학 특성에 집중한다

금속, 유리, 피부는 공통 PBR 입력을 사용하더라도 강조해야 할 현상이 다르다.

flowchart TD
  A[Material Master Graph] --> B[공통 입력과 품질 제어]
  B --> C[SG Detail Blend]
  B --> D[SG Normal Intensity]
  B --> E[SG Mask Remap]
  C --> F[Metal Surface]
  D --> F
  E --> F
  C --> G[Glass Surface]
  D --> G
  E --> G
  C --> H[Skin Surface]
  D --> H
  E --> H
  F --> I[Lit Master Stack]
  G --> I
  H --> I

이 구조에서 Master Graph는 최종 조립과 머티리얼별 기능 선택만 담당한다. 복잡한 수식과 텍스처 조합은 Sub-Graph 안으로 숨겨 그래프를 읽기 쉽게 유지한다.

공통 Sub-Graph와 금속 유리 피부 전용 Sub-Graph의 연결 구조를 보여주는 Shader Graph 화면

금속 셰이더는 어떤 Sub-Graph가 필요한가?

금속은 비금속과 달리 반사색이 재질 고유의 색을 가진다. 일반적인 금속 표면에서는 Metallic을 1에 가깝게 두고 스크래치·산화·도장 벗겨짐 마스크로 금속도와 스무스니스를 변화시키는 방식이 실용적이다.

SG_MetalWear의 입출력 예시

입력출력설명
WearMaskMetallic마모 영역을 비금속 또는 노출 금속으로 전환
WearMaskSmoothness긁힘 영역의 정반사 흐림 조절
ScratchNormalNormal방향성 스크래치 노멀 합성
OxidationColorBase Color산화 또는 오염 색상 오버레이

도장된 철제 소품이라면 도장 영역은 Metallic = 0, 벗겨진 철 영역은 Metallic = 1로 두는 편이 자연스럽다. 스크래치가 있다는 이유만으로 전체 금속도를 높이면 플라스틱 도장면까지 금속처럼 보일 수 있다.

// Custom Function Node에서 사용할 수 있는 마모 마스크 기반 보간 예시
void MetalWear_float(float wearMask, float paintedSmoothness, float exposedSmoothness,
                     out float metallic, out float smoothness)
{
    metallic = saturate(wearMask);
    smoothness = lerp(paintedSmoothness, exposedSmoothness, wearMask);
}

유리 셰이더는 투명도와 반사를 어떻게 함께 처리할까?

유리는 투명도만 낮춘 표면이 아니다. 프레넬 반사, 두께에 따른 색 흡수, 왜곡, 렌더 파이프라인의 투명 객체 정렬까지 고려해야 한다. URP Shader Graph에서 투명 표면은 일반적으로 Graph Inspector의 Surface Type을 Transparent로 설정한다.

유리 표현의 우선순위

  1. 프레넬 반사: 시선 각도가 얕을수록 반사 비중을 높인다.
  2. 투과 색상: 얇은 유리와 두꺼운 유리의 색 차이를 표현한다.
  3. 왜곡: 노멀맵 또는 화면 색상 샘플링을 사용할 때 비용과 호환성을 점검한다.
  4. 정렬 문제: 투명 객체끼리 겹칠 때의 렌더 순서를 별도로 검증한다.

Schlick 근사식은 프레넬 반사를 가볍게 제어할 때 자주 사용된다.

F=F0+(1−F0)(1−V⋅H)5F = F_0 + (1 - F_0)(1 - V\cdot H)^5

여기서 F0는 정면 반사율, V는 뷰 방향, H는 하프 벡터다. Shader Graph에서는 Fresnel Effect 노드를 기본 마스크로 사용하고 아트 디렉션에 맞춰 Power와 색을 조절하는 편이 구현과 튜닝 모두 간단하다.

화면 색상 기반 굴절은 파이프라인 설정과 카메라 텍스처에 의존한다. 특히 모바일이나 다수의 투명 오브젝트가 있는 장면에서는 기본 품질 옵션에서 제외하고 영웅 오브젝트에만 제한적으로 켜는 구성이 좋다.

프레넬 반사와 노멀 기반 왜곡이 적용된 반투명 유리 재질 비교 이미지

피부 셰이더에서 Subsurface Scattering은 어떻게 근사할까?

피부는 빛이 표면 아래로 일부 들어갔다가 주변으로 퍼져 나오는 특성 때문에 단순한 Lambert 또는 기본 Lit 조명만으로는 밀랍처럼 보이기 쉽다. 실제 Subsurface Scattering(SSS) 구현은 파이프라인과 예산에 따라 달라지므로 범용 라이브러리에서는 먼저 가벼운 근사 기능을 제공하는 것이 현실적이다.

SG_SkinTransmission의 간단한 근사

빛이 뒤쪽에서 들어오는 영역을 마스크로 만들고 얇은 부위에 붉은 계열의 투과광을 더한다.

T=saturate((−L⋅V)×thickness)powerT = saturate((-L \cdot V) \times thickness)^{power}
  • L: 광원 방향
  • V: 카메라 방향
  • thickness: 귀, 콧등, 손가락 등 얇은 부위를 지정하는 두께 마스크
  • power: 투과광이 나타나는 가장자리를 조절하는 지수

이 값을 Emission에 직접 더하는 방식은 구현은 쉽지만 조명 환경과 분리되어 과하게 빛날 수 있다. 가능하다면 엔진이 제공하는 SSS 또는 Transmission 기능을 우선 사용하고 근사식은 저사양 품질 단계나 스타일라이즈드 캐릭터에 한정하는 편이 낫다.

피부용 공통 기능으로는 모공 노멀, T존과 볼의 스무스니스 차이, 혈색 마스크, 메이크업 레이어가 자주 필요하다. 이 중 모공 노멀과 마스크 재매핑은 다른 유기 재질에도 재사용할 수 있으므로 공통 Sub-Graph로 유지한다.

범용 Shader Sub-Graph의 성능은 어떻게 관리할까?

Sub-Graph를 쓴다고 런타임 호출 비용이 별도로 생기는 것은 아니다. Shader Graph는 최종 셰이더 코드로 생성되므로 핵심은 Sub-Graph의 개수가 아니라 최종 생성 코드의 텍스처 샘플 수, 분기, 연산량이다.

점검 항목권장 기준
텍스처 샘플동일 텍스처를 여러 Sub-Graph에서 중복 샘플링하지 않기
키워드기능 조합마다 키워드를 늘리지 말고 꼭 필요한 기능만 Keyword로 분리
분기픽셀별 조건문보다 정적 키워드 또는 마스크 보간을 우선 검토
투명 재질오버드로와 정렬 비용을 프로파일링
노멀 합성여러 노멀맵을 합칠 때 정확한 노멀 블렌드 함수 사용
LOD원거리에서 디테일 맵, 굴절, 피부 투과를 단계적으로 제거

특히 Boolean Keyword를 남용하면 셰이더 변형(variant)이 빠르게 늘어난다. 예를 들어 독립적인 기능 6개를 각각 온·오프 키워드로 만들면 이론상 조합 수는 다음과 같다.

variants=26=64variants = 2^6 = 64

모든 조합이 실제 빌드에 포함되는 것은 아니더라도 플랫폼과 조명 옵션이 더해지면 컴파일 시간과 빌드 크기에 영향을 준다. 공통 라이브러리에서는 “모든 기능을 선택 가능하게” 하기보다 재질군별 프리셋을 먼저 정하는 편이 관리하기 쉽다.

재질 라이브러리를 도입하는 3단계

1. 기존 셰이더의 반복 기능을 목록화한다

금속, 유리, 피부 그래프를 열고 반복되는 노드를 조사한다. 노멀 강도, 마스크 리맵, 색조 보정처럼 입력과 출력이 명확한 기능부터 추출한다. 이 단계에서 재질별로 다른 속성명도 함께 정리한다.

2. 공통 Sub-Graph와 전용 Sub-Graph를 분리한다

공통 기능은 SG_ 접두사로 재질 전용 기능은 SG_Metal_, SG_Glass_, SG_Skin_처럼 구분한다. 각 Sub-Graph에는 입력 범위와 색 공간, 예상되는 텍스처 채널을 설명하는 간단한 메모를 남긴다.

3. 대표 에셋과 품질 단계에서 검증한다

각 재질군에서 최소 하나씩 대표 에셋을 골라 동일 조명 환경에서 비교한다. 가까운 거리와 먼 거리, 실내와 실외, 저·중·고 품질 설정을 모두 확인한다. Visual Effect가 많은 전투 장면에서는 유리와 반투명 이펙트가 겹칠 때의 오버드로도 함께 측정한다.

자주 묻는 질문 (FAQ)

Sub-Graph를 많이 만들면 셰이더가 느려지나요?

아니다. Sub-Graph 자체보다 생성된 셰이더의 텍스처 샘플 수, 분기, 투명 오버드로가 성능에 더 직접적인 영향을 준다. 다만 같은 텍스처를 여러 Sub-Graph에서 중복 샘플링하지 않도록 설계해야 한다.

금속, 유리, 피부를 정말 하나의 Master Graph로 만들어야 하나요?

필수는 아니다. 공통 Sub-Graph를 공유하되 Master Graph는 재질군별로 분리하는 구성이 보통 더 읽기 쉽고 안전하다. 특히 유리의 투명 처리와 피부의 SSS는 금속과 요구 조건이 다르다.

Shader Graph와 HLSL Custom Function Node는 언제 나눠 써야 하나요?

노드 조합으로 의미가 명확한 로직은 Shader Graph에 유지한다. 반복되는 복잡한 수식, 정규화된 노멀 블렌드, 엔진 내장 노드로 표현하기 어려운 계산은 HLSL Custom Function Node로 감싸되 입출력을 작게 유지한다.

정리

범용 재질 라이브러리의 목표는 모든 표면을 하나의 셰이더로 통합하는 것이 아니라 반복되는 제작 규칙을 재사용 가능한 계약으로 만드는 데 있다. 공통 Sub-Graph는 작고 예측 가능하게 유지하고 금속의 마모, 유리의 프레넬·투명 처리, 피부의 투과광처럼 재질의 정체성을 만드는 기능은 별도 모듈로 분리하자. 이렇게 구성하면 아티스트는 일관된 파라미터로 빠르게 결과를 만들고 테크 아티스트는 품질과 성능을 중앙에서 관리할 수 있다.