실시간 디졸브(Dissolve) 셰이더 구현 가이드: 노이즈 텍스처 샘플링과 타오르는 에미시브 글로우 연출

실시간 디졸브(Dissolve) 셰이더 구현 가이드: 노이즈 텍스처 샘플링과 타오르는 에미시브 글로우 연출

노이즈 텍스처와 HLSL discard/clip 함수를 활용해 게임 오브젝트가 서서히 소멸하는 실시간 디졸브 셰이더 원리와 불타는 외곽선 글로우(Emissive Edge) 구현법을 단계별로 정리합니다.

TL;DR

  • 디졸브 셰이더(Dissolve Shader)는 그레이스케일 노이즈 텍스처의 픽셀 밝기값(N(uv)N(uv))을 시간의 흐름에 따라 변화하는 임계값(θ\theta)과 비교하여 프래그먼트를 폐기(clip / discard)함으로써 소멸 효과를 생성합니다.
  • 경계면(Edge)의 불타는 효과는 임계값 주변 구간([θ,θ+w][\theta, \theta + w])을 마스킹하여 HDR 에미시브(Emissive) 컬러 및 블룸(Bloom) 후처리를 적용해 구현합니다.
  • 반투명 블렌딩 대비 오버드로(Overdraw) 정렬 부하가 적으나 clip 함수 사용에 따른 Early-Z(하드웨어 오클루전) 무효화 비용을 고려한 최적화가 필수적입니다.

디졸브(Dissolve) 셰이더의 기본 동작 원리는 무엇인가?

디졸브 셰이더는 몬스터 처치 시 사라지는 연출, 텔레포트 이동, 환경 오브젝트의 동적 스폰 등에 널리 쓰이는 대표적인 테크니컬 아트 테크닉입니다. 이 연출의 핵심은 3D 메시 표면을 알파 블렌딩(Transparent)으로 서서히 투명화하는 대신, 알파 테스트(Alpha Testing / Cutout) 방식을 활용해 픽셀을 물리적으로 잘라내는(Clipping) 데 있습니다.

알파 블렌딩 방식은 렌더 큐(Render Queue) 정렬 문제와 깊이 버퍼(Depth Buffer) 쓰기 불가로 인한 정렬 오류(Sorting Issue)가 빈번히 발생합니다. 반면 알파 테스트 방식은 불투명(Opaque) 파이프라인과 동일하게 깊이 쓰기를 유지하면서 임계값에 도달한 프래그먼트만 파이프라인에서 폐기하므로 렌더링 순서에 구애받지 않고 안정적인 깊이 판정을 유지합니다.

디졸브 판정의 기본 수식은 다음과 같이 정의됩니다:

