Unity Shader Graph로 바람에 휘날리는 깃발과 천(Cloth) 버텍스 애니메이션 구현하기
Unity Shader Graph에서 버텍스 오프셋과 사인파, 노이즈를 결합하여 물리 연산 없이 가볍고 자연스럽게 펄럭이는 깃발 및 천 버텍스 애니메이션을 구현하는 테크니컬 아트 가이드입니다.

핵심 요약
- 핵심 원리: 깃발 및 천의 바람 효과는 버텍스 셰이더 단계에서 정점 위치(Vertex Position)를 사인파(Sine Wave)와 노이즈(Gradient Noise)의 합성 함수로 변위시켜 연출합니다.
- 마스킹 처리: 깃대에 고정되는 영역은 UV의 U 좌표 또는 버텍스 컬러(Vertex Color)의 R 채널을 가중치() 마스크로 사용하여 변위량을 제한합니다.
- 성능 이점: CPU 기반의 무거운 Cloth 컴포넌트(물리 시뮬레이션) 대신 GPU 버텍스 오프셋을 활용하므로 대규모 배경 오브젝트(깃발, 텐트, 망토 등)를 드로우콜 부담 없이 고성능으로 렌더링할 수 있습니다.
깃발 애니메이션에 물리 시뮬레이션 대신 버텍스 셰이더를 쓰는 이유는 무엇인가?
게임 환경에서 바람에 흔들리는 깃발이나 텐트 천막을 구현할 때 흔히 Unity의 Cloth 컴포넌트나 본(Bone) 기반 스키닝을 떠올립니다. 하지만 수십 개의 깃발이 배치된 전장이나 오픈월드 마을에서 매 프레임 CPU 물리 연산이나 스킨드 메시 렌더러를 동작시키면 상당한 연산 오버헤드가 발생합니다.
버텍스 셰이더 기반 애니메이션(Vertex Displacement)은 GPU 파이프라인의 버텍스 단계에서 수학적 연산만으로 정점을 직접 이동시킵니다. 물리 컴포넌트와 버텍스 셰이더 방식의 특성을 비교하면 다음과 같습니다.
| 비교 항목 | Unity Cloth 컴포넌트 | 본(Bone) 스키닝 애니메이션 | Shader Graph 버텍스 애니메이션 |
|---|---|---|---|
| 연산 주체 | CPU (물리 엔진) | CPU / GPU (Skinning) | GPU (Vertex Shader) |
| 메모리/배치 최적화 | 물리 버퍼 할당, 배칭 불가 | 본 트랜스폼 및 애니메이션 클립 | GPU 인스턴싱(GPU Instancing) 완전 지원 |
| 상호작용성 | 충돌체(Collider) 반응 가능 | 사전 제작된 클립만 재생 | 외부 파라미터(풍속, 풍향) 즉각 반영 |
| 적합한 사용처 | 플레이어 의상, 주요 컷씬 캐릭터 | 정밀한 연출이 필요한 주요 오브젝트 | 대량의 환경 배경(깃발, 배너, 텐트 등) |
배경에 배치되는 수많은 깃발이나 장식용 천은 상호작용 충돌보다 성능 효율성과 자연스러운 펄럭임이 훨씬 중요합니다. 따라서 버텍스 셰이더를 사용하는 방식이 압도적으로 유리합니다.

깃발 펄럭임의 수학적 원리와 변위 알고리즘
자연스러운 천의 움직임을 셰이더로 표현하기 위해서는 세 가지 핵심 요소가 결합되어야 합니다.
- 주기적 기본 파동 (Primary Traveling Wave): 깃대에서 깃발 끝으로 전파되는 사인파
- 불규칙한 와류 노이즈 (Secondary Turbulence): 시간에 따라 흐르는 2D/3D 노이즈
- 정점 고정 가중치 마스크 (Weight Mask): 깃대에 묶인 부분은 움직이지 않고 끝으로 갈수록 진폭이 커지도록 제어하는 감쇠 계수
이를 수식으로 표현하면 정점의 위치 변위 는 다음과 같이 정의됩니다.
여기서 각 기호의 의미는 다음과 같습니다.
- : 정점의 법선 벡터(Normal Vector) 또는 바람의 진행 수직 방향 벡터
- : UV 좌표 또는 버텍스 컬러 기반의 가중치 마스크 함수 ()
- : 기본 사인파와 난류 노이즈의 진폭(Amplitude)
- : 파수(Wave Number)와 각진동수(Angular Frequency)
- : 바람 벡터에 따른 노이즈 패닝 속도
flowchart LR
A["UV Coord (U Value)"] --> D["Power / Multiply (Mask)"]
B["Sine Wave (Time + Position)"] --> E["Add Combined Wave"]
C["Gradient Noise (Panner)"] --> E
E --> F["Multiply Wave * Mask"]
D --> F
F --> G["Multiply by Normal/Wind Vector"]
G --> H["Vertex Position (Offset)"]
위 흐름도와 같이 깃발 고정 마스크와 합성된 파동을 곱한 뒤 법선 방향 또는 바람 방향으로 정점을 밀어내어 최종 버텍스 오프셋을 완성합니다.
Unity Shader Graph 단계별 구현 가이드
Universal Render Pipeline(URP) 환경에서 Universal Target을 기반으로 Shader Graph를 생성하여 구현하는 단계별 절차입니다.
Step 1: 깃대 고정 영역 마스크(Pin Mask) 생성하기
깃발 3D 모델의 UV가 좌측(깃대 결합부)을 , 우측(펄럭이는 끝단)을 로 매핑했다고 가정합니다.
- UV 노드를 생성하고 Split 노드로 연결하여
R(U)채널을 추출합니다. - 깃대에서 멀어질수록 펄럭임 강도가 비선형적으로 증가하도록 Power 노드(지수값: 1.5 ~ 2.0)에 연결합니다.
- 버텍스 컬러를 페인팅하여 마스크를 커스텀한 에셋의 경우 Vertex Color 노드의
R채널을 대신 사용하거나 UV와 곱합니다.

