基於 Navier-Stokes 的流體模擬
阿新 • • 發佈:2020-12-10
效果圖
實現基於非常非常經典的 Paper ——Jos Stam 的Real-Time Fluid Dynamics for Games
程式碼和註釋
float visc = 0.0f; float diff = 0.0f; int N = 250; int size = (N + 2) * (N + 2); float[] u = new float[size]; // 速度, 水平/豎直 float[] v = new float[size]; float[] u_prev = new float[size]; float[] v_prev = new float[size]; float[] dens = new float[size]; // 密度 float[] dens_prev = new float[size]; float dt = 0.4f; // 時間間隔 float densityAdd = 50.0f; float forceX = 0.0f; float forceY = -7.0f; float circleRadius = 1.0f; void setup() { size(252, 252); } // reference one dimensional array element /// @note 二維轉一維,便於索引 int IX(int i, int j) { return i + (N + 2) * j; } /// @note 新增密度 void addSource(float[] x, float s[], float dt) { for (int index = 0; index < size; index++) { x[index] += dt * s[index]; } } // diffusion solver // possible diffusion at rate diff /// @note 密度擴散(4 鄰域) /// 由於是稀疏矩陣所以採用 Gauss-Seidel relaxation(鬆弛)法 求解 void diffuse(int N, int b, float[] x, float[] x0, float diff, float dt) { int i, j, k; float a = dt * diff * N * N; for (k = 0; k < 20; k++) ///< Gauss-Seidel relaxation { for (i = 1; i <= N; i++) { for (j = 1; j <= N; j++) { x[IX(i, j)] = (x0[IX(i, j)] + a * (x[IX(i - 1, j)] + x[IX(i + 1, j)] + x[IX(i, j - 1)] + x[IX(i, j + 1)])) / (1 + 4 * a); } } setBounds(N, b, x); } } /// @note 平流 /// 當作一個粒子集合進行建模求解移動的密度值 /// 注意是沿這速度場逆向回溯求解,通過上一個時間片的密度值進行插值求解 void advect(int N, int b, float[] d, float[] d0, float[] u, float v[], float dt) { int i, j, i0, j0, i1, j1; float x, y, s0, t0, s1, t1; float dt0 = dt * N; for (i = 1; i <= N; i++) { for (j = 1; j <= N; j++) { /// @note 柑橘速度場得到上一個時間片的位置 x = i - dt0 * u[IX(i, j)]; y = j - dt0 * v[IX(i, j)]; if (x < 0.5) { x = 0.5; } if (x > N + 0.5) { x = N + 0.5; } i0 = (int)x; i1 = i0 + 1; if (y < 0.5) { y = 0.5; } if (y > N + 0.5) { y = N + 0.5; } j0 = (int)y; j1 = j0 + 1; /// @note 插值的權重 s1 = x - i0; s0 = 1 - s1; t1 = y - j0; t0 = 1 - t1; /// @note 四鄰域的插值 d[IX(i, j)] = s0 * (t0 * d0[IX(i0, j0)] + t1 * d0[IX(i0, j1)]) + s1 * (t0 * d0[IX(i1, j0)] + t1 * d0[IX(i1, j1)]); } } setBounds(N, b, d); } void densityStep(int N, float[] x, float[] x0, float[] u, float[] v, float diff, float dt) { float[] tmp; addSource(x, x0, dt); tmp = x0; x0 = x; x = tmp; // Swap x0, x diffuse(N, 0, x, x0, diff, dt); tmp = x0; x0 = x; x = tmp; // Swap Back x0, x advect(N, 0, x, x0, u, v, dt); } /// @note 投射算符(強制速度的質量不變性,mass conserving,通過 Hodge 分界實現 ) /// 每個速度場都是質量保留場和梯度場的和 /// 由於是稀疏矩陣所以採用 Gauss-Seidel relaxation(鬆弛)法 求解 泊松方程 void project(int N, float[] u, float[] v, float[] p, float[] div) { int i, j, k; float h = 1.0f / N; for (i = 1; i <= N; i++) { for (j = 1; j <= N; j++) { div[IX(i, j)] = -0.5 * h * (u[IX(i + 1, j)] - u[IX(i - 1, j)] + v[IX(i, j + 1)] - v[IX(i, j - 1)]); p[IX(i, j)] = 0; } } // 邊界處理 setBounds(N, 0, div); setBounds(N, 0, p); for (k = 0; k < 20; k++) { for (i = 1; i <= N; i++) { for (j = 1; j <= N; j++) { p[IX(i, j)] = (div[IX(i, j)] + p[IX(i - 1, j)] + p[IX(i + 1, j)] + p[IX(i, j - 1)] + p[IX(i, j + 1)]) / 4.0f; } } setBounds(N, 0, p); } for (i = 1; i <= N; i++) { for (j = 1; j <= N; j++) { u[IX(i, j)] -= 0.5 * (p[IX(i + 1, j)] - p[IX(i - 1, j)]) / h; v[IX(i, j)] -= 0.5 * (p[IX(i, j + 1)] - p[IX(i, j - 1)]) / h; } } setBounds(N, 1, u); setBounds(N, 2, v); } /// @note 速度的更新 /// 來源於三個因素 —— 加力、粘性散度、自平流 void velocityStep(int N, float[] u, float[] v, float[] u0, float[] v0, float visc, float dt) { float[] tmp; addSource(u, u0, dt); addSource(v, v0, dt); tmp = u0; u0 = u; u = tmp; // Swap u0 u diffuse(N, 1, u, u0, visc, dt); tmp = v0; v0 = v; v = tmp; // Swap v0 v diffuse(N, 2, v, v0, visc, dt); project(N, u, v, u0, v0); tmp = u0; u0 = u; u = tmp; // Swap Back u0 u tmp = v0; v0 = v; v = tmp; // Swap Back v0 v advect(N, 1, u, u0, u0, v0, dt); advect(N, 2, v, v0, u0, v0, dt); project(N, u, v, u0, v0); } void setBounds(int N, int b, float[] x) { /// @note 邊界上速度的反向 for (int i = 1; i <= N; i++) { x[IX(0, i)] = b == 1 ? -x[IX(1, i)] : x[IX(1, i)]; x[IX(N + 1, i)] = b == 1 ? -x[IX(N, i)] : x[IX(N, i)]; x[IX(i, 0)] = b == 2 ? -x[IX(i, 1)] : x[IX(i, 1)]; x[IX(i, N + 1)] = b == 2 ? -x[IX(i, N)] : x[IX(i, N)]; } /// @note 四個角處的插值 x[IX(0, 0)] = 0.5 * (x[IX(1, 0)] + x[IX(0, 1)]); x[IX(0, N + 1)] = 0.5 * (x[IX(1, N + 1)] + x[IX(0, N)]); x[IX(N + 1, 0)] = 0.5 * (x[IX(N, 0)] + x[IX(N + 1, 1)]); x[IX(N + 1, N + 1)] = 0.5 * (x[IX(N, N + 1)] + x[IX(N + 1, N)]); } /// @note 跟新速度和密度 void updateSimulation(float dt) { velocityStep(N, u, v, u_prev, v_prev, visc, dt); densityStep(N, dens, dens_prev, u, v, diff, dt); } void drawDensity() { background(0); loadPixels(); float density; for (int index = 0; index < size; index++) { density = dens[index] * 255.0f; pixels[index] = color(density, density, density); } updatePixels(); } void drawCircle(float dt, float val, float radius) { // create B&W mask and apply density where pixels > 0.0f background(0); noStroke(); ellipse(mouseX, mouseY, radius, radius); loadPixels(); float pixelVal; for (int index = 0; index < size; index++) { pixelVal = red(pixels[index]); if (pixelVal > 0.0f) { dens_prev[index] = val; u_prev[index] += dt * forceX; ///< 加力 v_prev[index] += dt * forceY; } } } void draw() { for (int index = 0; index < size; index++) { dens_prev[index] = 0; u_prev[index] = 0; v_prev[index] = 0; } if (mousePressed == true) { if (mouseButton == LEFT) { drawCircle(dt, densityAdd, circleRadius); } } updateSimulation(dt); drawDensity(); }
擴充套件延申
《GPU Gem3》(Chapter 38. Fast Fluid Dynamics Simulation on the GPU)
Simulation and Visualization of a 3D Fluid
流體模擬FluidSimulation:流體模擬基礎 —— 很詳盡的 Navier Stokes 方程的數學推導,很棒棒