많은 게임에서 높은 벽 뒤에 있는 낙엽도 벽 앞의 낙엽과 똑같이 바람에 날려 갑니다. 벽이 바람을 막아 주지 않는 것처럼 보입니다.
언리얼 엔진의 Wind Directional Source 나 유니티의 윈드 존(Wind Zone) 같은 엔진의 바람은 공기를 계산하지 않는 방향과 세기의 값이고, 바람을 받는 쪽은 제 위치에서 그 값을 읽어 움직일 뿐이라 사이에 벽이 있는지는 아무도 확인하지 않습니다. 벽에 막히는 바람이 필요하면 값을 정할 때 벽을 따로 반영해야 하는데, 라이트의 그림자처럼 바람 쪽에서 벽을 찍어 두는 방법과 바람을 미리 계산해 베이크해 두는 방법을 직접 짜 봤습니다.
영상판 — 언리얼 엔진 5.8 에 맨바닥을 깔고 높이 3 m · 길이 12 m 벽 하나를 세운 뒤, 직접 짠 코드로 낙엽 수천 장을 움직여 찍었습니다.
바람은 방향과 세기, 값 하나입니다
언리얼 엔진에서 레벨에 바람을 두는 컴포넌트는 Wind Directional Source 이고, 세기 · 속도 · 돌풍의 크기를 값으로 가집니다. 언리얼 엔진 5.8 소스에서 위치마다 바람을 묻는 함수는 위치 하나를 받아, 레벨의 바람 컴포넌트를 차례로 돌며 그 위치에 닿는 바람의 방향 · 속도 · 돌풍을 세기로 가중 평균해 돌려줍니다. 이 과정에 벽이나 가림을 확인하는 단계는 없습니다. 이 함수로 바람을 읽는 쪽은 스켈레탈 메시의 천, AnimDynamics 노드, Chaos Cloth 에셋이고, SpeedTree 나무는 위치를 받지 않는 다른 함수로 방향 바람만 읽습니다(점 바람은 뺌).
유니티 매뉴얼은 윈드 존의 Directional 모드 바람이 지형 전체에 한꺼번에 작용한다고 적고, 나무와 External Forces 모듈을 쓰는 파티클 시스템이 이 바람을 받는다고 적습니다.
우리 판의 낙엽도 매 프레임 제 위치의 바람 값을 받아, 그 값으로 바닥을 미끄러지거나 떠올라 날아갑니다. 벽은 낙엽이 부딪히는 판정에만 쓰입니다. 낙엽 위의 화살표가 그 위치의 바람 값인데, 벽 앞이든 뒤든 방향과 길이가 같습니다(그림 1).

위치마다 박자를 어긋나게 줍니다
값이 하나라 그대로 쓰면 돌풍도 어디서나 같은 박자로 옵니다. 우리 판에서 돌풍 박자를 모두 같게 두면, 바람이 들어오는 쪽부터 가는 쪽까지 낙엽이 한꺼번에 들썩입니다. 바람 방향으로 1 m 갈 때마다 돌풍 박자를 0.05 바퀴씩 늦추면, 들썩임이 바람 방향으로 번져 나가 바람이 훑고 지나가는 것처럼 보입니다(그림 2).
낙엽이 하는 일은 그대로 위치를 넘겨 값을 받는 것 하나이고, 늘어난 계산은 박자를 위치만큼 미는 것뿐입니다. 공기는 여전히 아무도 계산하지 않습니다. 언리얼 엔진의 SpeedTree 셰이더도 나무 인스턴스의 위치를 시간에 더해, 인스턴스끼리 박자가 맞지 않게 합니다.