Step 2: 진행파(Traveling Sine Wave) 노드 구성하기
바람이 한 방향으로 진행하면서 천을 물결치게 만드는 메인 사인파를 만듭니다.
- Position 노드(Space: Absolute World 또는 Object)를 생성하고 Split 노드로 특정 축 좌표(예: X 또는 Z)를 가져옵니다.
- Time 노드의
Time출력에Float파라미터WindSpeed를 곱합니다(Multiply). - 정점 좌표와
(Time * WindSpeed)값을 더한 뒤(Add),WaveFrequency파라미터와 곱합니다. - 이 결과를 Sine 노드에 연결하고 출력값에
WaveAmplitude파라미터를 곱합니다.
// HLSL 연산 대응 코드 예시
float wave = sin(position.x * _WaveFrequency + _Time.y * _WindSpeed) * _WaveAmplitude;
Step 3: 난류 노이즈(Turbulence) 합성하기
단순한 사인파만 적용하면 천이 고무판처럼 단조롭게 오르내립니다. 불규칙한 천의 주름을 만들기 위해 펄린/그레디언트 노이즈를 합성합니다.
- Gradient Noise 노드를 생성합니다.
- UV 입력부에 Tiling And Offset 노드를 연결하고 Offset에 Time 노드와
WindDirection벡터를 연결하여 노이즈가 바람 방향으로 흐르도록(Pan) 구성합니다. - 노이즈 출력 범위()를 Remap 노드를 사용해 범위로 변환합니다.
- Step 2의 사인파 결과와 노이즈 결과를 Add 노드로 합산합니다.
Step 4: 마스크 적용 및 Vertex Position 연결하기
- 합성된 파동 결과에 Step 1에서 만든 Pin Mask를 곱합니다(Multiply).
- 천이 펄럭일 방향(기본적으로 메시의 Normal 방향 또는 바람의 수직 방향)을 결정하기 위해 Normal Vector 노드와 곱합니다.
- 원래의 Position 노드(Space: Object)와 계산된 오프셋 벡터를 더한 후 Master Stack의 Vertex Position 슬롯에 연결합니다.

디테일 향상: Two-Sided 렌더링 및 노멀 보정
천 메시는 두께가 없는 단일 폴리곤 평면으로 제작되는 경우가 많습니다. 자연스러운 렌더링을 위해 다음 두 가지 설정을 필수로 적용해야 합니다.
- Two-Sided 활성화: Graph Settings에서 Two Sided 옵션을 체크하여 깃발 뒷면이 컬링(Culling)되지 않도록 합니다.
- Is Front Face 노드를 활용한 법선 반전: 뒷면 렌더링 시 라이팅이 어색하게 반전되는 현상을 방지하기 위해 Is Front Face 노드와 Branch 노드를 활용하여 뒷면일 경우 Normal 벡터를 역전( 곱하기)시켜 Fragment Master Stack의 Normal 슬롯에 전달합니다.
자주 묻는 질문 (FAQ)
Q1. 씬 안에 깃발을 여러 개 배치하면 모두 똑같은 타이밍으로 움직이는데 어떻게 해결하나요?
A. 모든 오브젝트가 동일한 전역 시간(Time)과 로컬 좌표를 참조하기 때문에 발생하는 동기화 현상입니다. Position 노드(Space: World)에서 오브젝트의 월드 위치를 가져와 시간 오프셋 값에 더해주거나 머티리얼 프로퍼티 블록(MaterialPropertyBlock)을 통해 개별 인스턴스마다 랜덤한 PhaseOffset 프로퍼티를 전달하면 비동기적으로 자연스럽게 펄럭입니다.
Q2. 깃발이 크게 휘날릴 때 그림자(Shadow)가 공중에 뜨거나 잘리는 현상은 왜 발생하나요?
A. 메시 렌더러의 바운딩 박스(Bounding Box / Bounds)는 원본 버텍스 위치를 기준으로 계산되기 때문입니다. 버텍스 셰이더로 정점을 크게 이동시키면 실제 렌더링된 메시는 바운딩 박스를 벗어나 컬링되거나 섀도우 캐스터 패스에서 누락될 수 있습니다. MeshRenderer.localBounds 또는 SkinnedMeshRenderer.bounds의 크기를 스크립트에서 인위적으로 확장(Extents 증가)시켜 해결할 수 있습니다.
Q3. 버텍스 셰이더로 움직인 깃발의 라이팅과 노멀(Normal)이 어색해 보입니다. 해결 방법이 있나요?
A. 정점 위치만 변경하고 정점의 법선(Normal)과 탄젠트(Tangent)를 재계산하지 않았기 때문입니다. 정밀한 라이팅이 필요하다면 변위 함수의 편미분(Partial Derivative)을 구하여 노멀을 분석적으로 재계산하거나 Shader Graph 내에서 근접한 세 점의 변위 위치를 샘플링하여 외적(Cross Product)으로 실시간 노멀을 재구성(Normal Reconstruction)해 주어야 합니다.
마치며
Shader Graph의 버텍스 애니메이션 기법은 깃발뿐만 아니라 수풀의 흔들림, 잔물결, 마법 포털 등 다양한 테크니컬 아트 분야에 광범위하게 응용할 수 있습니다. GPU 인스턴싱과 가벼운 수학 함수를 결합해 물리 연산 비용 없이 풍부하고 생동감 넘치는 배경을 구축해 보시기 바랍니다.