Fragment Status={discard,if N(uv)θ<0render,if N(uv)θ0\text{Fragment Status} = \begin{cases} \text{discard}, & \text{if } N(uv) - \theta < 0 \\ \text{render}, & \text{if } N(uv) - \theta \ge 0 \end{cases}

여기서 N(uv)N(uv)는 UV 좌표 기반으로 샘플링한 단일 채널(흑백) 노이즈 값(0.01.00.0 \sim 1.0)이며 θ\theta는 디졸브 진행도를 제어하는 임계값(Cutoff, 0.01.00.0 \sim 1.0)입니다. 진행도 θ\theta가 0에서 1로 증가함에 따라 노이즈 값이 낮은 영역부터 순차적으로 화면에서 제외됩니다.

flowchart LR
    A["메시 버텍스 / UV 데이터"] --> B["그레이스케일 노이즈 텍스처 샘플링"]
    B --> C["진행도 임계값(Cutoff)과 차분 연산"]
    C -->|Cutoff 미만 영역| D["clip 및 discard 픽셀 폐기"]
    C -->|Cutoff와 Width 사이의 경계 영역| E["HDR 에미시브 엣지 마스킹"]
    C -->|Cutoff 초과 영역| F["표준 PBR 셰이딩과 알베도 연산"]
    E --> G["최종 컬러 버퍼 출력 + Bloom 후처리"]
    F --> G

디졸브 셰이더의 기본 알파 클리핑 동작 원리와 텍스처 노이즈 마스크

불타는 외곽선(Burning Edge)과 글로우는 어떻게 구현할까?

단순히 픽셀을 지우기만 하면 단면이 밋밋하게 잘려 나가는 느낌을 줍니다. 마법 주문으로 소멸하거나 화염에 휩싸여 타들어 가는 자연스러운 VFX를 연출하려면 픽셀이 잘려 나가기 직전 경계면에 강렬한 발광(Emissive Glow) 외곽선을 덧입혀야 합니다.

1. 외곽선 마스크(Edge Mask) 추출 함수 비교

경계선 마스크를 계산할 때 주로 step() 또는 smoothstep() 함수를 사용합니다. 두 함수의 동작 차이는 외곽선의 질감과 그라디언트 표현에 직접적인 영향을 미칩니다.

함수 명칭수식 및 특징시각적 결과추천 사용 사례
step(a, x)x<a0x < a \to 0, xa1x \ge a \to 1 (이진화 판정)칼로 자른 듯한 날카롭고 선명한 단색 외곽선디지털 글리치, 기계 분해 연출
smoothstep(a, b, x)3차 에르미트 보간(3t22t33t^2 - 2t^3)을 적용한 부드러운 감쇠경계면 중심으로 서서히 번지는 연소 그라디언트유기체 소멸, 마법 화염, 재 연출

외곽선 마스크는 노이즈 값 N(uv)N(uv)가 디졸브 컷오프 θ\theta보다 크면서 θ+EdgeWidth\theta + \text{EdgeWidth} 이하인 밴드(Band) 영역을 검출하여 생성합니다:

EdgeMask=1.0smoothstep(θ,θ+EdgeWidth,N(uv))EdgeMask = 1.0 - \text{smoothstep}(\theta, \theta + \text{EdgeWidth}, N(uv))

2. HDR 컬러와 블룸(Bloom) 연동

추출된 EdgeMaskEdgeMask에 단순 RGB 컬러를 곱하면 일반적인 단색 라인만 표시됩니다. 빛이 타오르는 글로우를 생성하기 위해서는 1.0을 초과하는 HDR 에미시브 강도(Intensity) 값을 곱해주어야 합니다.

FinalEmission=EdgeColorRGB×EdgeIntensity×EdgeMaskFinalEmission = EdgeColor_{RGB} \times EdgeIntensity \times EdgeMask

후처리 볼륨(Post-processing Volume)의 블룸 임계값(Bloom Threshold)을 초과하는 픽셀은 렌더 타깃 주변으로 빛이 번져 나가며 실제 불꽃이 이는 시각적 효과를 완성합니다.

HLSL 기반 디졸브 셰이더 단계별 구현 가이드 (Step-by-Step)

Unity URP(Universal Render Pipeline) 환경을 기준으로 작성된 실전 HLSL 셰이더 코드 구현 절차입니다.

1단계: 셰이더 프로퍼티 및 버퍼 정의

머티리얼 인스펙터에서 제어할 파라미터를 선언하고 SRP Batcher 호환을 위해 CBUFFER 블록 내에 상수를 배치합니다.

// Properties 블록
_BaseMap("Base Texture", 2D) = "white" {}
_NoiseMap("Noise Texture", 2D) = "white" {}
_Cutoff("Dissolve Cutoff", Range(0.0, 1.0)) = 0.0
_EdgeWidth("Edge Width", Range(0.0, 0.2)) = 0.05
[HDR] _EdgeColor("Edge Color (HDR)", Color) = (1.0, 0.4, 0.0, 1.0)
_EmissionIntensity("Emission Multiplier", Range(1.0, 20.0)) = 5.0

2단계: 프래그먼트 셰이더의 클리핑 및 엣지 연산

프래그먼트 셰이더에서 노이즈 텍스처를 읽어와 알파 클리핑을 수행하고 외곽선 에미시브 컬러를 블렌딩합니다.

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

struct Attributes
{
    float4 positionOS : POSITION;
    float2 uv         : TEXCOORD0;
};

struct Varyings
{
    float4 positionCS : SV_POSITION;
    float2 uv         : TEXCOORD0;
    float2 noiseUV    : TEXCOORD1;
};

TEXTURE2D(_BaseMap);       SAMPLER(sampler_BaseMap);
TEXTURE2D(_NoiseMap);      SAMPLER(sampler_NoiseMap);

CBUFFER_START(UnityPerMaterial)
    float4 _BaseMap_ST;
    float4 _NoiseMap_ST;
    float  _Cutoff;
    float  _EdgeWidth;
    float4 _EdgeColor;
    float  _EmissionIntensity;
CBUFFER_END

Varyings vert(Attributes input)
{
    Varyings output;
    output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
    output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
    output.noiseUV = TRANSFORM_TEX(input.uv, _NoiseMap);
    return output;
}

half4 frag(Varyings input) : SV_Target
{
    // 1. 노이즈 텍스처 R 채널 샘플링
    half noise = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, input.noiseUV).r;

    // 2. 픽셀 폐기 판정 (Cutoff 값 미만인 프래그먼트 제거)
    clip(noise - _Cutoff);

    // 3. 베이스 알베도 텍스처 샘플링
    half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);

    // 4. 불타는 외곽선 영역 마스크 계산 (0.0 ~ 1.0)
    half edgeFactor = 1.0 - smoothstep(_Cutoff, _Cutoff + _EdgeWidth, noise);

    // 5. HDR 에미시브 컬러 합성
    half3 emissive = _EdgeColor.rgb * _EmissionIntensity * edgeFactor;

    // 6. 기본 컬러 위에 발광 색상 덧씌우기
    half3 finalColor = baseColor.rgb + emissive;

    return half4(finalColor, baseColor.a);
}

