Vertex Color 마스킹으로 머드·눈 축적 셰이더 만드는 법

Vertex Color 마스크와 월드 노멀, 높이 기반 블렌딩을 결합해 메시별 머드·눈 축적을 제어하는 셰이더 제작법을 정리합니다. 페인팅 워크플로, HLSL 수식, 경계 품질과 성능 최적화까지 실무 기준으로 설명합니다.

✓ 복사 완료!
챕터셰이더 프로그래밍 (HLSL/ShaderGraph)
난이도중급
상태● 검토 완료
대상 엔진
Engine Agnostic
최종 업데이트
Vertex Color 마스킹으로 머드·눈 축적 셰이더 만드는 법

핵심 요약

Vertex Color 마스킹 기반 머드·눈 축적 셰이더는 메시 정점의 RGBA 채널에 축적 영역을 저장하고 기본 재질과 머드 또는 눈 재질을 보간하는 방식이다. 월드 노멀의 위쪽 방향성, 월드 높이, 노이즈를 마스크에 곱하면 아티스트가 칠한 범위를 유지하면서도 자연스러운 눈 쌓임과 진흙 오염 경계를 만들 수 있다.

핵심은 Vertex Color를 최종 마스크가 아니라 아티스트 제어값으로 사용하는 것이다. 셰이더에서 방향성·높이·노이즈를 결합해 최종 마스크를 만들면 하나의 메시와 머티리얼로 다양한 환경 상태를 표현할 수 있다.

Vertex Color 마스크는 머드·눈 축적에 왜 적합한가?

Vertex Color는 메시 정점마다 저장되는 float4 데이터다. 별도 마스크 텍스처를 만들지 않아도 되므로 작은 프롭, 지형 주변 에셋, 반복 배치되는 모듈러 메시의 재질 변형에 특히 유용하다. DCC 도구나 엔진의 Vertex Paint 기능으로 즉시 수정할 수 있다는 점도 장점이다.

방식제어 단위장점주의점
Vertex Color정점텍스처 추가 없이 빠르게 페인팅 가능저폴리 메시에서는 경계 해상도가 낮음
마스크 텍스처픽셀섬세한 얼룩과 선명한 경계 표현UV, 텍스처 메모리, 제작 비용 필요
월드 좌표 프로시저럴월드 공간넓은 환경에 일관된 효과 적용아티스트가 특정 영역을 직접 고정하기 어려움
Vertex Color + 프로시저럴정점 + 월드 공간직접 제어와 자연스러운 자동 축적을 함께 확보채널 규칙과 디버그 뷰가 필요

권장 채널 규칙은 프로젝트 전체에서 고정한다. 예를 들어 R은 머드 축적, G는 눈 축적, B는 젖음도, A는 효과 강도 또는 가장자리 보정값으로 사용할 수 있다.

Vertex Color RGBA 채널을 머드, 눈, 젖음도 마스크로 구분해 페인팅한 3D 메시 예시

머드와 눈 축적 마스크를 어떻게 계산할까?

최종 마스크는 페인팅 마스크에 환경 조건을 곱해 계산한다. 눈은 위를 향한 면에 잘 쌓이고 머드는 낮은 위치나 발이 닿는 영역에 강하게 나타나는 경우가 많다.

1. Vertex Color에서 아티스트 마스크 읽기

Unity URP/HDRP Custom Function 또는 일반 HLSL에서 정점 색상은 보통 COLOR 시맨틱으로 전달한다.

struct Attributes
{
    float4 positionOS : POSITION;
    float3 normalOS   : NORMAL;
    float4 color      : COLOR;
    float2 uv         : TEXCOORD0;
};

struct Varyings
{
    float4 positionCS : SV_POSITION;
    float3 positionWS : TEXCOORD0;
    float3 normalWS   : TEXCOORD1;
    float4 color      : COLOR;
    float2 uv         : TEXCOORD2;
};

// R: mud, G: snow, B: wetness, A: local intensity
float mudPaintMask  = input.color.r;
float snowPaintMask = input.color.g;
float wetnessMask   = input.color.b;

Vertex Color는 보간되어 프래그먼트 셰이더에 전달된다. 따라서 마스크의 부드러운 경계는 자연스럽지만 정점 간격보다 작은 얼룩을 표현할 수는 없다.

2. 월드 노멀로 위쪽 면을 판별하기

정규화된 월드 노멀과 월드 업 벡터 (0, 1, 0)의 내적을 사용한다.

