符号说明
仿真物体包括 N N N 个节点和 M M M 个约束。
每个节点 i ∈ [ 1 , . . . , N ] i\in [1,...,N] i∈[1,...,N] 包含的参数有质量 m i m_i mi 、位置 x i \boldsymbol{x_i} xi 和速度 v i \boldsymbol{v_i} vi ;
每个约束 j ∈ [ 1 , . . . , M ] j\in[1,...,M] j∈[1,...,M] 包含的参数有:
- 基数 n j n_j nj ;
- 约束函数 C j : R 3 n j → R C_j: \mathbb{R}^{3n_j} \rightarrow \mathbb{R} Cj:R3nj→R ;
- 节点编号集合 { i 1 , . . . , i n j } , i k ∈ [ 1 , . . . , N ] \{i_1,...,i_{n_j}\}, \ i_k\in[1,...,N] {i1,...,inj}, ik∈[1,...,N] ;
- 刚度参数 k j ∈ [ 0 , 1 ] k_j\in[0,1] kj∈[0,1] ,描述约束的强度;
- 约束类型:等式 或 不等式;
- 等式表示 C j ( x i 1 , . . . , x i n j ) = 0 C_j(\boldsymbol{x_{i_1}},...,\boldsymbol{x_{i_{n_j}}})=0 Cj(xi1,...,xinj)=0 ;
- 不等式表示 C j ( x i 1 , . . . , x i n j ) ≥ 0 C_j(\boldsymbol{x_{i_1}},...,\boldsymbol{x_{i_{n_j}}})\ge 0 Cj(xi1,...,xinj)≥0 ;
算法流程
首先初始化 x i = x i 0 \boldsymbol{x_i}=\boldsymbol{x_i^0} xi=xi0, v i = v i 0 \boldsymbol{v_i}=\boldsymbol{v_i^0} vi=vi0, ω i = 1 / m i \omega_i=1/m_i ωi=1/mi ,然后在每个 Δ t \Delta t Δt 时间内重复一次以下过程:
- 处理外力: v i ← v i + Δ t w i f e x t ( x i ) \boldsymbol{v_i} \leftarrow \boldsymbol{v_i} + \Delta t w_i f_{ext}(\boldsymbol{x_i}) vi←vi+Δtwifext(xi) ;
- 速度阻尼: d a m p V e l o c i t i e s ( v 1 , . . . , v N ) dampVelocities(\boldsymbol{v_1},...,\boldsymbol{v_N}) dampVelocities(v1,...,vN) ;
- 下一个位置预测: p i ← x i + Δ t v i \boldsymbol{p_i} \leftarrow \boldsymbol{x_i}+\Delta t \boldsymbol{v_i} pi←xi+Δtvi ;
- 碰撞检测,生成 M c o l l M_{coll} Mcoll 个碰撞约束: g e n e r a t e C o l l i s i o n C o n s t r a i n t s ( x i → p i ) generateCollisionConstraints(\boldsymbol{x_i}\rightarrow \boldsymbol{p_i}) generateCollisionConstraints(xi→pi) ;
- 若干次迭代:每次迭代处理一遍所有约束 p r o j e c t C o n s t r a i n t s ( C 1 , . . . , C M + M c o l l , p 1 , . . . , p N ) projectConstraints(C_1,...,C_{M+M_{coll}},\boldsymbol{p_1},...,\boldsymbol{p_N}) projectConstraints(C1,...,CM+Mcoll,p1,...,pN) ;
- 速度、位置更新: v i ← ( p i − x i ) / Δ t \boldsymbol{v_i}\leftarrow (\boldsymbol{p_i}-\boldsymbol{x_i})/\Delta t vi←(pi−xi)/Δt , x i ← p i \boldsymbol{x_i}\leftarrow \boldsymbol{p_i} xi←pi ;
- 碰撞节点的速度更新: v e l o c i t y U p d a t e ( v i , . . . , v N ) velocityUpdate(\boldsymbol{v_i},...,\boldsymbol{v_N}) velocityUpdate(vi,...,vN) ;
流程详细说明
2
由于物体内部的摩擦力、粘性等特性,节点速度变化时会有阻尼。PBD中速度阻尼的计算方法如下:
首先计算系统的质心、角速度:
- 质心 x c = ( Σ i x i m i ) / ( Σ i m i ) \boldsymbol{x_c}=(\Sigma_i{\boldsymbol{x_i}m_i})/(\Sigma_i{m_i}) xc=(Σiximi)/(Σimi) ;
- 质心速度 v c = ( Σ i v i m i ) / ( Σ i m i ) \boldsymbol{v_c}=(\Sigma_i{\boldsymbol{v_i}m_i})/(\Sigma_i{m_i}) vc=(Σivimi)/(Σimi) ;
- 系统角动量 L = Σ i r i × ( m i v i ) \boldsymbol{L}=\Sigma_i{\boldsymbol{r_i}\times (m_i\boldsymbol{v_i})} L=Σiri×(mivi) ,其中 r i = x i − x c \boldsymbol{r_i}=\boldsymbol{x_i}-\boldsymbol{x_c} ri=xi−xc ;
- 系统转动惯量 I = Σ i r i ~ r i ~ T m i \boldsymbol{I}=\Sigma_i{\widetilde{\boldsymbol{r_i}}\widetilde{\boldsymbol{r_i}}^Tm_i} I=Σiri ri Tmi ;
- 系统角速度 ω = I − 1 L \boldsymbol{\omega}=\boldsymbol{I}^{-1}\boldsymbol{L} ω=I−1L ;
然后设定一个全局的参数 k d a m p i n g ∈ [ 0 , 1 ] k_{damping}\in[0,1] kdamping∈[0,1] ,对于每个节点的速度 v i \boldsymbol{v_i} vi,做如下变换:
- Δ v i = v c + ω × r i − v i \Delta \boldsymbol{v_i}=\boldsymbol{v_c}+\boldsymbol{\omega}\times\boldsymbol{r_i}-\boldsymbol{v_i} Δvi=vc+ω×ri−vi;
- v i ← v i + k d a m p i n g Δ v i \boldsymbol{v_i}\leftarrow\boldsymbol{v_i}+k_{damping}\Delta\boldsymbol{v_i} vi←vi+kdampingΔvi ;
从中可看出 k d a m p i n g k_{damping} kdamping 描述了阻尼强度,如果 k d a m p i n g = 1 k_{damping}=1 kdamping=1,那么系统为刚体;如果 k d a m p i n g = 0 k_{damping}=0 kdamping=0,那么每个节点自由运动,不存在阻尼。
4
当某个物体的一个节点从 x \boldsymbol{x} x 运动到 p \boldsymbol{p} p 时,可能与其它物体发生碰撞,这里碰撞有两种类型(原文描述的两种类型为 continuous collision 和 static collision,但我不明白这么命名的原因):
- x \boldsymbol{x} x 在另一个物体外部而 p \boldsymbol{p} p 在另一个物体内部;
- x \boldsymbol{x} x 和 p \boldsymbol{p} p 都在另一个物体内部;

