비정상적일 정도로 최적화된 모래 떨어뜨리기 시뮬레이션 만들기
Optimizing a Falling Sand Simulation To an Unreasonable Degree by NivMiz
이 기록물은 과거 성능 문제로 실패했던 '모래 떨어뜨리기(Falling Sand)' 시뮬레이션을 GPU 병렬 처리를 통해 극한으로 최적화하고, 그 과정에서 발생한 기술적 난제와 해결책을 상세히 다룹니다.
-
시뮬레이션의 기본 원리와 배경
- 개발자는 게임뿐만 아니라 물리 엔진, 유체 시뮬레이션 등 코드로 현실 세계를 재현하는 것을 즐깁니다.
- '모래 떨어뜨리기' 게임은 세포 자동자(Cellular Automaton) 알고리즘의 일종입니다.
- 과거 유니티(Unity)로 만든 첫 프로젝트는 120x68 해상도에서 30fps를 겨우 유지할 정도로 성능이 나빴습니다.
- 이번 프로젝트의 목표는 당시의 실수를 만회하고 압도적인 속도의 시뮬레이션을 구현하는 것입니다.
-
모래 시뮬레이션의 핵심 규칙
- 각 모래 알갱이는 그리드의 한 칸(Cell)을 차지하며 미리 정의된 규칙에 따라 행동합니다.
- 규칙 1: 현재 칸 바로 아래가 비어 있으면 그곳으로 이동합니다.
- 규칙 2: 바로 아래가 막혀 있고 오른쪽 아래가 비어 있으면 그곳으로 이동합니다.
- 규칙 3: 아래와 오른쪽 아래가 모두 막혀 있고 왼쪽 아래가 비어 있으면 그곳으로 이동합니다.
- 이 단순한 규칙들이 모여 실제 모래처럼 자연스러운 삼각형 더미를 형성합니다.
-
성능 병목 현상과 병렬화(Parallelization)
- CPU 방식은 모든 셀을 순차적으로 루프를 돌며 계산해야 합니다.
- 1280x720(720p) 해상도의 경우 약 90만 개의 셀을 매번 순차적으로 처리해야 하므로 매우 느립니다.
- 해결책으로 '병렬화'를 선택했습니다. 모든 셀의 계산을 동시에 처리하는 방식입니다.
- 범용 계산을 수행하는 CPU와 달리, 수많은 픽셀을 동시에 계산하도록 설계된 GPU를 활용하기로 했습니다.
-
Compute Shader와 HLSL 활용
- 유니티에서 GPU 코드를 작성하기 위해 Compute Shader를 사용했습니다.
- 언어는 HLSL(High-Level Shader Language)을 사용하며, 현대적 프로그래밍 언어의 기능은 적지만 속도는 압도적입니다.
- 예시: 리스트의 정수들에 1을 더할 때, CPU는 순차적으로 더하지만 GPU는 각 요소에 스레드를 할당해 동시에 처리합니다.
- 1024x576 해상도에서도 매우 빠른 실행 속도를 확인했습니다.
-
병렬 프로그래밍의 난제: 레이스 컨디션(Race Condition)
- 초기 구현에서 모래가 쌓이지 않고 사라지는 현상이 발생했습니다.
- 이는 두 개 이상의 스레드가 동일한 데이터(빈 공간)에 동시에 쓰기를 시도할 때 발생하는 '레이스 컨디션' 때문입니다.
- 예시: 픽셀 1과 픽셀 2가 동시에 픽셀 3을 비어있다고 판단하고 이동하려 하면, 결과적으로 입자 하나가 소멸되는 물리 법칙 위배가 일어납니다.
-
원자적 연산(Atomic Operations)을 통한 해결
- 'Atoms(나눌 수 없는)'라는 어원처럼, 중단되지 않고 순차적으로 실행됨을 보장하는 원자적 연산을 도입했습니다.
- 'InterlockedCompareExchange' 함수를 사용하여 특정 위치를 점유하기 전에 값을 비교하고 교체하는 방식을 사용했습니다.
- 'Claims'라는 별도의 그리드를 운용하여 매 프레임 시작 시 비워두고, 입자가 이동하려는 위치를 먼저 '선점(Claim)'하게 했습니다.
- 이미 선점된 위치에는 다른 입자가 들어오지 못하게 함으로써 입자 유실 문제를 완전히 해결했습니다.
-
시뮬레이션의 특성과 '기능'화
- 해결 후 모래가 경사면을 따라 내려가는 속도가 다소 느려지는 부작용이 있었습니다.
- 시뮬레이션을 프레임당 두 번 실행하는 임시 방편을 썼으나, 유명한 시뮬레이션인 'Powder Toy' 등에서도 동일한 현상이 있음을 확인했습니다.
- 이를 버그가 아닌 시뮬레이션의 고유한 '특징(Feature)'으로 받아들이기로 결정했습니다.
-
시각적 개선 및 최종 결과
- 모래 색상을 어두운 색과 밝은 색 사이에서 무작위로 보간(Interpolate)하여 질감을 살렸습니다.
- 시간에 따라 색상이 부드럽게 변하는 기능을 추가하여 불꽃과 같은 화려한 시각 효과를 구현했습니다.
- 과거 버전 대비 성능이 약 2,000배 향상된 것으로 추정됩니다.
- 오래된 프로젝트를 최적화 관점에서 다시 개발하는 것이 개발자의 성장에 큰 도움이 됨을 강조하며 마무리합니다.