upness=saturate(dot(normalWS,float3(0,1,0)))upness = saturate(dot(normalWS, float3(0, 1, 0)))

upness가 1에 가까우면 위쪽 면이고 0에 가까우면 수직 또는 아래쪽 면이다. smoothstep으로 축적 시작 각도와 완전 축적 각도를 제어하면 경계가 안정적이다.

float topFacing = smoothstep(_SnowMinUpness, _SnowMaxUpness, upness);
float snowMask = snowPaintMask * topFacing;

예를 들어 _SnowMinUpness = 0.2, _SnowMaxUpness = 0.7이면 완전히 수직인 벽에는 눈이 거의 쌓이지 않고 위를 향한 면부터 점진적으로 축적된다.

3. 높이와 노이즈를 결합해 반복을 줄이기

높이 기반 마스크는 눈선이나 물웅덩이 주변의 머드를 제어할 때 유용하다.

heightMask=saturate(positionWS.y−minHeightmaxHeight−minHeight)heightMask = saturate(\frac{positionWS.y - minHeight}{maxHeight - minHeight})

텍스처 노이즈는 UV 또는 월드 XZ 좌표로 샘플링한다. 월드 XZ 좌표를 사용하면 UV 심이나 서로 다른 UV 스케일의 영향을 줄일 수 있다.

float2 noiseUV = input.positionWS.xz * _NoiseTiling + _Time.y * _NoiseSpeed;
float noise = SAMPLE_TEXTURE2D(_MaskNoiseTex, sampler_MaskNoiseTex, noiseUV).r;

float snowVariation = smoothstep(_NoiseMin, _NoiseMax, noise);
float finalSnowMask = saturate(snowPaintMask * topFacing * snowVariation * _SnowAmount);

노이즈를 최종 마스크에 곱할 때는 대비를 과도하게 높이지 않는 편이 좋다. 큰 얼룩은 눈이 녹거나 뭉친 느낌을 줄 수 있지만 작은 고대비 노이즈는 화면 공간에서 깜빡임을 만들 수 있다.

위쪽 노멀, 높이 그라데이션, 노이즈와 Vertex Color를 순서대로 결합한 눈 축적 마스크 디버그 뷰

Step-by-Step: Vertex Color 머드·눈 셰이더 구현 절차

1. 메시와 페인팅 규칙을 먼저 준비한다

  1. 축적 경계를 표현할 만큼 정점 밀도가 있는지 확인한다.
  2. R=머드, G=눈, B=젖음도, A=로컬 강도처럼 채널 규칙을 문서화한다.
  3. 머드가 필요한 하단부와 눈이 필요한 상단부를 기본값 0으로 두고 필요한 부분만 페인팅한다.

낮은 폴리곤 밀도의 바위나 건물 벽에 정교한 얼룩이 필요하다면 Vertex Color만으로 해결하려 하지 말고 마스크 텍스처 또는 디테일 노이즈를 함께 사용한다.

2. 재질 속성을 분리한다

기본 재질, 머드 재질, 눈 재질은 최소한 다음 값을 분리해 두는 편이 좋다.

속성기본 표면머드눈
Base Color원본 알베도갈색·저채도밝은 저채도 색상
Normal Strength원본값낮거나 중간낮음 또는 압축된 결
Metallic재질별 값대체로 00
Smoothness재질별 값젖음도에 따라 증가건조한 눈은 낮게 유지

알베도만 섞으면 표면이 페인트칠처럼 보이기 쉽다. 노멀과 스무스니스를 함께 블렌딩해야 머드는 젖고 거친 표면으로 눈은 확산 반사가 강한 표면으로 읽힌다.

3. 마스크를 만들고 PBR 입력값을 보간한다

float mudMask = saturate(mudPaintMask * _MudAmount);
float snowMask = saturate(snowPaintMask * topFacing * snowVariation * _SnowAmount);

// 눈이 머드보다 우선하도록 필요에 따라 순서를 조정한다.
mudMask *= (1.0 - snowMask);

float3 baseAlbedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv).rgb;
float3 mudAlbedo  = SAMPLE_TEXTURE2D(_MudMap, sampler_MudMap, input.uv * _MudTiling).rgb;
float3 snowAlbedo = SAMPLE_TEXTURE2D(_SnowMap, sampler_SnowMap, input.uv * _SnowTiling).rgb;

