├── .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 |
--------------------------------------------------------------------------------