디졸브 진행도에 따른 노이즈 마스킹과 에미시브 글로우 엣지 변화

디졸브 연출 품질을 높이는 테크니컬 아트 팁

기본 디졸브 셰이더에 몇 가지 수학적 변형을 가하면 단순한 텍스처 마스킹을 넘어 사실적인 소멸 연출을 만들 수 있습니다.

1. 월드 좌표(World Y) 기반 방향성 그래디언트 결합

오브젝트 전체가 동시에 무작위로 뚫리는 대신, 발밑에서 머리 방향으로 타오르며 사라지는 연출을 하려면 버텍스의 월드 높이값(Pworld.yP_{world}.y)을 노이즈와 합성합니다.

DissolveMap=N(uv)×(1.0wgrad)+(Pworld.yYminYmaxYmin)×wgradDissolveMap = N(uv) \times (1.0 - w_{grad}) + \left(\frac{P_{world}.y - Y_{min}}{Y_{max} - Y_{min}}\right) \times w_{grad}

높이 그래디언트와 노이즈를 선형 보간(lerp)하면 아래에서 위로 타들어 가는 물리적 연소 흐름이 형성됩니다.

2. 버텍스 디스플레이스먼트(Vertex Displacement) 추가

경계선 영역의 버텍스를 노멀 방향으로 밀어내거나 찌그러뜨리면 소멸하기 직전 열기로 인해 표면이 부풀어 오르고 재(Ash)가 날리는 입체감을 부여할 수 있습니다.

// 버텍스 셰이더 내 디스플레이스먼트 적용 예시
float noiseVal = SAMPLE_TEXTURE2D_LOD(_NoiseMap, sampler_NoiseMap, input.uv, 0).r;
float edgeMask = 1.0 - smoothstep(_Cutoff, _Cutoff + _EdgeWidth, noiseVal);
input.positionOS.xyz += input.normalOS * edgeMask * _DisplacementAmount;

실시간 렌더링 최적화 및 주의사항

