├── .gitignore ├── HW01 ├── README.md ├── Smoke3d │ └── smoke_3D.py └── images │ ├── 01.jpg │ ├── 02.jpg │ ├── 03.jpg │ └── 04.jpg └── HW02 └── MPM3D ├── mpm3d_bunny.py ├── mpm3d_vortex.py └── plyImporter.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /HW01/README.md: -------------------------------------------------------------------------------- 1 | 使用[Taichi](https://github.com/yuanming-hu/taichi) 实现了一个基础的三维烟雾模拟器,基本是按照第四课的内容,包括了Advection 和 Projection。 2 | 3 | * Advection 4 | 代码中的advection 有Semi-Lagrangian 和 BFECC,Advection_Reflection 也已经完成。 5 | 6 | 7 | * Projection 8 | Projection部分主要参考了案例 stable_fluid.py, mgpcg.py和mgpcg_advanced.py。 9 | 我代码中的线性系统求解方法为 Jacobi iteration, red-black Gauss-Seidel, Conjugate gradients, Multigrid preconditioned conjugate gradients。 前两个比较简单,后两个主要参考了案例的写法,还得加深理解。 10 | 其中Jacobi iteration 在迭代次数少的时候(如下图,5次迭代),烟雾形态比较模糊。而Gauss-Seidel在同样迭代次数少的情况下更准确。 11 | 12 | 13 | 14 | Conjugate gradients方法在不用 preconditioner 的情况下收敛很慢,所以迭代次数少的时候形态会有较大差异。使用Jacobi preconditioner 的情况简单试了一下,没有太大差异。而使用Multigrid preconditioner 的效果却很惊人,在迭代十多次的时候就已经收敛了,而且就算只用5个iteration,最后的结果也非常好,和15次的几乎一样。 15 | 16 | 17 | 18 | * 其它 19 | 我使用的dt 为0.04, 测试中发现大于0.07左右就会有Artifact 产生,当然这与初始发射源和速度的设置是有关系的。 20 | 21 | [视频地址(B站):](https://www.bilibili.com/video/BV1cK4y1x782/) 22 | -------------------------------------------------------------------------------- /HW01/Smoke3d/smoke_3D.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | from enum import Enum 4 | import time 5 | 6 | ti.init(arch=ti.gpu) 7 | wi = 1.0 / 6 8 | real = ti.f32 9 | 10 | 11 | class SolverType(Enum): 12 | jacobi = 1 13 | Gauss_Seidel = 2 14 | multigrid = 3 15 | 16 | 17 | class TexPair: 18 | def __init__(self, cur, nxt): 19 | self.cur = cur 20 | self.nxt = nxt 21 | 22 | def swap(self): 23 | self.cur, self.nxt = self.nxt, self.cur 24 | 25 | 26 | @ti.data_oriented 27 | class SmokeSolver: 28 | def __init__(self, x, y, z): 29 | self.res = [x, y, z] 30 | self.resx, self.resy, self.resz = self.res 31 | self.dx = 1.0 32 | self.dt = 0.04 33 | self.inv_dx = 1.0 / self.dx 34 | self.half_inv_dx = 0.5 * self.inv_dx 35 | self.p_alpha = -self.dx * self.dx 36 | self.use_bfecc = True 37 | self.use_reflect = True 38 | self.solver_type = SolverType.jacobi 39 | self.max_iter = 60 40 | 41 | self._velocities = ti.Vector(3, dt=ti.f32, shape=self.res) 42 | self._new_velocities = ti.Vector(3, dt=ti.f32, shape=self.res) 43 | self._vel_temp = ti.Vector(3, dt=ti.f32, shape=self.res) 44 | self.velocity_divs = ti.var(dt=ti.f32, shape=self.res) 45 | self._pressures = ti.var(dt=ti.f32, shape=self.res) 46 | self._new_pressures = ti.var(dt=ti.f32, shape=self.res) 47 | self._dens_buffer = ti.var(dt=ti.f32, shape=self.res) 48 | self._new_dens_buffer = ti.var(dt=ti.f32, shape=self.res) 49 | 50 | self.velocities_pair = TexPair(self._velocities, self._new_velocities) 51 | self.pressures_pair = TexPair(self._pressures, self._new_pressures) 52 | self.dens_pair = TexPair(self._dens_buffer, self._new_dens_buffer) 53 | 54 | self._img = ti.Vector(3, dt=ti.f32, shape=(self.resx + self.resx, self.resy)) 55 | self._pos = ti.Vector(3, dt=ti.f32, shape=self.res) 56 | 57 | # 58 | self.use_multigrid = True 59 | self.N = self.resx 60 | self.n_mg_levels = 4 61 | self.pre_and_post_smoothing = 2 62 | self.bottom_smoothing = 50 63 | self.dim = 3 64 | 65 | self.N_ext = self.N // 2 # number of ext cells set so that that total grid size is still power of 2 66 | self.N_tot = 2 * self.N 67 | 68 | # setup sparse simulation data arrays 69 | self.r = [ti.var(dt=real) for _ in range(self.n_mg_levels)] # residual 70 | self.z = [ti.var(dt=real) 71 | for _ in range(self.n_mg_levels)] # M^-1 self.r 72 | self.x = ti.var(dt=real) # solution 73 | self.p = ti.var(dt=real) # conjugate gradient 74 | self.Ap = ti.var(dt=real) # matrix-vector product 75 | self.alpha = ti.var(dt=real) # step size 76 | self.beta = ti.var(dt=real) # step size 77 | self.sum = ti.var(dt=real) # storage for reductions 78 | 79 | indices = ti.ijk if self.dim == 3 else ti.ij 80 | self.grid = ti.root.pointer(indices, [self.N_tot // 4]).dense( 81 | indices, 4).place(self.x, self.p, self.Ap) 82 | 83 | for l in range(self.n_mg_levels): 84 | self.grid = ti.root.pointer(indices, 85 | [self.N_tot // (4 * 2 ** l)]).dense( 86 | indices, 87 | 4).place(self.r[l], self.z[l]) 88 | 89 | ti.root.place(self.alpha, self.beta, self.sum) 90 | # 91 | 92 | @ti.func 93 | def sample(self, qf, u, v, w): 94 | i, j, k = int(u), int(v), int(w) 95 | i = max(0, min(self.resx - 1, i)) 96 | j = max(0, min(self.resy - 1, j)) 97 | k = max(0, min(self.resz - 1, k)) 98 | return qf[i, j, k] 99 | 100 | @ti.func 101 | def lerp(self, vl, vr, frac): 102 | # frac: [0.0, 1.0] 103 | return vl + frac * (vr - vl) 104 | 105 | @ti.func 106 | def trilerp(self, vf, u, v, w): 107 | s, t, n = u - 0.5, v - 0.5, w - 0.5 108 | iu, iv, iw = int(s), int(t), int(n) 109 | fu, fv, fw = s - iu, t - iv, n - iw 110 | a = self.sample(vf, iu + 0.5, iv + 0.5, iw + 0.5) 111 | b = self.sample(vf, iu + 1.5, iv + 0.5, iw + 0.5) 112 | c = self.sample(vf, iu + 0.5, iv + 1.5, iw + 0.5) 113 | d = self.sample(vf, iu + 1.5, iv + 1.5, iw + 0.5) 114 | e = self.sample(vf, iu + 0.5, iv + 0.5, iw + 1.5) 115 | f = self.sample(vf, iu + 1.5, iv + 0.5, iw + 1.5) 116 | g = self.sample(vf, iu + 0.5, iv + 1.5, iw + 1.5) 117 | h = self.sample(vf, iu + 1.5, iv + 1.5, iw + 1.5) 118 | 119 | bilerp1 = self.lerp(self.lerp(a, b, fu), self.lerp(c, d, fu), fv) 120 | bilerp2 = self.lerp(self.lerp(e, f, fu), self.lerp(g, h, fu), fv) 121 | return self.lerp(bilerp1, bilerp2, fw) 122 | 123 | @ti.func 124 | def mult_const(self, qf: ti.template(), vec3): 125 | for i, j, k in qf: 126 | qf[i, j, k] *= vec3 127 | 128 | @ti.func 129 | def add_scaled(self, qf: ti.template(), bf: ti.template(), vec3): 130 | for i, j, k in qf: 131 | qf[i, j, k] += bf[i, j, k] * vec3 132 | 133 | @ti.func 134 | def sample_min(self, qf: ti.template(), coord): 135 | grid = coord * self.inv_dx - ti.Vector([0.5, 0.5, 0.5]) 136 | I = ti.cast(ti.floor(grid), ti.i32) 137 | min_val = qf[I] 138 | for i, j, k in ti.ndrange(2, 2, 2): 139 | min_val = min(min_val, qf[I + ti.Vector([i, j, k])]) 140 | return min_val 141 | 142 | @ti.func 143 | def sample_max(self, qf: ti.template(), coord): 144 | grid = coord * self.inv_dx - ti.Vector([0.5, 0.5, 0.5]) 145 | I = ti.cast(ti.floor(grid), ti.i32) 146 | max_val = qf[I] 147 | for i, j, k in ti.ndrange(2, 2, 2): 148 | max_val = max(max_val, qf[I + ti.Vector([i, j, k])]) 149 | return max_val 150 | 151 | @ti.func 152 | def back_trace_rk2(self, vf: ti.template(), pos, delta_t): 153 | mid = pos - 0.5 * delta_t * self.trilerp(vf, pos[0], pos[1], pos[2]) 154 | coord = pos - delta_t * self.trilerp(vf, mid[0], mid[1], mid[2]) 155 | return coord 156 | 157 | @ti.kernel 158 | def advect_semi_l(self, vf: ti.template(), qf: ti.template(), new_qf: ti.template()): 159 | for i, j, k in vf: 160 | pos = ti.Vector([i, j, k]) + 0.5 * self.dx 161 | coord = self.back_trace_rk2(vf, pos, self.dt) 162 | new_qf[i, j, k] = self.trilerp(qf, coord[0], coord[1], coord[2]) 163 | 164 | @ti.kernel 165 | def advect_bfecc_scalar(self, vf: ti.template(), qf: ti.template(), new_qf: ti.template(), dt: ti.f32): 166 | for I in ti.grouped(vf): 167 | pos = ti.Vector([I[0], I[1], I[2]]) + 0.5 * self.dx 168 | coord = self.back_trace_rk2(vf, pos, dt) 169 | x1 = self.trilerp(qf, coord[0], coord[1], coord[2]) 170 | coord2 = self.back_trace_rk2(vf, coord, -dt) 171 | x2 = self.trilerp(qf, coord2[0], coord2[1], coord[2]) 172 | new_qf[I] = x1 + 0.5 * (x2 - qf[I]) 173 | 174 | # clipping 175 | min_val = self.sample_min(qf, coord) 176 | max_val = self.sample_max(qf, coord) 177 | if new_qf[I] < min_val or new_qf[I] > max_val: 178 | new_qf[I] = x1 179 | 180 | @ti.kernel 181 | def advect_bfecc_vec3(self, vf: ti.template(), qf: ti.template(), new_qf: ti.template(), dt: ti.f32): 182 | for I in ti.grouped(vf): 183 | pos = ti.Vector([I[0], I[1], I[2]]) + 0.5 * self.dx 184 | coord = self.back_trace_rk2(vf, pos, dt) 185 | x1 = self.trilerp(qf, coord[0], coord[1], coord[2]) 186 | coord2 = self.back_trace_rk2(vf, coord, -dt) 187 | x2 = self.trilerp(qf, coord2[0], coord2[1], coord[2]) 188 | new_qf[I] = x1 + 0.5 * (x2 - qf[I]) 189 | 190 | # clipping 191 | min_val = self.sample_min(qf, coord) 192 | max_val = self.sample_max(qf, coord) 193 | if new_qf[I][0] < min_val[0] or new_qf[I][0] > max_val[0]: 194 | new_qf[I][0] = x1[0] 195 | if new_qf[I][1] < min_val[1] or new_qf[I][1] > max_val[1]: 196 | new_qf[I][1] = x1[1] 197 | if new_qf[I][2] < min_val[2] or new_qf[I][2] > max_val[2]: 198 | new_qf[I][2] = x1[2] 199 | 200 | @ti.kernel 201 | def divergence(self, vf: ti.template()): 202 | for i, j, k in vf: 203 | vl = self.sample(vf, i - 1, j, k)[0] 204 | vr = self.sample(vf, i + 1, j, k)[0] 205 | vb = self.sample(vf, i, j - 1, k)[1] 206 | vt = self.sample(vf, i, j + 1, k)[1] 207 | vh = self.sample(vf, i, j, k - 1)[2] 208 | vq = self.sample(vf, i, j, k + 1)[2] 209 | vc = self.sample(vf, i, j, k) 210 | if i == 0: 211 | vl = -vc[0] 212 | if i == self.resx - 1: 213 | vr = -vc[0] 214 | if j == 0: 215 | vb = -vc[1] 216 | if j == self.resy - 1: 217 | vt = -vc[1] 218 | if k == 0: 219 | vh = -vc[2] 220 | if k == self.resz - 1: 221 | vq = -vc[2] 222 | self.velocity_divs[i, j, k] = (vr - vl + vt - vb + vq - vh) * self.half_inv_dx 223 | 224 | @ti.kernel 225 | def pressure_jacobi(self, pf: ti.template(), new_pf: ti.template()): 226 | for i, j, k in pf: 227 | pl = self.sample(pf, i - 1, j, k) 228 | pr = self.sample(pf, i + 1, j, k) 229 | pb = self.sample(pf, i, j - 1, k) 230 | pt = self.sample(pf, i, j + 1, k) 231 | ph = self.sample(pf, i, j, k - 1) 232 | pq = self.sample(pf, i, j, k + 1) 233 | div = self.velocity_divs[i, j, k] 234 | new_pf[i, j, k] = (pl + pr + pb + pt + ph + pq + self.p_alpha * div) * wi 235 | 236 | @ti.kernel 237 | def Gauss_Seidel(self, pf: ti.template(), new_pf: ti.template()): 238 | for i, j, k in pf: 239 | if (i + j + k) % 2 == 0: 240 | pl = self.sample(pf, i - 1, j, k) 241 | pr = self.sample(pf, i + 1, j, k) 242 | pb = self.sample(pf, i, j - 1, k) 243 | pt = self.sample(pf, i, j + 1, k) 244 | ph = self.sample(pf, i, j, k - 1) 245 | pq = self.sample(pf, i, j, k + 1) 246 | div = self.velocity_divs[i, j, k] 247 | new_pf[i, j, k] = (pl + pr + pb + pt + ph + pq + self.p_alpha * div) * wi 248 | for i, j, k in pf: 249 | if (i + j + k) % 2 == 1: 250 | pl = self.sample(new_pf, i - 1, j, k) 251 | pr = self.sample(new_pf, i + 1, j, k) 252 | pb = self.sample(new_pf, i, j - 1, k) 253 | pt = self.sample(new_pf, i, j + 1, k) 254 | ph = self.sample(new_pf, i, j, k - 1) 255 | pq = self.sample(new_pf, i, j, k + 1) 256 | div = self.velocity_divs[i, j, k] 257 | new_pf[i, j, k] = (pl + pr + pb + pt + ph + pq + self.p_alpha * div) * wi 258 | 259 | @ti.kernel 260 | def subtract_gradient(self, vf: ti.template(), pf: ti.template()): 261 | for i, j, k in vf: 262 | pl = self.sample(pf, i - 1, j, k) 263 | pr = self.sample(pf, i + 1, j, k) 264 | pb = self.sample(pf, i, j - 1, k) 265 | pt = self.sample(pf, i, j + 1, k) 266 | ph = self.sample(pf, i, j, k - 1) 267 | pq = self.sample(pf, i, j, k + 1) 268 | v = self.sample(vf, i, j, k) 269 | v = v - self.half_inv_dx * ti.Vector([pr - pl, pt - pb, pq - ph]) 270 | vf[i, j, k] = v 271 | 272 | @ti.kernel 273 | def my_copy_from(self, af: ti.template(), bf: ti.template()): 274 | for i, j, k in af: 275 | af[i, j, k] = bf[i, j, k] 276 | 277 | @ti.kernel 278 | def reflect(self, af: ti.template(), vdf: ti.template()): 279 | self.mult_const(af, ti.Vector([-1, -1, -1])) 280 | self.add_scaled(af, vdf, ti.Vector([2, 2, 2])) 281 | 282 | # 283 | 284 | @ti.kernel 285 | def init(self): 286 | for i, j, k in self.velocity_divs: 287 | self.r[0][i, j, k] = - 1.0 * self.velocity_divs[i, j, k] 288 | self.z[0][i, j, k] = 0.0 289 | self.Ap[i, j, k] = 0.0 290 | self.p[i, j, k] = 0.0 291 | self.x[i, j, k] = 0.0 292 | 293 | @ti.func 294 | def neighbor_sum(self, x, I): 295 | ret = 0.0 296 | for i in ti.static(range(3)): 297 | offset = ti.Vector.unit(3, i) 298 | ret += x[I + offset] + x[I - offset] 299 | return ret 300 | 301 | @ti.kernel 302 | def compute_Ap(self): 303 | for I in ti.grouped(self.Ap): 304 | self.Ap[I] = (2 * 3) * self.p[I] - self.neighbor_sum( 305 | self.p, I) 306 | 307 | @ti.kernel 308 | def reduce(self, p: ti.template(), q: ti.template()): 309 | self.sum[None] = 0 310 | for I in ti.grouped(p): 311 | self.sum[None] += p[I] * q[I] 312 | 313 | @ti.kernel 314 | def update_x(self): 315 | for I in ti.grouped(self.p): 316 | self.x[I] += self.alpha[None] * self.p[I] 317 | 318 | @ti.kernel 319 | def update_r(self): 320 | for I in ti.grouped(self.p): 321 | self.r[0][I] -= self.alpha[None] * self.Ap[I] 322 | 323 | @ti.kernel 324 | def update_p(self): 325 | for I in ti.grouped(self.p): 326 | self.p[I] = self.z[0][I] + self.beta[None] * self.p[I] 327 | 328 | @ti.kernel 329 | def restrict(self, l: ti.template()): 330 | for I in ti.grouped(self.r[l]): 331 | res = self.r[l][I] - (2.0 * self.dim * self.z[l][I] - 332 | self.neighbor_sum(self.z[l], I)) 333 | self.r[l + 1][I // 2] += res * 0.5 334 | 335 | @ti.kernel 336 | def prolongate(self, l: ti.template()): 337 | for I in ti.grouped(self.z[l]): 338 | self.z[l][I] = self.z[l + 1][I // 2] 339 | 340 | @ti.kernel 341 | def smooth(self, l: ti.template(), phase: ti.template()): 342 | # phase = red/black Gauss-Seidel phase 343 | for I in ti.grouped(self.r[l]): 344 | if (I.sum()) & 1 == phase: 345 | self.z[l][I] = (self.r[l][I] + self.neighbor_sum( 346 | self.z[l], I)) / (2.0 * self.dim) 347 | 348 | def apply_preconditioner(self): 349 | self.z[0].fill(0) 350 | for l in range(self.n_mg_levels - 1): 351 | for i in range(self.pre_and_post_smoothing << l): 352 | self.smooth(l, 0) 353 | self.smooth(l, 1) 354 | self.z[l + 1].fill(0) 355 | self.r[l + 1].fill(0) 356 | self.restrict(l) 357 | 358 | for i in range(self.bottom_smoothing): 359 | self.smooth(self.n_mg_levels - 1, 0) 360 | self.smooth(self.n_mg_levels - 1, 1) 361 | 362 | for l in reversed(range(self.n_mg_levels - 1)): 363 | self.prolongate(l) 364 | for i in range(self.pre_and_post_smoothing << l): 365 | self.smooth(l, 1) 366 | self.smooth(l, 0) 367 | 368 | @ti.kernel 369 | def jacobi_precondition(self): 370 | for i, j, k in self.z[0]: 371 | self.z[0][i, j, k] = self.r[0][i, j, k] * wi 372 | 373 | def mgpcg_run(self): 374 | self.init() 375 | self.reduce(self.r[0], self.r[0]) 376 | initial_rTr = self.sum[None] 377 | 378 | # self.r = b - Ax = b since self.x = 0 379 | # self.p = self.r = self.r + 0 self.p 380 | if self.use_multigrid: 381 | self.apply_preconditioner() 382 | else: 383 | self.z[0].copy_from(self.r[0]) 384 | 385 | self.update_p() 386 | 387 | self.reduce(self.z[0], self.r[0]) 388 | old_zTr = self.sum[None] 389 | 390 | # CG 391 | for i in range(self.max_iter): 392 | # self.alpha = rTr / pTAp 393 | self.compute_Ap() 394 | self.reduce(self.p, self.Ap) 395 | pAp = self.sum[None] 396 | self.alpha[None] = old_zTr / pAp 397 | 398 | # self.x = self.x + self.alpha self.p 399 | self.update_x() 400 | 401 | # self.r = self.r - self.alpha self.Ap 402 | self.update_r() 403 | 404 | # check for convergence 405 | self.reduce(self.r[0], self.r[0]) 406 | rTr = self.sum[None] 407 | if rTr < initial_rTr * 1.0e-12: 408 | print(i) 409 | break 410 | 411 | # self.z = M^-1 self.r 412 | if self.use_multigrid: 413 | self.apply_preconditioner() 414 | else: 415 | self.z[0].copy_from(self.r[0]) 416 | 417 | # self.beta = new_rTr / old_rTr 418 | self.reduce(self.z[0], self.r[0]) 419 | new_zTr = self.sum[None] 420 | self.beta[None] = new_zTr / old_zTr 421 | 422 | # self.p = self.z + self.beta self.p 423 | self.update_p() 424 | old_zTr = new_zTr 425 | 426 | # print(f'iter {i}, residual={rTr}') 427 | # 428 | 429 | @ti.kernel 430 | def source(self): 431 | a1 = self.resx // 2 - 8 432 | b1 = self.resx // 2 + 8 433 | c1 = self.resz // 2 - 8 434 | d1 = self.resz // 2 + 8 435 | 436 | for i, j, k in ti.ndrange((a1, b1), (10, 20), (c1, d1)): 437 | self._dens_buffer[i, j, k] = 0.5 438 | for i, j, k in ti.ndrange((a1, b1), (10, 20), (c1, d1)): 439 | self._velocities[i, j, k] = ti.Vector([0, 50, 0]) 440 | 441 | @ti.kernel 442 | def to_image(self): 443 | for i, j in ti.ndrange((0, self.resx), (0, self.resy)): 444 | self._img[i, j] = self._dens_buffer[i, j, self.resz // 2] * ti.Vector([1, 0.8, 0.8]) 445 | for k, l in ti.ndrange((0, self.resz), (0, self.resy)): 446 | self._img[k + self.resx, l] = self._dens_buffer[self.resx // 2, l, k] * ti.Vector([0.8, 0.9, 1.0]) 447 | 448 | def set_solver(self, solver): 449 | if solver in (1, SolverType.jacobi, "jacobi"): 450 | self.solver_type = SolverType.jacobi 451 | elif solver in (2, SolverType.Gauss_Seidel, "Gauss_Seidel"): 452 | self.solver_type = SolverType.Gauss_Seidel 453 | elif solver in (3, SolverType.multigrid, "multigrid"): 454 | self.solver_type = SolverType.multigrid 455 | else: 456 | self.solver_type = SolverType.jacobi 457 | print("No solver type found. Use jacobi instead.") 458 | 459 | def set_bfecc(self, foo): 460 | self.use_bfecc = True if foo else False 461 | 462 | def set_max_iter(self, val): 463 | self.max_iter = val 464 | 465 | def step(self): 466 | if self.use_bfecc: 467 | self.advect_bfecc_vec3(self.velocities_pair.cur, self.velocities_pair.cur, 468 | self.velocities_pair.nxt, self.dt) 469 | self.advect_bfecc_scalar(self.velocities_pair.cur, self.dens_pair.cur, 470 | self.dens_pair.nxt, self.dt) 471 | else: 472 | self.advect_semi_l(self.velocities_pair.cur, self.velocities_pair.cur, self.velocities_pair.nxt) 473 | self.advect_semi_l(self.velocities_pair.cur, self.dens_pair.cur, self.dens_pair.nxt) 474 | self.velocities_pair.swap() 475 | self.dens_pair.swap() 476 | self.divergence(self.velocities_pair.cur) 477 | 478 | if self.solver_type == SolverType.jacobi: 479 | for _ in range(self.max_iter): 480 | self.pressure_jacobi(self.pressures_pair.cur, self.pressures_pair.nxt) 481 | self.pressures_pair.swap() 482 | self.subtract_gradient(self.velocities_pair.cur, self.pressures_pair.cur) 483 | elif self.solver_type == SolverType.Gauss_Seidel: 484 | for _ in range(self.max_iter): 485 | self.Gauss_Seidel(self.pressures_pair.cur, self.pressures_pair.nxt) 486 | self.pressures_pair.swap() 487 | self.subtract_gradient(self.velocities_pair.cur, self.pressures_pair.cur) 488 | else: 489 | self.mgpcg_run() 490 | self.pressures_pair.cur.copy_from(self.x) 491 | self.subtract_gradient(self.velocities_pair.cur, self.pressures_pair.cur) 492 | 493 | def step_reflect(self): 494 | self.advect_bfecc_vec3(self.velocities_pair.cur, self.velocities_pair.cur, 495 | self.velocities_pair.nxt, self.dt * 0.5) 496 | self.advect_bfecc_scalar(self.velocities_pair.cur, self.dens_pair.cur, 497 | self.dens_pair.nxt, self.dt * 0.5) 498 | self.my_copy_from(self._vel_temp, self.velocities_pair.nxt) 499 | self.divergence(self.velocities_pair.nxt) 500 | for _ in range(self.max_iter): 501 | self.Gauss_Seidel(self.pressures_pair.cur, self.pressures_pair.nxt) 502 | self.pressures_pair.swap() 503 | self.subtract_gradient(self._vel_temp, self.pressures_pair.cur) 504 | self.reflect(self.velocities_pair.nxt, self._vel_temp) 505 | self.velocities_pair.swap() 506 | self.dens_pair.swap() 507 | 508 | self.advect_bfecc_vec3(self._vel_temp, self.velocities_pair.cur, 509 | self.velocities_pair.nxt, self.dt * 0.5) 510 | self.advect_bfecc_scalar(self._vel_temp, self.dens_pair.cur, 511 | self.dens_pair.nxt, self.dt * 0.5) 512 | self.velocities_pair.swap() 513 | self.dens_pair.swap() 514 | self.divergence(self.velocities_pair.cur) 515 | for _ in range(self.max_iter): 516 | self.Gauss_Seidel(self.pressures_pair.cur, self.pressures_pair.nxt) 517 | self.pressures_pair.swap() 518 | self.subtract_gradient(self.velocities_pair.cur, self.pressures_pair.cur) 519 | 520 | def reset(self): 521 | self._dens_buffer.fill(0.0) 522 | self._velocities.fill(0.0) 523 | self._pressures.fill(0.0) 524 | 525 | @ti.kernel 526 | def place_pos(self): 527 | for i, j, k in self._pos: 528 | self._pos[i, j, k] = 0.01 * ti.Vector([i, j, k]) 529 | 530 | def save_ply(self, frame): 531 | series_prefix = "smoke.ply" 532 | num_vertices = self.resx * self.resy * self.resz 533 | self.place_pos() 534 | np_pos = np.reshape(self._pos.to_numpy(), (num_vertices, 3)) 535 | np_dens = np.reshape(self._dens_buffer.to_numpy(), (num_vertices, 1)) 536 | writer = ti.PLYWriter(num_vertices=num_vertices) 537 | writer.add_vertex_pos(np_pos[:, 0], np_pos[:, 1], np_pos[:, 2]) 538 | writer.add_vertex_alpha(np_dens) 539 | writer.export_frame_ascii(frame, series_prefix) 540 | 541 | def run(self): 542 | gui = ti.GUI("smoke_solver", (self.resx + self.resz, self.resy)) 543 | self.reset() 544 | self.source() 545 | count = 0 546 | timer = 0 547 | while count < 300: 548 | for e in gui.get_events(ti.GUI.PRESS): 549 | if e.key in [ti.GUI.ESCAPE, ti.GUI.EXIT]: 550 | exit() 551 | if self.use_reflect: 552 | self.step_reflect() 553 | else: 554 | self.step() 555 | self.source() 556 | self.to_image() 557 | gui.set_image(self._img) 558 | delta_t = time.time() - timer 559 | fps = 1/delta_t 560 | timer = time.time() 561 | gui.text(content=f'fps: {fps:.1f}', pos=(0, 0.98), color=0xffaa77) 562 | filename = f'advection_reflect_{self.max_iter:02d}iter_{count:05d}.png' 563 | gui.show() 564 | # self.save_ply(count) 565 | count += 1 566 | 567 | 568 | def main(): 569 | smoke_solver = SmokeSolver(160, 256, 160) 570 | smoke_solver.set_bfecc(True) 571 | smoke_solver.set_solver(2) 572 | smoke_solver.set_max_iter(30) 573 | smoke_solver.run() 574 | 575 | 576 | if __name__ == '__main__': 577 | main() 578 | -------------------------------------------------------------------------------- /HW01/images/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShaneFX/GAMES201/330d9c75cacfad6901605d3f589eea11954d9a93/HW01/images/01.jpg -------------------------------------------------------------------------------- /HW01/images/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShaneFX/GAMES201/330d9c75cacfad6901605d3f589eea11954d9a93/HW01/images/02.jpg -------------------------------------------------------------------------------- /HW01/images/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShaneFX/GAMES201/330d9c75cacfad6901605d3f589eea11954d9a93/HW01/images/03.jpg -------------------------------------------------------------------------------- /HW01/images/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShaneFX/GAMES201/330d9c75cacfad6901605d3f589eea11954d9a93/HW01/images/04.jpg -------------------------------------------------------------------------------- /HW02/MPM3D/mpm3d_bunny.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | from plyImporter import PlyImporter 4 | 5 | 6 | ti.init(arch=ti.gpu) 7 | 8 | ply3 = PlyImporter("bunny.ply") 9 | dim = 3 10 | n_particles = ply3.get_count() 11 | n_grid = 128 12 | dx = 1 / n_grid 13 | inv_dx = 1 / dx 14 | dt = 2.0e-4 15 | p_vol = (dx * 0.5)**3 16 | p_rho = 1 17 | p_mass = p_vol * p_rho 18 | E = 400 19 | 20 | x = ti.Vector(dim, dt=ti.f32, shape=n_particles) 21 | v = ti.Vector(dim, dt=ti.f32, shape=n_particles) 22 | C = ti.Matrix(dim, dim, dt=ti.f32, shape=n_particles) 23 | J = ti.var(dt=ti.f32, shape=n_particles) 24 | grid_v = ti.Vector(dim, dt=ti.f32, shape=(n_grid, n_grid, n_grid)) 25 | grid_m = ti.var(dt=ti.f32, shape=(n_grid, n_grid, n_grid)) 26 | img = ti.Vector(2, dt=ti.f32, shape=n_particles) 27 | 28 | 29 | @ti.kernel 30 | def substep(): 31 | for p in x: 32 | base = (x[p] * inv_dx - 0.5).cast(int) 33 | fx = x[p] * inv_dx - base.cast(float) 34 | w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] 35 | stress = -dt * p_vol * (J[p] - 1) * 4 * inv_dx * inv_dx * E 36 | affine = ti.Matrix([[stress, 0, 0], [0, stress, 0], [0, 0, stress]]) + p_mass * C[p] 37 | for i in ti.static(range(3)): 38 | for j in ti.static(range(3)): 39 | for k in ti.static(range(3)): 40 | offset = ti.Vector([i, j, k]) 41 | dpos = (offset.cast(float) - fx) * dx 42 | weight = w[i][0] * w[j][1] * w[k][2] 43 | grid_v[base + offset].atomic_add( 44 | weight * (p_mass * v[p] + affine @ dpos)) 45 | grid_m[base + offset].atomic_add(weight * p_mass) 46 | 47 | for i, j, k in grid_m: 48 | if grid_m[i, j, k] > 0: 49 | bound = 3 50 | inv_m = 1 / grid_m[i, j, k] 51 | grid_v[i, j, k] = inv_m * grid_v[i, j, k] 52 | grid_v[i, j, k][1] -= dt * 9.8 53 | if i < bound and grid_v[i, j, k][0] < 0: 54 | grid_v[i, j, k][0] = 0 55 | if i > n_grid - bound and grid_v[i, j, k][0] > 0: 56 | grid_v[i, j, k][0] = 0 57 | if j < bound and grid_v[i, j, k][1] < 0: 58 | grid_v[i, j, k][1] = 0 59 | if j > n_grid - bound and grid_v[i, j, k][1] > 0: 60 | grid_v[i, j, k][1] = 0 61 | if k < bound and grid_v[i, j, k][2] < 0: 62 | grid_v[i, j, k][2] = 0 63 | if k > n_grid - bound and grid_v[i, j, k][2] > 0: 64 | grid_v[i, j, k][2] = 0 65 | 66 | for p in x: 67 | base = (x[p] * inv_dx - 0.5).cast(int) 68 | fx = x[p] * inv_dx - base.cast(float) 69 | w = [ 70 | 0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1.0) ** 2, 0.5 * (fx - 0.5) ** 2 71 | ] 72 | new_v = ti.Vector.zero(ti.f32, 3) 73 | new_C = ti.Matrix.zero(ti.f32, 3, 3) 74 | for i in ti.static(range(3)): 75 | for j in ti.static(range(3)): 76 | for k in ti.static(range(3)): 77 | dpos = ti.Vector([i, j, k]).cast(float) - fx 78 | g_v = grid_v[base + ti.Vector([i, j, k])] 79 | weight = w[i][0] * w[j][1] * w[k][2] 80 | new_v += weight * g_v 81 | new_C += 4 * weight * g_v.outer_product(dpos) * inv_dx 82 | 83 | v[p] = new_v 84 | x[p] += dt * v[p] 85 | J[p] *= 1 + dt * new_C.trace() 86 | C[p] = new_C 87 | 88 | 89 | @ti.kernel 90 | def to_img(): 91 | for i in img: 92 | img[i] = ti.Vector([x[i][0], x[i][1]]) 93 | 94 | 95 | def save_ply(frame1): 96 | series_prefix = "bunny.ply" 97 | num_vertices = n_particles 98 | np_pos = np.reshape(x.to_numpy(), (num_vertices, 3)) 99 | writer = ti.PLYWriter(num_vertices=num_vertices) 100 | writer.add_vertex_pos(np_pos[:, 0], np_pos[:, 1], np_pos[:, 2]) 101 | writer.export_frame_ascii(frame1, series_prefix) 102 | 103 | 104 | def initial_state(): 105 | x.from_numpy(ply3.get_array()) 106 | J.fill(1) 107 | 108 | 109 | initial_state() 110 | for frame in range(100): 111 | for s in range(200): 112 | grid_v.fill([0, 0, 0]) 113 | grid_m.fill(0) 114 | substep() 115 | 116 | # save_ply(frame) 117 | -------------------------------------------------------------------------------- /HW02/MPM3D/mpm3d_vortex.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | from plyImporter import PlyImporter 4 | ti.init(arch=ti.gpu) 5 | 6 | 7 | ply1 = PlyImporter("initial_a.ply") 8 | ply2 = PlyImporter("velocity.ply") 9 | dim = 3 10 | n1 = ply1.get_count() 11 | n2 = ply2.get_count() 12 | n_particles = n1 13 | n_grid = 128 14 | dx = 1 / n_grid 15 | inv_dx = 1 / dx 16 | dt = 1.0e-4 17 | p_vol = (dx * 0.5)**3 18 | p_rho = 1 19 | p_mass = p_vol * p_rho 20 | E = 400 21 | 22 | x = ti.Vector(dim, dt=ti.f32, shape=n_particles) 23 | v = ti.Vector(dim, dt=ti.f32, shape=n_particles) 24 | C = ti.Matrix(dim, dim, dt=ti.f32, shape=n_particles) 25 | J = ti.var(dt=ti.f32, shape=n_particles) 26 | grid_v = ti.Vector(dim, dt=ti.f32, shape=(n_grid, n_grid, n_grid)) 27 | grid_m = ti.var(dt=ti.f32, shape=(n_grid, n_grid, n_grid)) 28 | img = ti.Vector(2, dt=ti.f32, shape=n_particles) 29 | 30 | 31 | @ti.kernel 32 | def substep(): 33 | for p in x: 34 | base = (x[p] * inv_dx - 0.5).cast(int) 35 | fx = x[p] * inv_dx - base.cast(float) 36 | w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] 37 | stress = -dt * p_vol * (J[p] - 1) * 4 * inv_dx * inv_dx * E 38 | affine = ti.Matrix([[stress, 0, 0], [0, stress, 0], [0, 0, stress]]) + p_mass * C[p] 39 | for i in ti.static(range(3)): 40 | for j in ti.static(range(3)): 41 | for k in ti.static(range(3)): 42 | offset = ti.Vector([i, j, k]) 43 | dpos = (offset.cast(float) - fx) * dx 44 | weight = w[i][0] * w[j][1] * w[k][2] 45 | grid_v[base + offset].atomic_add( 46 | weight * (p_mass * v[p] + affine @ dpos)) 47 | grid_m[base + offset].atomic_add(weight * p_mass) 48 | 49 | for i, j, k in grid_m: 50 | if grid_m[i, j, k] > 0: 51 | bound = 3 52 | inv_m = 1 / grid_m[i, j, k] 53 | grid_v[i, j, k] = inv_m * grid_v[i, j, k] 54 | grid_v[i, j, k][1] -= dt * 9.8 55 | if i < bound and grid_v[i, j, k][0] < 0: 56 | grid_v[i, j, k][0] = 0 57 | if i > n_grid - bound and grid_v[i, j, k][0] > 0: 58 | grid_v[i, j, k][0] = 0 59 | if j < bound and grid_v[i, j, k][1] < 0: 60 | grid_v[i, j, k][1] = 0 61 | if j > n_grid - bound and grid_v[i, j, k][1] > 0: 62 | grid_v[i, j, k][1] = 0 63 | if k < bound and grid_v[i, j, k][2] < 0: 64 | grid_v[i, j, k][2] = 0 65 | if k > n_grid - bound and grid_v[i, j, k][2] > 0: 66 | grid_v[i, j, k][2] = 0 67 | 68 | for p in x: 69 | base = (x[p] * inv_dx - 0.5).cast(int) 70 | fx = x[p] * inv_dx - base.cast(float) 71 | w = [ 72 | 0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1.0) ** 2, 0.5 * (fx - 0.5) ** 2 73 | ] 74 | new_v = ti.Vector.zero(ti.f32, 3) 75 | new_C = ti.Matrix.zero(ti.f32, 3, 3) 76 | for i in ti.static(range(3)): 77 | for j in ti.static(range(3)): 78 | for k in ti.static(range(3)): 79 | dpos = ti.Vector([i, j, k]).cast(float) - fx 80 | g_v = grid_v[base + ti.Vector([i, j, k])] 81 | weight = w[i][0] * w[j][1] * w[k][2] 82 | new_v += weight * g_v 83 | new_C += 4 * weight * g_v.outer_product(dpos) * inv_dx 84 | 85 | # if new_v.norm() > dx * 50: 86 | # new_v = 50 * dx * new_v.normalized() 87 | v[p] = new_v 88 | x[p] += dt * v[p] 89 | J[p] *= 1 + dt * new_C.trace() 90 | C[p] = new_C 91 | 92 | 93 | @ti.kernel 94 | def to_img(): 95 | for i in img: 96 | img[i] = ti.Vector([x[i][0], x[i][1]]) 97 | 98 | 99 | def save_ply(frame1): 100 | series_prefix = "liquid.ply" 101 | num_vertices = n_particles 102 | np_pos = np.reshape(x.to_numpy(), (num_vertices, 3)) 103 | writer = ti.PLYWriter(num_vertices=num_vertices) 104 | writer.add_vertex_pos(np_pos[:, 0], np_pos[:, 1], np_pos[:, 2]) 105 | writer.export_frame_ascii(frame1, series_prefix) 106 | 107 | 108 | def initial_state(): 109 | x.from_numpy(np.concatenate((ply1.get_array(), ply2.get_array()))) 110 | J.fill(1) 111 | 112 | 113 | def update_v(): 114 | grid_v.from_numpy(ply2.get_array().reshape(n_grid, n_grid, n_grid, 3)) 115 | 116 | 117 | initial_state() 118 | ply2.multiply(dt * 0.00005) 119 | for frame in range(100): 120 | for s in range(400): 121 | update_v() 122 | grid_m.fill(0) 123 | substep() 124 | # save_ply(frame) 125 | -------------------------------------------------------------------------------- /HW02/MPM3D/plyImporter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from plyfile import PlyData 3 | 4 | 5 | class PlyImporter: 6 | def __init__(self, file): 7 | self.file_name = file 8 | plydata = PlyData.read(self.file_name) 9 | data = plydata['vertex'].data 10 | self.count = plydata['vertex'].count 11 | self.np_array = np.array([[x, y, z] for x, y, z in data]) 12 | 13 | def get_array(self): 14 | return self.np_array 15 | 16 | def get_count(self): 17 | return self.count 18 | 19 | def multiply(self, mul): 20 | self.np_array *= mul 21 | 22 | 23 | if __name__ == "__main__": 24 | ply1 = PlyImporter("test.ply") 25 | ply1.multiply(0.5) 26 | np1 = ply1.get_array() 27 | np2 = np1.reshape((4, 4, 4, 3)) 28 | print(np2) 29 | --------------------------------------------------------------------------------