OpenGL을 사용한 2D 유체 시뮬레이터
요약
본 프로젝트는 OpenGL 4.3 compute shaders를 활용하여 GPU 상에서 2D 유체 시뮬레이션을 구현한 예제입니다. Navier-Stokes 방정식을 풀기 위해 이류(advection) 단계에 반-라그랑주 접근 방식을 사용하며, Maccormack 수치 기법과 Runge Kutta 방법을 결합했습니다.
핵심 포인트
- OpenGL 4.3 compute shaders를 사용하여 GPU 기반 유체 시뮬레이션 구현
- Navier-Stokes 방정식을 풀기 위해 반-라그랑주 이류 접근 방식 채택
- Maccormack 수치 기법과 Runge Kutta 방법을 결합하여 안정성 확보
OpenGL을 사용한 2D 유체 시뮬레이터
Build Status
이 프로젝트는 OpenGL 4.3 compute shaders 기능을 사용하여 GPU 상에서 eulerian fluid simulation을 구현한 것입니다.
시작하기 (Getting Started)
You는 먼저 저장소를 clone해야 합니다.
git clone https://github.com/cgurps/2DFluidSimulation.git [PROJECT_FOLDER]
그리고 서브모듈을 초기화합니다.
cd [PROJECT_FOLDER]
git submodule update --init
프로젝트를 컴파일하려면 compute shader 기능을 얻기 위해 OpenGL 버전 4.3 이상이 필요합니다. 또한, 시스템에 Boost가 설치되어 있어야 합니다. 프로젝트는 CMake를 사용하여 컴파일에 필요한 Makefile을 생성합니다. 다음 명령어를 사용하여 실행 파일을 빌드할 수 있습니다.
mkdir build
cd build
cmake ..
...
프로그램 옵션은 -h를 사용하여 확인할 수 있습니다.
수치 기법
우리는 비압축성 유체에 대한 Navier-Stokes 방정식을 풀어냅니다:
<p align="center"> <img src="https://raw.githubusercontent.com/cgurps/2DFluidSimulation/HEAD/images/equations/NS.png"> </p> 모든 eulerian 접근 방식에서, 양들(속도, 압력, 발산, 와도 등)은 사각형 격자에 저장됩니다. 이류(advection) 단계에서는 반-라그랑주(semi-Lagrangian) 접근 방식을 사용합니다. 이류는 Maccormack 수치 기법과 4차 Runge Kutta 방법을 결합합니다. 새로 생성된 극값들은 첫 번째 이류에서 얻은 값들을 사용하여 클램프됩니다. 만약 새로운 극값이 원래 계산된 값에서 너무 멀다면, 저는 오차 보정 항을 제거합니다 (이는 4차 Runge Kutta 접근 방식으로 되돌리는 것과 같습니다). 다음 단계에서는 속도장(velocity field)에 힘을 추가합니다 (와도가 구현되지만, 이류 기법이 장 내의 소용돌이를 보존하기에 충분히 정확하여 사용되지는 않습니다). 그 후, 중간 필드는 Helmholtz-Hodge 분해를 기반으로 하는 투영 방식(projection method)을 사용하여 비압축성으로 만듭니다. 저는 연관된 poisson 방정식을 Jacobi 방법을 사용하여 풀어냅니다. 시간 단계는 각 반복에서 다음 공식을 통해 계산됩니다: <p align="center"> <img src="https://raw.githubusercontent.com/cgurps/2DFluidSimulation/HEAD/images/equations/CFL.png"> </p> 속도장의 최댓값은 GPU의 reduce 메서드를 통해 계산됩니다.구현
각 양(quantity)은 GPU 상의 16비트 부동 소수점 텍스처로 표현됩니다. 정확한 텍셀(texel) 조회에는 texelFetch 메서드를 사용합니다 (이는 texture2D 사용보다 빠릅니다). 그리고 경계 케이스는 수동으로 처리합니다. 또한, 이류(advection) 단계의 쌍선형 보간(bilinear interpolation)도 더 나은 정확도를 위해 수동으로 계산됩니다. 구현에는 세 가지 주요 클래스가 포함되어 있습니다:
GLFWHandler: OpenGL 초기화와 메인 프로그램 루프를 담고 있는 GLFW 래퍼입니다.SimulationBase: 시뮬레이션 인터페이스를 제공하는 순수 가상 함수(pure virtual function)입니다. 프로그램의 메인 루프는shared_texture변수에 접근하여 관련 텍스처를 화면에 표시합니다. 다양한 텍스처가 생성되고 저장되는 곳이 바로 이곳입니다.SimulationFactory: 시뮬레이션 단계(예: 이류, 압력 투영 등)를 계산하는 데 필요한 도우미 함수들을 담고 있습니다. 이 클래스는 GPU 메모리를 할당하지 않으며, 대신 시뮬레이션 루프에 의해 데이터를 공급받습니다.
만약 이 시뮬레이션을 가지고 놀아보고 싶다면, SimulationBase를 상속받는 새로운 클래스를 만들고 SimulationFactory를 사용하여 필요한 모든 것을 계산해야 합니다. 이 새로운 클래스는 시뮬레이션이 작동하려면 Init(), Update(), AddSplat(), AddSplat(const int), 그리고 RemoveSplat()을 오버로딩(overload)해야 합니다.
Jacobi 방법론에 대한 참고 사항
저는 Harris et al.에서 설명된 원래의 Jacobi 방법론을 변형한 Red-Black Jacobi 방법을 구현했습니다. 이 아이디어는 네 개의 값을 하나의 텍셀(texel)에 패킹하는 것입니다. 이러한 패킹은 발산량(divergence)과 압력(pressure) 값 모두에 적용됩니다. 다음 그림(Figure 5를 Harris et al.에서 재현)은 이 과정을 보여줍니다.
<p align="center"> <img src="https://raw.githubusercontent.com/cgurps/2DFluidSimulation/HEAD/images/RB.png"> </p> 결과로 생성된 텍스처는 원래 크기의 절반입니다. 그런 다음, Jacobi 반복은 먼저 검은색 값(red 값에만 의존함)을 업데이트하고, 그 다음 계산된 검은색 값을 사용하여 빨간색 값을 업데이트합니다. 이는 텍셀 가져오기(texel fetches) 횟수를 거의 절반으로 나누므로, 근사적으로 절반의 시간으로 동일한 수렴 순서(order of convergence)를 얻을 수 있습니다! 실제로 까다로운 부분은 발산량을 하나의 텍셀에 패킹하는 것입니다. 이는 현재 텍셀의 여러 인접 값을 가져와 단일 셰이더 패스(shader pass)에서 수행됩니다 (파일 <code class="language-plaintext">divRB.comp</code> 참조).참고 자료
- @: 유체 시뮬레이션에 대한 간단한 튜토리얼
- @: 이 멋진 책은 유체 시뮬레이션을 위한 많은 기술을 다룹니다 (클래식!)
- @: GPU gems 1의 2D 유체
- @: GPU gems 3의 3D 유체
- @: Maccormack 방법
- @: 이 논문은 Maccormack 스킴에 의해 생성된 극값(extremas)을 처리하기 위한 역방향 방법(reverse method)을 설명합니다.
- @: 준-라그랑주 접근법(semi-Lagrangian approach)에서의 입자 이류(particle advection)를 위한 Runge Kutta 방법
좋은 GitHub 프로젝트
- tunabrain/gpu-fluid: 하이브리드 접근법(FLIP)을 사용하여 GPU에서 2D 유체 시뮬레이션
- PavelDoGreat/WebGL-Fluid-Simulation: 온라인 유체 시뮬레이션
AI 자동 생성 콘텐츠
본 콘텐츠는 GitHub GameDev의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기