폭발도 반경 안이면 값을 돌려줄 뿐입니다
언리얼 엔진의 바람 컴포넌트는 점 바람으로도 둘 수 있습니다. 소스에서 점 바람은 물어본 위치가 반경 안이면 중심에서 그 위치로 향하는 방향을 돌려주고, 속도에 1 − (거리 / 반경)² 을 곱해 줄입니다. 소스의 주석은 이것이 포인트 라이트의 감쇠를 흉내 낸 것이라고 적습니다. 반경 밖의 위치에는 답하지 않고, 여기에도 벽을 확인하는 단계는 없습니다.
유니티 매뉴얼도 Spherical 모드를 반경 안에서 바깥으로 부는 바람으로 설명하고, 파티클 시스템의 폭발 같은 효과에 쓴다고 적습니다.
우리 판도 같은 식으로 줄인 점 바람(수평 거리 기준)을 반경 10 m 로 터뜨렸습니다. 벽에서 4.6 m 떨어진 곳에서 터졌는데, 반경 안에 든 벽 너머의 낙엽도 바깥으로 함께 쓸려 나갑니다.
벽에 막히는 바람은 바람 그림자로 막습니다
벽에 막히는 바람이 필요하면, 라이트가 그림자를 만드는 방식을 빌릴 수 있습니다. 이 글에서는 이것을 바람 그림자라고 부르겠습니다. 섀도 맵은 라이트 쪽에서 장면의 깊이를 한 번 찍어 두고, 픽셀마다 그 깊이보다 뒤에 있는지만 봅니다. 우리 판은 폭발점에서 본 방향을 방위 0.5° × 앙각 1° 셀(720 × 145)로 나누고, 셀마다 그 방향으로 나아간 선이 벽을 빠져나오는 거리를 맵에 적어 두었습니다. 낙엽은 폭발점에서 본 제 방향의 셀 하나를 읽어 그 거리보다 멀면 벽 뒤로 보고 바람을 0.08 배로 줄입니다. 벽 뒤로 30 m 를 가야 원래 바람이 되게 해서, 반경 10 m 안에서는 거의 막힙니다.
같은 폭발 0.7초 뒤, 폭발 반대쪽 벽 너머 구역(벽 길이 × 6 m)의 낙엽 평균 속도는 바람 그림자가 없을 때 110 cm/s, 있을 때 7 cm/s 였고, 2.4초 뒤 위에서 보면 바람 그림자 자리의 낙엽만 남아 있습니다(그림 3). 맵은 폭발마다 한 번 만들면 되고 만드는 데 1.5 ms, 낙엽 8,500장이 모두 한 번씩 읽는 데 0.22 ms 가 걸렸습니다(CPU 스레드 하나).
방향만 있는 바람이라면 바람 쪽에서 본 가로 × 높이 셀(25 cm · 400 × 13)로 충분하고, 바람 방향이 바뀔 때만 맵을 다시 만들면 됩니다. 만드는 데 16 µs, 낙엽 9,500장이 읽는 데 0.10 ms 였습니다. 바람 600 cm/s 를 켜고 2.4초 뒤 벽 뒤 구역 위의 낙엽은 처음 712장에서 값 하나로는 168장, 바람 그림자로는 705장이었습니다. 바람 그림자는 막기만 하고 벽을 돌아 흐르지는 않습니다.

벽을 돌아 흐르는 바람은 미리 계산해 베이크합니다
벽을 돌아 흐르고 벽 뒤에서 말려 도는 바람까지 필요하면, 공기의 흐름을 미리 계산해 값으로 베이크해 둘 수 있습니다. 우리 판은 벽 둘레 32 m × 52 m × 8 m 를 40 cm 셀로 나누고, 컴퓨터 그래픽스 연구자 Jos Stam 이 게임용으로 정리한 풀이(GDC 2003)에서 확산 단계를 뺀 꼴로 흐름을 계산해 셀마다 평균 바람을 적어 두었습니다. 낙엽은 제 위치를 둘러싼 셀 여덟 개를 보간해 읽습니다.
들어오는 바람을 1 로 두면 높이 60 cm 에서 바람의 크기는 벽 끝 옆의 트인 곳이 1.07, 벽 앞이 0.39, 벽 뒤가 0.27 이었습니다. 벽 뒤 땅 가까이(높이 60 cm)에서는 바람이 벽 쪽으로 거꾸로 불었고(와류), 벽 가운데를 지나는 선을 따라 벽 높이의 아홉 배쯤인 27 m 까지 이어졌습니다. 점성 없이 계산했고 이 거리는 계산 상자 끝 가까이까지라, 실제 바람과는 다를 수 있습니다.
같은 바람 600 cm/s 를 켜고 5.6초 뒤, 벽 뒤 구역의 낙엽은 값 하나로는 712장에서 13장으로 줄었고 베이크한 값으로는 857장으로 오히려 늘었습니다(그림 4). 다만 이 값은 베이크한 방향의 바람에만 맞아, 방향이 바뀌면 다시 계산해야 합니다.