float3 albedo = lerp(baseAlbedo, mudAlbedo, mudMask);
albedo = lerp(albedo, snowAlbedo, snowMask);

float smoothness = lerp(_BaseSmoothness, _MudSmoothness, mudMask);
smoothness = lerp(smoothness, _SnowSmoothness, snowMask);
smoothness = lerp(smoothness, _WetSmoothness, wetnessMask * mudMask);

재질 우선순위는 프로젝트의 날씨 연출에 따라 달라진다. 폭설 직후라면 눈이 머드를 덮어야 하며 해빙 상태라면 머드와 젖음도가 눈 표면 일부를 침식하는 편이 설득력 있다.

머드와 눈의 경계가 인위적으로 보이는 이유는 무엇인가?

대부분은 마스크가 한 종류의 정보만 사용하거나 서로 다른 표면 속성을 충분히 바꾸지 않아서 발생한다.

  • 경계가 너무 매끈하다: Vertex Color에 저주파 노이즈를 곱하고 smoothstep 범위를 조절한다.
  • 벽에도 눈이 붙는다: 월드 노멀의 upness를 눈 마스크에 곱한다.
  • 머드가 건조한 페인트처럼 보인다: 머드 영역의 스무스니스와 다크닝을 함께 변경하고 젖음도 마스크를 추가한다.
  • 타일링이 눈에 띈다: 기본 UV 노이즈 대신 월드 XZ 노이즈 또는 서로 다른 스케일의 노이즈 두 장을 사용한다.
  • 마스크가 각져 보인다: 해당 영역의 메시 분할을 늘리거나 작은 디테일을 텍스처 마스크로 옮긴다.

성능과 품질을 함께 관리하는 방법

Vertex Color 자체는 텍스처 샘플을 추가하지 않지만 머드·눈 재질을 각각 샘플링하면 픽셀 비용은 늘어난다. 특히 모바일이나 대규모 씬에서는 마스크가 0인 영역에서도 모든 텍스처를 샘플링하는지 확인해야 한다.

항목권장 방법
텍스처 샘플 수Base, Mud, Snow의 맵 구성과 채널 패킹을 검토한다.
셰이더 배리언트기능 조합이 적다면 shader_feature_local로 선택 기능만 분리한다.
분기화면에서 넓게 혼합되는 효과는 동적 분기보다 lerp가 예측하기 쉽다.
디버깅finalSnowMask, mudMask, wetnessMask를 RGB로 출력하는 Debug 모드를 둔다.
메시 데이터Vertex Color가 실제 임포트·압축 과정에서 유지되는지 확인한다.

노멀 블렌딩은 단순 lerp보다 Reoriented Normal Mapping(RNM)이 품질 면에서 유리할 수 있다. 다만 머드와 눈 노멀의 강도가 낮다면 단순 보간도 충분히 안정적이며 먼저 화면 품질과 프로파일링 결과로 필요성을 판단하는 편이 낫다.

자주 묻는 질문 (FAQ)

Vertex Color만으로 눈 축적을 구현해도 충분한가?

넓고 부드러운 축적 영역에는 충분하다. 발자국, 녹은 얼룩, 얇은 눈 결정처럼 픽셀 단위 디테일이 필요하면 마스크 텍스처나 런타임 렌더 타깃을 추가해야 한다.

눈이 움직이는 오브젝트에서도 위쪽 면에만 쌓이게 하려면?

normalWS를 사용하면 오브젝트 회전을 반영한 현재 월드 방향을 기준으로 판정한다. 오브젝트 로컬 기준으로 고정하고 싶다면 normalOS와 로컬 업 벡터를 사용한다.

Vertex Color 채널을 여러 효과가 공유해도 되는가?

가능하지만 한 채널에 서로 독립적인 의미를 겹치면 페인팅과 디버깅이 어려워진다. 효과마다 채널을 고정하거나 마스크 텍스처와 역할을 분리하는 것이 안전하다.

정리

Vertex Color는 머드와 눈의 위치를 직접 제어하는 가벼운 마스크 레이어다. 여기에 월드 노멀 기반 방향성, 높이, 노이즈, PBR 속성 변화를 결합하면 텍스처 추가를 최소화하면서도 표면 상태가 환경과 맞물려 변화하는 축적 셰이더를 만들 수 있다. 먼저 채널 규칙과 디버그 뷰를 정하고 필요한 세부 묘사만 텍스처 마스크로 보완하는 구성이 유지보수에 가장 안정적이다.