디졸브 셰이더는 시각적 효과가 뛰어난 반면 GPU 하드웨어 관점에서 병목을 유발할 수 있는 요소가 존재합니다.

1. Early-Z 비활성화 문제

최신 GPU는 프래그먼트 셰이더를 실행하기 전 깊이 버퍼를 먼저 테스트하여 보이지 않는 픽셀의 셰이더 연산을 생략하는 Early-Z(Early Depth Testing) 기술을 사용합니다.

하지만 프래그먼트 셰이더 내부에 clip() 또는 discard 명령어가 포함되어 있으면 GPU는 셰이더가 완전히 실행되기 전까지 픽셀의 폐기 여부를 확정할 수 없으므로 Early-Z 최적화를 자동으로 해제합니다. 이로 인해 심한 오버드로가 발생하는 씬에서는 렌더링 비용이 급격히 증가합니다.

2. 성능 최적화 가이드라인

  • 소멸 완료 시 메시 렌더러 비활성화: 디졸브 진행도가 1.0에 도달하여 오브젝트가 완전히 사라진 순간 즉시 MeshRenderer 컴포넌트를 비활성화하거나 게임 오브젝트를 풀링(Pooling)/파괴해야 합니다. 보이지 않더라도 렌더 큐에 남아 있으면 버텍스 연산과 클리핑 테스트 비용이 계속 소모됩니다.
  • LOD(Level of Detail) 셰이더 분기: 원거리 카메라 LOD에서는 복잡한 2-텍스처 샘플링 대신 단순 버텍스 알파 페이드를 적용하는 저비용 셰이더로 스위칭합니다.
  • 텍스처 채널 패킹(Texture Packing): 디졸브 노이즈를 별도의 독립 텍스처로 분리하지 않고 Metallic/Smoothness 맵의 미사용 채널(예: Alpha 채널)에 패킹하여 텍스처 샘플러 유닛 사용을 최소화합니다.

자주 묻는 질문 (FAQ)

Q1. 디졸브 경계면에 지글거리는 계단 현상(Aliasing)이 생깁니다. 어떻게 해결하나요?

노이즈 텍스처의 해상도가 낮거나 선형 필터링(Bilinear/Trilinear)이 아닌 포인트(Point) 필터링으로 설정된 경우 경계면에 계단 현상이 두드러집니다. 텍스처 임포트 설정에서 Filter Mode를 Bilinear 또는 Trilinear로 설정하고 프래그먼트 셰이더에서 fwidth() 함수를 이용해 화면 해상도에 비례하는 앤티에일리어싱 스무딩 밴드를 적용하면 픽셀 지글거림이 완화됩니다.

Q2. 3D 캐릭터 모델의 UV 이음새(Seam)에서 디졸브 경계선이 끊어져 보입니다.

메시의 UV 언래핑 경계선에서 텍스처 좌표가 불연속할 때 노이즈 패턴이 어긋나는 현상입니다. 모델의 UV 매핑에 의존하지 않고 버텍스의 월드 공간 좌표(World Position) 또는 오브젝트 로컬 좌표(Object Position)를 3축 투영(Triplanar Mapping)하여 노이즈를 샘플링하면 이음새 없는 연속적인 디졸브를 구현할 수 있습니다.

Q3. 모바일 기기(타일 기반 렌더러)에서 clip() 성능 저하를 방지하는 팁이 있나요?

타일 기반 지연 렌더링(TBDR) 아키텍처를 채택한 모바일 GPU(PowerVR, Mali, Adreno 등)는 clip() 호출 시 타일 버퍼의 온칩 메모리 프리패스가 방해받아 프레임 드랍이 발생하기 쉽습니다. 모바일 타깃 프로젝트에서는 디졸브가 필요한 특수 연출 순간에만 해당 머티리얼로 교체하고 평상시에는 표준 Opaque 셰이더를 사용하도록 머티리얼 스왑 로직을 구성하는 것이 안전합니다.

#셰이더#디졸브#HLSL#VFX#유니티#테크니컬아트#그래픽스

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs