SPH法理论
前言
SPH(Smoothed Particle Hydrodynamics)法,中文称其为光滑粒子动力学。最早是为了模拟银河系中天体之间的碰撞与形成等等宇宙物理学的现象而提出的算法[Lucy1977]1。虽然如此,近些年来SPH法在流体模拟热扩散等等的领域内都被广泛应用了起来。而CG(Computer Graphics)相关的研究者[Desbrun1996]2最早在1996年将SPH法引入计算机模拟“软体”的物理效果。随后[Muller2003]3将SPH法扩展到适用于三维粘性流体的模拟。而本文也将主要基于Muller3在2003年的方法进行介绍与实践。
OpenGL版SSF SPH
Note
本文将涉及到大量数学以及物理知识,限于个人能力如有错误和疑点,请在评论指出。
从粒子角度看世界
既然是光滑粒子动力学,那么自然而然需要从粒子的角度来入手了解这个方法。也就是说,世间万物我们都可以将其看成是由一个一个微小的粒子(质点)组成的。那么对于流体来说,我们并不是将其看作一个整体,比如“水”来看待,而是应该将“水”看作是由无数个微小的粒子而组成的。在这种观点下,我们就需要思考如何计算每一个微小的水粒子的受力以及了解它是如何运动起来的。
光滑粒子动力学
基于上述介绍,我们需要将这种理论先数学模型化,这样才能便于我们的后续分析和研究:
f
(
x
i
)
=
∫
f
(
x
j
)
δ
(
x
i
−
x
j
)
d
x
j
(
1
)
f(x_i) = \int f(x_j)\delta(x_i-x_j)dx_j \qquad(1)
f(xi)=∫f(xj)δ(xi−xj)dxj(1)
此处我们要先思考一个问题,如何计算每一个微小粒子的受力。如果说水是由一个个独立的微小粒子构成的话,那么为了研究粒子
x
i
x_i
xi的受力情况。我们先可以从非常特殊的情况来进行考虑。假设我们先需要获取
x
i
x_i
xi的某一种属性或所受到某种力
f
f
f,即
f
(
x
i
)
f(x_i)
f(xi),单独来考虑粒子
x
i
x_i
xi是难以计算它的密度或者受到的压强等等的力,但是如果说考虑在粒子
x
i
x_i
xi的周围存在着一群粒子的话,那么
x
i
x_i
xi所受到的力自然而然可以看为是受到了周围粒子
x
j
x_j
xj的作用而产生的。
回到这个数学模型上,
δ
(
⋅
)
\delta(\cdot)
δ(⋅)被称为狄拉克函数,主要有以下的定义:
δ
(
x
)
=
{
∞
x
=
0
0
x
=
0̸
\delta(x)= \left \{ \begin{array}{c} \infty& &{x = 0} \\ 0& & {x =\not 0} \end{array} \right.
δ(x)={∞0x=0x=0
∫ − ∞ + ∞ δ ( x ) d x = 1 \int ^{+\infty}_{-\infty}\delta(x)dx = 1 ∫−∞+∞δ(x)dx=1
从Wiki4和"小时"5所介绍的内容看来,当我们需要独立考察一个质点的密度得到的是无穷大时, 然而其密度在空间中积分却又能得到有限的质量。我们将这样的密度分布可以使用狄拉克函数来表达。再次回到公式 ( 1 ) (1) (1),我们可以将 δ ( x i − x j ) \delta(x_i-x_j) δ(xi−xj)看作是粒子 x i x_i xi和邻近粒子 x j x_j xj之间的关系,而 f ( x j ) f(x_j) f(xj)则可以表示粒子群 x j x_j xj的某一种属性或者某一种力,那么 f ( x j ) δ ( x i − x j ) f(x_j)\delta(x_i-x_j) f(xj)δ(xi−xj)就可以表示为粒子 x j x_j xj对粒子 x i x_i xi产生的某种作用,对其求积分的含义就是粒子 x i x_i xi的某受力 f ( x i ) f(x_i) f(xi)是由邻近粒子 x j x_j xj所产生某种力 f ( x j ) f(x_j) f(xj)加和而成,即 ∫ f ( x j ) δ ( x i − x j ) d x j \int f(x_j)\delta(x_i-x_j)dx_j ∫f(xj)δ(xi−xj)dxj。
在分析完理论模型后,我们需要将其离散化这样才可以使得计算机可以去具体进行计算。首先,我们需要将广义的狄拉克函数替换成一个光滑核函数 ω \omega ω。具体来说我们希望可以在考察粒子 x i x_i xi时,能够有一个具体范围内的粒子 x j x_j xj来让我们进行计算。
由此,我们可以将式
(
1
)
(1)
(1)重写为:
f
(
x
i
)
≈
∫
f
(
x
j
)
ω
(
∥
x
i
−
x
j
∥
h
)
d
p
j
(
2
)
f(x_i) \approx \int f(x_j)\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})dp_j \qquad(2)
f(xi)≈∫f(xj)ω(h∥xi−xj∥)dpj(2)
其中 h h h是光滑核的半径长度,光滑核函数 ω \omega ω具有以下的性质6:
ω ( ∥ x i − x j ∥ h ) = { 0 ∥ x i − x j ∥ h > 1 δ ( x i − x j ) h → 0 \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})= \left \{ \begin{array}{c} 0&&{\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}>1} \\ \delta(\boldsymbol{x}_i-\boldsymbol{x}_j)& & {h \to 0} \end{array} \right. ω(h∥xi−xj∥)={0δ(xi−xj)h∥xi−xj∥>1h→0
∫ − ∞ + ∞ ω ( ∥ x i − x j ∥ h ) d x j = 1 \int ^{+\infty}_{-\infty}\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})dx_j = 1 ∫−∞+∞ω(h∥xi−xj∥)dxj=1
当粒子 x i x_i xi和粒子群中某一粒子的距离大于光滑核半径的话,函数值为0。就是为了计算粒子 x i x_i xi的某一属性或者受力情况时我们只考虑来自光滑核内部的粒子群的作用。
在三维空间中,我们的积分变量是体积,因而可以将公式
2
2
2离散成以下形式:
f
(
x
i
)
≈
∫
f
(
x
j
)
ω
(
∥
x
i
−
x
j
∥
h
)
d
x
j
≈
∑
i
=
1
N
f
j
ω
(
∥
x
i
−
x
j
∥
h
)
V
j
≈
∑
i
=
1
N
m
j
ρ
j
f
j
ω
(
∥
x
i
−
x
j
∥
h
)
(
3
)
\begin{aligned} f(x_i) &\approx \int f(x_j)\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})dx_j \\ &\approx \sum_{i=1}^N f_j \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})V_j \\ &\approx \sum_{i=1}^N \frac{m_j}{\rho_j}f_j \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(3) \end{aligned}
f(xi)≈∫f(xj)ω(h∥xi−xj∥)dxj≈i=1∑Nfjω(h∥xi−xj∥)Vj≈i=1∑Nρjmjfjω(h∥xi−xj∥)(3)
式
3
3
3中
V
j
V_j
Vj是体积微元, 因为存在
ρ
=
m
V
\rho = \frac{m}{V}
ρ=Vm,因而可以变换出最终形式。
尽管我们现在可以得到粒子 x i x_i xi的某一属性 f ( x i ) f(x_i) f(xi)的离散化结果,为了便于后续的计算,此处我们还需要补充如何计算 f ( x i ) f(x_i) f(xi)的梯度和拉普拉斯算子。
梯度(Gradient)
梯度符号为
∇
\nabla
∇, 梯度为矢量的空间偏导数,结果依然为矢量。
三维梯度有如下形式:
∇
f
(
x
,
y
,
z
)
=
(
∂
f
∂
x
,
∂
f
∂
y
,
∂
f
∂
z
)
\nabla f(x,y,z) =\left(\frac{\partial f}{\partial x},\frac{\partial f}{\partial y},\frac{\partial f}{\partial z}\right)
∇f(x,y,z)=(∂x∂f,∂y∂f,∂z∂f)
散度(Divergence)
散度算子仅仅应用于向量场,它衡量在某一点出相应的矢量聚集或者发散程度,测量方向为径向,结果为标量。3维形式的散度算子如下所示:
∇
⋅
u
=
∇
⋅
(
u
,
v
,
w
)
=
∂
u
∂
x
+
∂
v
∂
y
+
∂
w
∂
z
\nabla\cdot \boldsymbol{u} = \nabla\cdot (u,v,w) =\frac{\partial u}{\partial x}+\frac{\partial v}{\partial y}+\frac{\partial w}{\partial z}
∇⋅u=∇⋅(u,v,w)=∂x∂u+∂y∂v+∂z∂w
输入是矢量,而输出为标量。类比梯度,散度符号
∇
⋅
u
\nabla\cdot \boldsymbol{u}
∇⋅u可以理解为梯度
∇
\nabla
∇与矢量
u
⃗
\vec{u}
u的点乘:
∇
⋅
u
=
(
∂
∂
x
,
∂
∂
y
,
∂
∂
z
)
⋅
(
u
,
v
,
w
)
=
∂
∂
x
u
+
∂
∂
y
v
+
∂
∂
z
w
\nabla\cdot \boldsymbol{u} =\left(\frac{\partial }{\partial x},\frac{\partial }{\partial y},\frac{\partial }{\partial z}\right) \cdot (u,v,w) =\frac{\partial }{\partial x}u+\frac{\partial }{\partial y}v+\frac{\partial }{\partial z}w
∇⋅u=(∂x∂,∂y∂,∂z∂)⋅(u,v,w)=∂x∂u+∂y∂v+∂z∂w
拉普拉斯算子(Laplacian)
拉普拉斯算子定义为梯度的散度,符号表示为
∇
⋅
∇
\nabla\cdot \nabla
∇⋅∇。其中
∇
⋅
\nabla\cdot
∇⋅为散度是标量,后面的
∇
\nabla
∇为梯度是矢量。拉普拉斯算子即为梯度的散度,为二阶微分算子,三维形式如下:
∇
⋅
∇
f
=
∂
2
f
∂
x
2
+
∂
2
f
∂
y
2
+
∂
2
f
∂
z
2
\nabla\cdot \nabla f =\frac{\partial^2f }{\partial x^2}+\frac{\partial^2f }{\partial y^2}+\frac{\partial^2f }{\partial z^2}
∇⋅∇f=∂x2∂2f+∂y2∂2f+∂z2∂2f
偏微分方程 ∇ ⋅ ∇ f = 0 \nabla\cdot \nabla f = 0 ∇⋅∇f=0 被称为拉普拉斯方程。 ∇ ⋅ ∇ f = p \nabla\cdot \nabla f = p ∇⋅∇f=p被称为泊松方程。
此处补充内容来自于[流体模拟基础]7。
物理量 f f f的梯度以及拉普拉斯算子
对于公式
3
3
3来说我们将继续做进一步的离散化:
∇
i
f
(
x
i
)
≈
∇
i
∫
f
(
x
j
)
ω
(
∥
x
i
−
x
j
∥
h
)
d
x
j
≈
∫
[
∇
i
f
(
x
j
)
]
ω
(
∥
x
i
−
x
j
∥
h
)
d
x
j
+
∫
f
(
x
j
)
[
∇
i
ω
(
∥
x
i
−
x
j
∥
h
)
]
d
x
j
≈
∫
f
(
x
j
)
[
∇
i
ω
(
∥
x
i
−
x
j
∥
h
)
]
d
x
j
≈
∑
j
=
1
N
m
j
ρ
j
f
j
∇
ω
(
∥
x
i
−
x
j
∥
h
)
(
4
)
\begin{aligned} \nabla_{i} f(x_i) \approx& \nabla_i \int f(x_j)\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})dx_j \\ \approx& \int [\nabla_i f(x_j)] \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})dx_j + \int f(x_j) \left[\nabla_i \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})\right]dx_j \\ \approx& \int f(x_j) \left[\nabla_i \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})\right]dx_j \\ \approx& \sum_{j=1}^N \frac{m_j}{\rho_j}f_j \nabla\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(4) \end{aligned}
∇if(xi)≈≈≈≈∇i∫f(xj)ω(h∥xi−xj∥)dxj∫[∇if(xj)]ω(h∥xi−xj∥)dxj+∫f(xj)[∇iω(h∥xi−xj∥)]dxj∫f(xj)[∇iω(h∥xi−xj∥)]dxjj=1∑Nρjmjfj∇ω(h∥xi−xj∥)(4)
求得物理量
f
f
f的梯度后,我们同理也可以得到它的拉普拉斯算子:
∇
i
2
f
(
x
i
)
≈
∑
j
=
1
N
m
j
ρ
j
f
j
∇
2
ω
(
∥
x
i
−
x
j
∥
h
)
(
5
)
\nabla_i^2 f(x_i) \approx \sum_{j=1}^N \frac{m_j}{\rho_j}f_j \nabla^2\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(5)
∇i2f(xi)≈j=1∑Nρjmjfj∇2ω(h∥xi−xj∥)(5)
但是此处还存在对称性等问题,在后面通过纳韦斯托克斯方程来求粘着项和压力项的时候会简述。
核函数
为了获取粒子群对当前粒子 x i x_i xi影响作用的大小。我们先衡量了粒子群中每一个粒子到粒子 x i x_i xi之间的距离,并将这个距离代入核函数 ω \omega ω中计算出一个数值,这个值在此处可以理解为权重。
对于核函数
ω
\omega
ω的选取,最常见的是poly6核函数:
W
p
o
l
y
6
(
r
,
d
)
=
315
64
π
r
3
(
1
−
d
2
r
2
)
3
0
≤
d
≤
r
W_{poly6} (r,d)=\frac{315}{64\pi r^3}\left(1-\frac{d^2}{r^2}\right)^3 \qquad 0\leq d \leq r
Wpoly6(r,d)=64πr3315(1−r2d2)30≤d≤r
此处的
d
d
d是粒子群中某一粒子
x
j
x_j
xj到粒子
x
i
x_i
xi之间的距离,
r
r
r是核半径。
同时,我们也可以得到他的一阶导数:
∇
W
p
o
l
y
6
(
r
,
d
)
=
−
945
32
π
r
5
(
1
−
d
2
r
2
)
2
0
≤
d
≤
r
\nabla W_{poly6} (r,d)=\frac{-945}{32\pi r^5}\left(1-\frac{d^2}{r^2}\right)^2 \qquad 0\leq d \leq r
∇Wpoly6(r,d)=32πr5−945(1−r2d2)20≤d≤r
以及二阶导数:
∇
2
W
p
o
l
y
6
(
r
,
d
)
=
945
32
π
r
5
(
1
−
d
2
r
2
)
(
5
d
2
r
2
−
1
)
0
≤
d
≤
r
\nabla^2 W_{poly6} (r,d)=\frac{945}{32\pi r^5}\left(1-\frac{d^2}{r^2}\right) \left(\frac{5d^2}{r^2}-1\right) \qquad 0\leq d \leq r
∇2Wpoly6(r,d)=32πr5945(1−r2d2)(r25d2−1)0≤d≤r
但是,当我们需要计算核函数的梯度以及拉普拉斯算子时,poly6核函数并不是很好的一个选择。我们为了确保流体的不可压缩性,需要计算流体的压力梯度,而poly6核函数的一阶导数在中心处数值为0这就意味着当两个粒子重合时,由于压力梯度为0而会导致没有力能够使得两个粒子分开,这就违背了流体的不可压缩性。
由于以上原因,普遍在计算梯度或拉普拉斯算子时我们并不会选择poly6光滑核,而是会选择使用spiky核函数:
W
s
p
i
k
y
(
r
,
d
)
=
15
π
r
3
(
1
−
d
r
)
3
0
≤
d
≤
r
W_{spiky} (r,d)=\frac{15}{\pi r^3}\left(1-\frac{d}{r}\right)^3 \qquad 0\leq d \leq r
Wspiky(r,d)=πr315(1−rd)30≤d≤r
一阶导数:
∇
W
s
p
i
k
y
(
r
,
d
)
=
−
45
π
r
4
(
1
−
d
r
)
2
0
≤
d
≤
r
\nabla W_{spiky} (r,d)=\frac{-45}{\pi r^4}\left(1-\frac{d}{r}\right)^2 \qquad 0\leq d \leq r
∇Wspiky(r,d)=πr4−45(1−rd)20≤d≤r
二阶导数:
∇
2
W
s
p
i
k
y
(
r
,
d
)
=
90
π
r
5
(
1
−
d
r
)
0
≤
d
≤
r
\nabla^2 W_{spiky} (r,d)=\frac{90}{\pi r^5}\left(1-\frac{d}{r}\right) \qquad 0\leq d \leq r
∇2Wspiky(r,d)=πr590(1−rd)0≤d≤r
同时,我们也提供了光滑核函数的网页绘制版:SPH Kernel 绘制
以及核函数的代码实现(Typescript版):SPH Kernel 代码
在介绍完核函数之后,我们接下来将会介绍具体的SPH流体模拟算法以及实现。
纳维-斯托克斯方程(Navier-Stokes Equation)
1827年由Navier提出了粘性流体的运动方程,而后在1845年由Stokes独立提出粘性系数为一常数的形式。总的来说,纳维-斯托克斯方程描述了粘性不可压缩流体动量守恒的运动方程,简称N-S方程8。而N-S方程又分为两部分,一个是动量方程(式(6)),一个是连续方程(式(14))。先从动量方程的介绍开始:
ρ
(
∂
v
∂
t
+
v
⋅
∇
v
)
=
−
∇
p
+
∇
⋅
T
+
f
(
6
)
\rho \left(\frac{\partial v}{\partial t } + v \cdot \nabla v \right) = -\nabla p + \nabla \cdot T + f \qquad (6)
ρ(∂t∂v+v⋅∇v)=−∇p+∇⋅T+f(6)
ρ \rho ρ为密度, v v v是速度, t t t是时间, p p p是流体压强, T T T为粘滞力, f f f是外力。
动量方程核心其实就是来源于牛顿第二定律:
F
=
m
a
\boldsymbol{F} = m \boldsymbol{a}
F=ma
接下来我们开始详细分析纳维-斯托克斯方程的来源。对于一个粒子
p
p
p而言,我们记他的质量为
m
m
m,加速度为
a
⃗
\vec{a}
a受力为
F
⃗
\vec{F}
F,速度为
u
⃗
≡
(
u
(
t
)
,
v
(
t
)
,
w
(
t
)
)
\vec{u} \equiv (u(t),v(t),w(t))
u≡(u(t),v(t),w(t)),则:
a
≡
D
u
D
t
F
=
m
D
u
D
t
\boldsymbol{a} \equiv \frac{D \boldsymbol{u }}{D t} \quad \boldsymbol{F} = m \frac{D \boldsymbol{u }}{D t}
a≡DtDuF=mDtDu
此处D是在流体力学中很常用的符号,被称为物质导数(material derivative)9。单独定义的原因是u不仅仅是时间的函数,还是空间位置x的函数。由链式法则可以推导出:
D
u
D
t
=
∂
u
∂
t
+
∇
u
⋅
∂
x
∂
t
=
∂
u
∂
t
+
∇
u
⋅
u
(
7
)
\frac{D\boldsymbol{u}}{Dt} = \frac{\partial \boldsymbol{u}}{\partial t} + \nabla \boldsymbol{u} \cdot \frac{\partial \boldsymbol{x}}{\partial t}= \frac{\partial \boldsymbol{u}}{\partial t} + \nabla \boldsymbol{u} \cdot \boldsymbol{u} \qquad (7)
DtDu=∂t∂u+∇u⋅∂t∂x=∂t∂u+∇u⋅u(7)
接下来我们来考虑流体压强,流体压强是流体内部的一种力同时也是维持流体不可压缩的关键6。如果说存在一个极小的区域
Ω
\Omega
Ω,设其表面积为
A
A
A,压强为
P
P
P 那么极小的区域
Ω
\Omega
Ω所受的压力可以写为:
F
p
=
−
d
P
⋅
d
A
(
8
)
\boldsymbol{F}_{p} = -dP \cdot dA \qquad (8)
Fp=−dP⋅dA(8)
而由牛顿第二定律我们可以将式8写为:
F
p
=
m
⋅
a
=
ρ
V
⋅
a
=
ρ
d
A
d
z
⋅
a
=
−
d
P
⋅
d
A
(
9
)
\boldsymbol{F}_{p} = m \cdot \boldsymbol{a} =\rho V \cdot \boldsymbol{a} = \rho dA dz \cdot \boldsymbol{a}= -dP \cdot dA \qquad (9)
Fp=m⋅a=ρV⋅a=ρdAdz⋅a=−dP⋅dA(9)
此处
z
z
z代表压强差最大方向的长度。由式(9)就可以推出:
a
=
−
d
P
ρ
d
z
(
10
)
\boldsymbol{a}= -\frac{dP}{\rho dz} \qquad (10)
a=−ρdzdP(10)
进而我们就可以得到压力项:
F
p
=
m
⋅
−
d
P
ρ
d
z
=
−
V
⋅
∇
p
(
11
)
\boldsymbol{F}_{p} = m \cdot -\frac{dP}{\rho dz} = -V\cdot\nabla p \qquad(11)
Fp=m⋅−ρdzdP=−V⋅∇p(11)
在推导出压力项后,我们还需要流体之间的粘滞力。对于粘滞力我们可以将其看为当前粒子与周围粒子群之间移动速度的关系。再结合上面补充的拉普拉斯算子可看为梯度的散度也就是可以用来衡量一个场中每点变化量的聚合或者发散程度。那么我们可以用拉普拉斯算子来描述当前粒子与周围粒子之间的移动速度的差距,那么我们可以将粘滞力定义为:
F
ν
=
m
⋅
ν
∇
2
u
(
12
)
\boldsymbol{F}_{\nu} = m \cdot \nu\nabla^2 \boldsymbol{u} \qquad (12)
Fν=m⋅ν∇2u(12)
此处
ν
\nu
ν为粘度系数。
将压力项,粘度项和重力再结合式(7),我们可以得到:
m
D
u
D
t
=
m
g
−
V
⋅
∇
p
+
m
⋅
ν
∇
2
u
m \frac{D\boldsymbol{u}}{Dt} =m \boldsymbol{g} -V\cdot\nabla p + m \cdot \nu\nabla^2 \boldsymbol{u}
mDtDu=mg−V⋅∇p+m⋅ν∇2u
D u D t = g ⃗ − 1 ρ ⋅ ∇ p + ν ∇ 2 u \frac{D\boldsymbol{u}}{Dt} =\vec{g} -\frac{1}{\rho} \cdot\nabla p + \nu\nabla^2 \boldsymbol{u} DtDu=g−ρ1⋅∇p+ν∇2u
∂ u ∂ t + ∇ u ⋅ u = g − 1 ρ ⋅ ∇ p + ν ∇ 2 u ( 13 ) \frac{\partial \boldsymbol{u}}{\partial t} + \nabla \boldsymbol{u} \cdot \boldsymbol{u} = \boldsymbol{g} -\frac{1}{\rho} \cdot\nabla p + \nu\nabla^2 \boldsymbol{u} \qquad(13) ∂t∂u+∇u⋅u=g−ρ1⋅∇p+ν∇2u(13)
由动量守恒的角度可以推导出N-S方程的动量方程形式。但由于还存在物质守恒,我们还需要得到N-S方程的连续方程。具体推导内容可以参考“ 电影工业中的流体模拟(三)”10:
∂
ρ
∂
t
+
∇
⋅
(
ρ
u
)
=
0
(
14
)
\frac{\partial \rho}{\partial t} + \nabla \cdot (\rho \boldsymbol{u}) = 0 \qquad(14)
∂t∂ρ+∇⋅(ρu)=0(14)
对于连续方程来说,首先我们模拟的流体是不可压缩的,因而密度
ρ
\rho
ρ是不会随着时间而变化的,即
∂
ρ
∂
t
=
0
\frac{\partial \rho}{\partial t} = 0
∂t∂ρ=0,则可以得到
∇
⋅
u
=
0
\nabla \cdot \boldsymbol{u} = 0
∇⋅u=0。将这个结果代入到式(13)可得:
∂
u
∂
t
=
g
−
1
ρ
⋅
∇
p
+
ν
∇
2
u
(
15
)
\frac{\partial \boldsymbol{u}}{\partial t} = \boldsymbol{g} -\frac{1}{\rho} \cdot\nabla p + \nu\nabla^2 \boldsymbol{u} \qquad(15)
∂t∂u=g−ρ1⋅∇p+ν∇2u(15)
而式(15)正表示了每一个粒子的加速度。到此为止,我们所需要的理论基础已经全部介绍完毕,接下来将结合我们的核函数将加速度中每一项的力进行离散化计算并完成流体的模拟。
流体密度计算
通过式3,我们将物理量
f
f
f设置为密度
ρ
\rho
ρ可以得到:
ρ
(
x
i
)
=
∑
j
=
1
N
m
j
ρ
j
ρ
j
ω
(
∥
x
i
−
x
j
∥
h
)
=
∑
j
=
1
N
m
j
ω
(
∥
x
i
−
x
j
∥
h
)
(
16
)
\begin{aligned} \rho(x_i) &= \sum_{j=1}^N \frac{m_j}{\rho_j}\rho_j \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \\ &= \sum_{j=1}^N m_j \omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})\qquad(16) \end{aligned}
ρ(xi)=j=1∑Nρjmjρjω(h∥xi−xj∥)=j=1∑Nmjω(h∥xi−xj∥)(16)
在实际尝试中光滑核
ω
\omega
ω我们选取使用Poly6作为核函数,通过空间探索方法得到
i
i
i点附近核半径为
h
h
h区域内的所有粒子
j
j
j,并根据式16可以计算得出粒子
i
i
i出的密度。
压力项计算
关于压力计算,我们普遍会使用能够体现压力和密度相互关联的状态方程式。理想气体的状态方程式
p
=
k
ρ
p=k\rho
p=kρ,
k
k
k是气体常数。在SPH法相关模拟中应用最多的方法是Desbrun提出的状态方程11:
p
=
k
(
ρ
−
ρ
0
)
p=k(\rho-\rho_0)
p=k(ρ−ρ0)
但是这个方程的缺点是模拟出的流体是可压缩的,如果要计算非压缩性的流体比较常见的是采用泰特方程式12。但是同样存在一个问题,如果要应用在实时模拟上那么粒子的位置和速度更新的步长要设定在0.0005左右13。这就意味着每模拟1秒我们需要计算2000次左右,离线模拟情况下可以使用。当然还有为了减少步长计算而提出的算法,比如PCISPH14,此处就不过多展开。因而我们普遍在简单对精度要求不高的情况下都会使用Desbrun提出的方法。
在计算压力项时,如果在计算梯度时使用了式4:
∇
i
f
(
x
i
)
≈
∑
j
=
1
N
m
j
ρ
j
f
j
∇
ω
(
∥
x
i
−
x
j
∥
h
)
(
4
)
\nabla_i f(x_i) \approx \sum_{j=1}^N \frac{m_j}{\rho_j}f_j \nabla\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(4)
∇if(xi)≈j=1∑Nρjmjfj∇ω(h∥xi−xj∥)(4)
那么就会产生不对称的问题,就是粒子
i
i
i对粒子
j
j
j的力和粒子
j
j
j对粒子
i
i
i的力在计算时不一样。解决方法是对求导函数除以密度函数:
∇
(
f
ρ
)
=
1
ρ
∇
f
−
f
ρ
2
∇
ρ
∇
f
=
ρ
(
∇
f
ρ
+
f
ρ
2
∇
ρ
)
(
17
)
\begin{aligned} \nabla(\frac{f}{\rho}) &= \frac{1}{\rho}\nabla f - \frac{f}{\rho^2}\nabla \rho \\ \nabla f &=\rho(\nabla\frac{f}{\rho}+\frac{f}{\rho^2}\nabla \rho) \qquad(17) \end{aligned}
∇(ρf)∇f=ρ1∇f−ρ2f∇ρ=ρ(∇ρf+ρ2f∇ρ)(17)
再将式17离散化后可得到:
∇
f
(
x
i
)
≈
ρ
i
∑
j
=
1
N
(
m
j
ρ
j
f
j
ρ
j
+
f
i
ρ
i
2
m
i
ρ
j
ρ
j
)
∇
ω
(
∥
x
i
−
x
j
∥
h
)
≈
ρ
i
∑
j
=
1
N
m
(
f
j
ρ
j
2
+
f
i
ρ
i
2
)
∇
ω
(
∥
x
i
−
x
j
∥
h
)
(
18
)
\begin{aligned} \nabla f(x_i) &\approx \rho_i\sum_{j=1}^N (\frac{m_j}{\rho_j}\frac{f_j}{\rho_j}+\frac{f_i}{\rho_i^2}\frac{m_i}{\rho_j}\rho_j) \nabla\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \\ &\approx \rho_i\sum_{j=1}^N m(\frac{f_j}{\rho_j^2}+\frac{f_i}{\rho_i^2}) \nabla\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(18) \end{aligned}
∇f(xi)≈ρij=1∑N(ρjmjρjfj+ρi2fiρjmiρj)∇ω(h∥xi−xj∥)≈ρij=1∑Nm(ρj2fj+ρi2fi)∇ω(h∥xi−xj∥)(18)
再参考纳维斯托克斯方程(式15)中的压力项:
f
p
r
e
s
s
u
r
e
=
−
m
ρ
⋅
∇
p
(
19
)
f^{pressure} = -\frac{m}{\rho} \cdot\nabla p \qquad(19)
fpressure=−ρm⋅∇p(19)
我们就可以将式18代入式19后可得:
f
i
p
r
e
s
s
u
r
e
=
−
m
i
ρ
i
ρ
i
∑
j
=
1
N
m
(
f
i
ρ
i
2
+
f
j
ρ
j
2
)
∇
ω
(
∥
x
i
−
x
j
∥
h
)
=
−
∑
j
=
1
N
m
2
(
f
j
ρ
j
2
+
f
i
ρ
i
2
)
∇
ω
(
∥
x
i
−
x
j
∥
h
)
(
20
)
\begin{aligned} f^{pressure}_i &= -\frac{m_i}{\rho_i}\rho_i\sum_{j=1}^N m(\frac{f_i}{\rho_i^2}+\frac{f_j}{\rho_j^2}) \nabla\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \\ &=-\sum_{j=1}^N m^2(\frac{f_j}{\rho_j^2}+\frac{f_i}{\rho_i^2}) \nabla\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(20) \end{aligned}
fipressure=−ρimiρij=1∑Nm(ρi2fi+ρj2fj)∇ω(h∥xi−xj∥)=−j=1∑Nm2(ρj2fj+ρi2fi)∇ω(h∥xi−xj∥)(20)
粘着项计算
对于粘着项来说,依然先参考纳维斯托克斯方程(式15):
f
v
i
s
c
o
s
i
t
y
=
m
ν
∇
2
u
(
21
)
f^{viscosity} = m\nu\nabla^2 \boldsymbol{u} \qquad(21)
fviscosity=mν∇2u(21)
此处计算拉普拉斯算子会产生和计算梯度一样的不对称问题,因而我们可以先对求梯度的函数乘一个密度函数即:
∇
(
ρ
f
)
=
f
∇
ρ
+
ρ
∇
f
∇
f
=
1
ρ
∇
(
ρ
f
)
−
f
ρ
∇
ρ
∇
2
f
=
1
ρ
∇
2
(
ρ
f
)
−
f
ρ
∇
2
ρ
(
22
)
\begin{aligned} \nabla(\rho f) &= f\nabla\rho+\rho\nabla f \\ \nabla f &= \frac{1}{\rho}\nabla(\rho f) - \frac{f}{\rho}\nabla\rho \\ \nabla^2 f &= \frac{1}{\rho}\nabla^2(\rho f) - \frac{f}{\rho}\nabla^2\rho \qquad(22) \end{aligned}
∇(ρf)∇f∇2f=f∇ρ+ρ∇f=ρ1∇(ρf)−ρf∇ρ=ρ1∇2(ρf)−ρf∇2ρ(22)
再将式22离散化后可得到:
∇
2
f
(
x
i
)
≈
1
ρ
j
∑
j
=
1
N
m
j
ρ
j
f
j
ρ
j
∇
2
ω
(
∥
x
i
−
x
j
∥
h
)
−
f
i
ρ
j
∑
j
=
1
N
m
j
ρ
j
ρ
j
∇
2
ω
(
∥
x
i
−
x
j
∥
h
)
≈
1
ρ
j
∑
j
=
1
N
m
(
f
j
−
f
i
)
∇
2
ω
(
∥
x
i
−
x
j
∥
h
)
(
23
)
\begin{aligned} \nabla^2 f(x_i) &\approx \frac{1}{\rho_j}\sum_{j=1}^N \frac{m_j}{\rho_j}f_j\rho_j \nabla^2\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) -\frac{f_i}{\rho_j}\sum_{j=1}^N \frac{m_j}{\rho_j}\rho_j \nabla^2\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \\ &\approx \frac{1}{\rho_j}\sum_{j=1}^N m(f_j-f_i)\nabla^2\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h}) \qquad(23) \end{aligned}
∇2f(xi)≈ρj1j=1∑Nρjmjfjρj∇2ω(h∥xi−xj∥)−ρjfij=1∑Nρjmjρj∇2ω(h∥xi−xj∥)≈ρj1j=1∑Nm(fj−fi)∇2ω(h∥xi−xj∥)(23)
继而将式21代入式23中:
f
i
v
i
s
c
o
s
i
t
y
=
ν
∑
j
=
1
N
m
2
(
u
j
−
u
i
ρ
j
)
∇
2
ω
(
∥
x
i
−
x
j
∥
h
)
(
24
)
f^{viscosity}_i = \nu\sum_{j=1}^N m^2(\frac{\boldsymbol{u}_j-\boldsymbol{u}_i}{\rho_j})\nabla^2\omega(\frac{\|\boldsymbol{x}_i-\boldsymbol{x}_j\|}{h})\qquad(24)
fiviscosity=νj=1∑Nm2(ρjuj−ui)∇2ω(h∥xi−xj∥)(24)
得出了压力项和粘着项之后,结合纳维斯托克斯方程,我们只需要再将重力以及其他外力(例如:风力)一起计算后便可以得到每个粒子当前时刻所受力的总和。最后再根据当前粒子的位置和速度便可以计算出下一时刻粒子的新的位置和速度,如此反复计算便可以达到模拟流体的效果。
Typescript版简易Demo
本质上来说推导出上面的压力项和粘着项的计算公式后,实际编程实践并不是非常困难。但是关于空间内粒子搜索的算法以及碰撞检测的部分依然需要实现,本文暂不介绍此部分,感兴趣的可以参考一下“FLUID ENGINE DEVELOPMENT”这本书15。本文实践制作的Demo主要参考了“FLUID ENGINE DEVELOPMENT”15和”SPH理論”13。
在线版本的DEMO:Typescript Version SPH
代码:Typescript Version SPH Code
CUDA版SPH实践
下一篇我会简单介绍如何通过CUDA来实现SPH法和WCSPH法并使用SSF(Screen Space Fluid)方法来实现流体渲染,当然比较常用的方法还有通过MC(Marching Cube)法来实现流体表面重建,可以参考“SPH(光滑粒子流体动力学)流体模拟实现四:各向异性(Anisotropic)表面光滑(1)”16。
[Lucy1977]:L. B. Lucy, A Numerical Approach to the Testing of the Fission Hypothesis, The Astronomical Journal, Vol.82, No.12, pp.1013-1024, 1977. ↩︎
[Desbrun1996]:M. Desbrun and M.-P. Cani, Smoothed Particles: A New Paradigm for Animating Highly Deformable Bodies, Eurographics Workshop on Computer Animation and Simulation (EGCAS), pp.61-76. 1996. ↩︎
[Muller2003]M. Muller, D. Charypar and M. Gross, Particle-based Fluid Simulation for Interactive Applications, In Proc. SCA2003, pp.154-159, 2003. ↩︎ ↩︎
[Desbrun1996]:M. Desbrun and M.-P. Cani, Smoothed Particles: A New Paradigm for Animating Highly Deformable Bodies, Eurographics Workshop on Computer Animation and Simulation (EGCAS), pp.61-76. 1996. ↩︎
[Becker2007]:M. Becker and M. Teschner, Weakly Compressible SPH for Free Furface Flows, In Proc. SCA2007, pp.209-217, 2007. ↩︎
[Solenthaler, B]:Solenthaler, B., & Pajarola, R. (2009). Predictive-corrective incompressible SPH. In ACM SIGGRAPH 2009 papers (pp. 1-6). ↩︎
[Kim2017]:Fluid Engine Development. Boca Raton: Taylor & Francis, a CRC Press, Taylor & Francis Group,2017. ↩︎ ↩︎