바람을 직접 계산하는 게임도 있습니다
- 갓 오브 워(2018) — 산타 모니카 스튜디오의 Rupert Renard 는 GDC 2019 에서 32 × 16 × 32 셀(셀 하나 약 1 m³)의 3D 바람 시뮬레이션을 발표했습니다. 메인 그래픽 파이프에서 돌리면 약 0.1 ms 였고, 크레토스가 도끼를 휘두르거나 던져도 바람이 생깁니다. 질의응답에서는 경사나 바위 같은 지형 · 정적 오브젝트가 바람을 밀어내게 하는 것을 생각했지만 그럴 만한 곳이 없어, 바람 볼륨은 전혀 막히지 않는다고 답했습니다.
- 리터널(2021) — 하우스마크는 개발 블로그에서 베이크한 속도장에 머물지 않고, 플레이어 둘레에서 실시간 유체 시뮬레이션으로 공기 흐름을 계산해 파티클 · 식생 · 다른 VFX 를 움직인다고 적었습니다. 이 시뮬레이션은 힘과 장애물에 반응하고, 장애물은 SDF 와 스켈레탈 메시에서 읽습니다.
벽 뒤 낙엽이 날아가는 것은 바람이 값이고 그 값을 읽는 쪽이 벽을 모르기 때문입니다. 막히는 바람이 필요하면 바람 그림자, 베이크한 값, 직접 계산 가운데 무엇을 더할지를 고르게 됩니다.
참고 자료
- Epic Games, UWindDirectionalSourceComponent (Unreal Engine 5.8 API). 컴포넌트와 값 https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Engine/UWindDirectionalSourceComponent
- 언리얼 엔진 5.8 소스, Runtime/Renderer 의 RendererScene.cpp(GetWindParameters_GameThread · SpeedTree 용 GetDirectionalWindParameters) · Runtime/Engine 의 WindDirectionalSource.cpp(점 바람) · Shaders 의 SpeedTreeCommon.ush(위치로 박자 어긋내기) · 부르는 곳 SkeletalMeshComponentPhysics.cpp · AnimNode_AnimDynamics.cpp · ChaosClothAsset 의 ClothSimulationContext.cpp https://github.com/EpicGames/UnrealEngine (에픽 계정을 깃허브에 연결해야 열림)
- Unity, Add wind zones to trees (Unity 매뉴얼). Directional · Spherical · 바람을 받는 것 https://docs.unity3d.com/Manual/class-WindZone.html
- Jos Stam, Real-Time Fluid Dynamics for Games (GDC 2003). 베이크에 쓴 풀이 https://www.dgp.toronto.edu/public_user/stam/reality/Research/pdf/GDC03.pdf
- Rupert Renard(Santa Monica Studio), Wind Simulation in 'God of War' (GDC 2019). 격자 크기 · 0.1 ms · 도끼 https://gdcvault.com/play/1026404/ · 슬라이드 https://media.gdcvault.com/gdc2019/presentations/Renard_Rupert_Wind_Simulation.pdf · 질의응답(발표 영상) https://www.youtube.com/watch?v=dDgyBKkSf7A
- Housemarque, Returnal VFX Breakdown (2021). 플레이어 둘레 실시간 유체 · 장애물 https://housemarque.com/news/2021/9/15/returnal-vfx-breakdown
'게임개발뜯어보기' 카테고리의 다른 글
| 이 연막은 그림이 아니라 공간을 채운 복셀이다 - 복셀과 레이 마칭 (0) | 2026.10.26 |
|---|---|
| 게임 속 계단현상, 이렇게 지워왔다 - 슈퍼샘플링부터 DLSS까지 (1) | 2026.10.24 |
| 이 게임 캐릭터는 다음 동작을 고르지 않고 다음 자세를 찾는다 - 모션 매칭 (0) | 2026.10.22 |
| 팩토리오 벨트는 아이템 자리 대신 간격을 적는다 - 팩토리오의 최적화 (0) | 2026.10.20 |
| 게임 속 불을 그리지 않고 돌리면 - 유체 시뮬레이션과 레이 마칭 (0) | 2026.10.19 |