对于这两种类型,我们都希望 p p p 能够被约束到另一个物体外部,因此构造如下约束:
- 对于第一种类型,构造不等式约束 C ( p ) = ( p − q c ) ⋅ n c C(\boldsymbol{p})=(\boldsymbol{p}-\boldsymbol{q_c})\cdot\boldsymbol{n_c} C(p)=(p−qc)⋅nc ,刚度参数 k = 1 k=1 k=1;
- 对于第二种类型,构造不等式约束 C ( p ) = ( p − q s ) ⋅ n s C(\boldsymbol{p})=(\boldsymbol{p}-\boldsymbol{q_s})\cdot\boldsymbol{n_s} C(p)=(p−qs)⋅ns ,刚度参数 k = 1 k=1 k=1;
还有其它很多碰撞检测的约束方法,比如运动节点和运动三角形的。
5
这一步要迭代若干次,因为其本质是用了类似**牛顿迭代法(Newton’s Method / Newton-Raphson Method)**的方法。
牛顿迭代法,用于求解方程 f ( x ) = 0 f(x)=0 f(x)=0,迭代函数为 x k + 1 = x k − f ( x k ) f ′ ( x k ) x_{k+1}=x_k-\frac{f(x_k)}{f^{'}(x_k)} xk+1=xk−f′(xk)f(xk),迭代足够多次时 x x x 接近零点。
而每一次迭代,采取了 Gauss-Seidel method 的思想,即独立地一个个地处理每个约束(Gauss-Seidel method 在求解 A x = b Ax=b Ax=b 时,一个个地求取 x i x_i xi,并且当前求取的 x i x_i xi 会用到后面分量的求取中)。
因此,第5步的过程就是:循环若干次,每次循环时一个个处理所有约束。
约束有两类,一类是固有的 M M M 个约束,另一类是每次碰撞检测生成的 M c o l l M_{coll} Mcoll 个约束,前者需要满足动量和角动量守恒,而后者不用。由于 C ( p ) C(\boldsymbol{p}) C(p) 的值与刚体运动(平移、旋转)无关,也就是说刚体运动对应 p \boldsymbol{p} p 的变化位于 C ( p ) C(\boldsymbol{p}) C(p) 的等值线上,因此梯度 ∇ p C ( p ) \nabla_{\boldsymbol{p}}C(\boldsymbol{p}) ∇pC(p) 与刚体运动正交,如果 p \boldsymbol{p} p 的变化 Δ p \Delta\boldsymbol{p} Δp 位于 ∇ p C ( p ) \nabla_{\boldsymbol{p}}C(\boldsymbol{p}) ∇pC(p) 的方向上,那么自然能保持动量守恒(这里存疑……)。
假设所有节点质量相等,下面说明如何处理一个等式约束 C ( p ) = C ( p 1 , . . . , p n ) = 0 C(\boldsymbol{p})=C(\boldsymbol{p_1},...,\boldsymbol{p_n})=0 C(p)=C(p1,...,pn)=0:
对于当前的
p
\boldsymbol{p}
p ,我们需要计算一个
Δ
p
\boldsymbol{\Delta p}
Δp,使得
C
(
p
+
Δ
p
)
≈
C
(
p
)
+
∇
p
C
(
p
)
⋅
Δ
p
=
0
C(\boldsymbol{p}+\boldsymbol{\Delta p})\approx C(\boldsymbol{p})+\nabla_{\boldsymbol{p}}C(\boldsymbol{p})\cdot\boldsymbol{\Delta p}=0
C(p+Δp)≈C(p)+∇pC(p)⋅Δp=0 ,由于
Δ
p
\Delta\boldsymbol{p}
Δp 在梯度的方向上,因此可设
Δ
p
=
λ
∇
p
C
(
p
)
\Delta\boldsymbol{p}=\lambda\nabla_{\boldsymbol{p}}C(\boldsymbol{p})
Δp=λ∇pC(p) ,联立求解可得
Δ
p
=
−
C
(
p
)
∣
∇
p
C
(
p
)
∣
2
∇
p
C
(
p
)
(1)
\Delta\boldsymbol{p}=-\frac{C(\boldsymbol{p})}{|\nabla_{\boldsymbol{p}}C(\boldsymbol{p})|^2}\nabla_{\boldsymbol{p}}C(\boldsymbol{p}) \tag{1}
Δp=−∣∇pC(p)∣2C(p)∇pC(p)(1)
上式就是牛顿迭代法中的一步变化
p
←
p
+
Δ
p
\boldsymbol{p}\leftarrow\boldsymbol{p}+\Delta\boldsymbol{p}
p←p+Δp,而一次迭代需要对所有约束都作这样的计算。
我们将
Δ
p
\Delta\boldsymbol{p}
Δp 进行拆分,可以得到与 (1) 等价的式子
Δ
p
i
=
−
s
∇
p
i
C
(
p
)
(2)
\Delta\boldsymbol{p}_i=-s\nabla_{\boldsymbol{p}_i}C(\boldsymbol{p}) \tag{2}
Δpi=−s∇piC(p)(2)
其中
s
=
C
(
p
)
∣
∇
p
C
(
p
)
∣
2
=
C
(
p
)
Σ
j
∣
∇
p
j
C
(
p
)
∣
2
(3)
s=\frac{C(\boldsymbol{p})}{|\nabla_{\boldsymbol{p}}C(\boldsymbol{p})|^2} = \frac{C(\boldsymbol{p})}{\Sigma_j{|\nabla_{\boldsymbol{p}_j}C(\boldsymbol{p})|^2}} \tag{3}
s=∣∇pC(p)∣2C(p)=Σj∣∇pjC(p)∣2C(p)(3)
这么做是为了处理节点质量不相等的情况,当质量不相等时,
p
i
\boldsymbol{p}_i
pi 的变化应当与质量成反比。因此考虑质量的倒数
ω
i
=
1
/
m
i
\omega_i=1/m_i
ωi=1/mi ,可得
Δ
p
i
=
−
s
n
ω
i
Σ
j
ω
j
∇
p
i
C
(
p
)
(4)
\Delta\boldsymbol{p}_i=-s\frac{n\omega_i}{\Sigma_j\omega_j}\nabla_{\boldsymbol{p}_i}C(\boldsymbol{p}) \tag{4}
Δpi=−sΣjωjnωi∇piC(p)(4)
对于不等式约束,我们只在
C
(
p
)
<
0
C(\boldsymbol{p})<0
C(p)<0 时进行迭代变换。
如果再考虑每个约束的刚度参数 k k k,假设需要 n s n_s ns 轮迭代(这里或许是:假设还需要 n s n_s ns 轮迭代?),我们将位置修正 Δ p \Delta\boldsymbol{p} Δp 乘上系数 1 − ( 1 − k ) 1 / n s 1-(1-k)^{1/n_s} 1−(1−k)1/ns ,存在线性误差 Δ p ( 1 − k ) \Delta\boldsymbol{p}(1-k) Δp(1−k) 。
7
这一步是用来更新发生了碰撞的节点的速度的。
如果一个节点发生了碰撞,假设碰撞位置的法向量为 n \boldsymbol{n} n ,那么该节点速度垂直于 n \boldsymbol{n} n 的分量将因为摩擦力而减少,并且由于反弹会产生与 n \boldsymbol{n} n 同方向的速度分量。