本文为课程CS417 Physics based animation的课程笔记。本课程从欧拉拉格朗日方程以及数值算法出发,介绍了如何模拟弹簧质点系统,有限元,刚体模拟以及流体模拟(流体还没做,摆烂)。对于课程内的公式,本文在力所能及的范围内适当补充了推导过程。

1.Euler-Lagrange

任何力学系统应满足的法则,给出广义坐标时能快速对系统受力分析。

两点之间直线最短:

对力学系统,定义广义坐标 ,广义速度 ,能量函数 ,动能函数 ,势能函数 ,有:

当物体运动的起始点和结束点 给定时,泛函 取得最小时系统稳定。当 稳定时,其偏导数趋近于0:

(1.3)便是Euler-Lagrange方程。对于任意力学系统,均需满足该方程。当 时,称 为系统广义力。

下面,我们在一维弹簧质点系统验证欧拉拉格朗日方程的正确性:

为位置, 为速度, 为质点动能, 为弹簧势能。

而(1.4) 为牛顿第二定律对应的胡克定律方程。

2.Numeric Method

一般来说动力学系统涉及2阶微分方程 打包为一个向量 我们可以将微分方程转为一阶的微分方程: ,对于该方程我们有几种数值解法

2.1 Forward Euler:

一阶泰勒展开:

2.2Runge-Kutta

时,退化为Forward Euler

时,退化为 Heun’s Method:

最为常用的Runge-Kutta方法为:

2.3 Backward Euler:

当函数 是线性变换时,我们可以利用这点推导出逆向Euler公式:

Backward Euler的优点之一是,它会让系统整体能量不断减少。

从Phase Space可以看到,每一步迭代 的坐标都落到轨迹圆的内部,一直迭代下去直到系统静止。这种现象让模拟器数值十分稳定(不会让物体速度越来越快),而且有助于模拟阻力。

2.4 Sycmplectic Euler:

结合了Forward Euler和Backward Euler的优点和缺点。它能保证系统整体的能量不会随着时间变化而太大的变化。

它的做法十分简单,用Forward Euler更新一步,再由Backward Euler更新一步。例如:在弹簧质点系统中,用Forward Euler更新速度,接着,用Backward Euler更新位置:

3.Mass-Spring System 3D

3.1 双质点单弹簧

假设第一个质点坐标维 第二个为,系统广义坐标为 ,弹簧劲度系数为,质点质量矩阵为 ,弹簧初始长度为则弹簧的潜在能为:

其中( 也成立,且不会对公式推导产生影响)

由广义力

在积分时使用backward euler算法:

由于 非线性函数,使用Linearly-Implicit euler 近似,将(3.4)代入(3.3)得:

求解线性系统(3.5)就可得到 ,公式去掉 项便退化为forward euler。

求解时涉及

关于 :

由于 对称,

有上述结论,我们建立了求解单弹簧质点系统的所有数学工具。

3.2 多质点系统

多质点系统可通过分解为多个双质点系统求解。

, ,若第个弹簧连接 两点,则,潜在能可以写为:

其中

对广义力公式也就有

通过些公式,公式(3.5) 可扩展到高维。在每次迭代中,找出所有弹簧单个计算 乘上矩阵 后将其叠加起来,带入(3.5)中求解线性系统,便可求解。计算 的代码为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
void assemble_forces(Eigen::VectorXd &f, Eigen::Ref<const Eigen::VectorXd> q, Eigen::Ref<const Eigen::VectorXd> qdot, 
Eigen::Ref<const Eigen::MatrixXd> V, Eigen::Ref<const Eigen::MatrixXi> E, Eigen::Ref<const Eigen::VectorXd> l0s,
double mass, double k)
{
//assemble force vector

f.resize(V.rows() * 3);
f.setZero();


//apply spring forces
for (uint32_t i = 0;i < E.rows();i++)
{
uint32_t v0 = E(i, 0), v1 = E(i, 1);
Eigen::Vector3d q0(q(v0 * 3), q(v0 * 3 + 1), q(v0 * 3 + 2));
Eigen::Vector3d q1(q(v1 * 3), q(v1 * 3 + 1), q(v1 * 3 + 2));


double l0 = l0s(i);
Eigen::Vector6d spring_force;
dV_spring_particle_particle_dq(spring_force, q0, q1, l0, k);
spring_force = -spring_force;

f(3 * v0) += spring_force(0);
f(3 * v0 + 1) += spring_force(1);
f(3 * v0 + 2) += spring_force(2);
f(3 * v1) += spring_force(3);
f(3 * v1 + 1) += spring_force(4);
f(3 * v1 + 2) += spring_force(5);
}
};

void assemble_stiffness(Eigen::SparseMatrixd &K, Eigen::Ref<const Eigen::VectorXd> q, Eigen::Ref<const Eigen::VectorXd> qdot,
Eigen::Ref<const Eigen::MatrixXd> V, Eigen::Ref<const Eigen::MatrixXi> E, Eigen::Ref<const Eigen::VectorXd> l0,
double k)
{
K.resize(V.rows() * 3, V.rows() * 3);
K.setZero();
triplets.clear();
accumlatedTriplets.clear();

{
//triplets.clear();
Timer timer("compute stiffness per-spring");
for (uint32_t i = 0; i < E.rows(); i++)
{
uint32_t v0 = E(i, 0), v1 = E(i, 1);
Eigen::Vector3d q0(q(v0 * 3), q(v0 * 3 + 1), q(v0 * 3 + 2));
Eigen::Vector3d q1(q(v1 * 3), q(v1 * 3 + 1), q(v1 * 3 + 2));
double l = l0(i);

Eigen::Matrix66d H;
d2V_spring_particle_particle_dq2(H, q0, q1, l, k);
H = -H;


SetK(v0, v1, triplets, H);
}
}

{
Timer timer("sort triplets");
std::sort(triplets.begin(), triplets.end(), [&](const Tri& lhs, const Tri& rhs)
{
if (lhs.row() < rhs.row())
{
return true;
}
else if (lhs.row() > rhs.row())
{
return false;
}
return lhs.col() < rhs.col();
}
);
//trick: insert a dummy triplet
triplets.push_back(Tri(V.rows() * 3 + 1, V.rows() * 3 + 1, 0));
}


{
Timer timer("accumulation");
uint32_t row = triplets[0].row(), col = triplets[0].col();
double val = triplets[0].value();
for (uint32_t i = 1; i < triplets.size(); i++)
{
Tri& tri = triplets[i];
if (tri.row() == row && tri.col() == col)
{
val += tri.value();
}
else
{
if (abs(val) > 1)
{
accumlatedTriplets.push_back(Tri(row, col, val));
}
row = tri.row(), col = tri.col(), val = tri.value();
}
}
K.setFromTriplets(accumlatedTriplets.begin(), accumlatedTriplets.end());
}

为了视觉效果,我们需要在一些质点添加定点约束 :

其中, 会从中挑选出非定点坐标,而 包含所有定点坐标,若点为顶点, ,否则 。在求 时需要用完整坐标 。当 更新速度时,需要 将其带入(3.5)并求解线性系统。

4. Finite Element Method

与弹簧质点系统不同,有限元假设一个物体由多个实心小四棱锥构成。每个四棱锥处处密度均匀且相等。

4.1 单个四棱锥

有限元中,系统的动能和势能主要四棱锥的运动和形变产生,这通过对四棱锥内每个质点的位置积分得到。四棱锥每个质点的位置通过其时间 和未形变时位置 的函数描述 为质点在 空间的坐标,而 为质点在 空间下的坐标。

对四棱锥的四个顶点 ,可以通过权重 ,描述其内部任意一点坐标

该方程在 空间中也成立。对于四棱锥内任意一点 ,加上归一化约束我们能通过求解一下方程得到

消去 得到:

对每个质点用 (4.2)解出 且给出时刻四顶点坐标后,可用(4.1)求出其在 时刻的位置

因此可以用任意时刻四个顶点位置表示系统广义坐标

对(4.1)两边使用向量化算子 ,有:

其中 ,克罗内克积

对每个四棱锥内质点,其动能有

其中 为密度,

对(4.4)积分有

其中积分区域 为reference 空间下的四棱锥, 有:

动能可以在预计算阶段通过逐个积分 并在模拟过程中带入(4.5)中求得。对单个积分有:

其中 积分区域

由(4.2)可得:

由行列式的几何意义, 行列式的值为3个列向量构成的平行六面体的体积,也就是3个列向量所构成4棱锥的体积的6倍。

其中 为四棱锥在reference空间下的体积而:

由于选取的任意性,积分值应关于索引对称,因此:

将其带入(4.6)便可求得矩阵

对于系统弹性势能由每个体积微元的形变定义

其中在deform 空间下

其中 表示一个体积微元的形变,而函数 描述了该形变下体微元的弹性势能。常用的Neohookean Strain Energy Density,课程中使用的形式为:

其中 C,D为材料参数。

,𝟙 由公式(4.2)和 的线性约束有:

将(4.9),(4.10)带入(4.1)得

其中有𝟙

两边同时取向量化算子 有:

其中 为四棱锥广义坐标,

而课程中给出的公式为:

这里的结论和课程中的不同,原因是这里采用以列为主展开而课程默认以行为主展开。两个结论均正确。潜在能公式可写为:

对整个reference空间积分,由于 无关,因此:

对于有多个有限元四棱锥组成的网格体,我们可以用类似弹簧质点系统的方式计算整个系统的动能和势能。

对于整个网格体,其广义坐标为所有顶点的位置 ,为了能单独研究每个四棱锥的能量,定义选择矩阵

其中 为四棱锥 四个顶点。而根据(4.5)和(4.12),系统的动能和势能公式能表示为

对其求导有:

5. Backward Implicit Euler Optimization

与弹簧质点类似,我们同样使用backward euler求解有限元:

这里 定义为:

将非线性方程求解问题转为优化问题,令 (5.1)可以写为:

其中由有:

则将(5.4)带入(5.3)并对等式左边积分有:

因此,问题转化为:

常见的优化算法见优化算法

对于牛顿法:

其中 当前正在搜索的 的值。

6. Cloth(FEM 2D)

和3维FEM类似,2D FEM可用三角形网格表示每个有限元

这里方程的维度高于未知数的维度,原因在于位置 不一定和 在一个平面上。因此,我们需要让 在平面 上的投影 满足:

关于如何求得投影值,我们可以将问题转为优化问题解决。令 ,则 在平面上对应点为 到平面的最近点,也就满足:

𝟙 ,

求导有:

为0时,即为所求:

可用 点在平面的投影坐标 和其到平面的距离 来表示其到有限元顶点的距离函数

其中 为平面法线。这种表示方式相比于单纯的三角面片可以表示布料的厚度。

对于系统动能我们有:

由于布料很薄。我们可以假定积分值在 上恒定,且布料处处厚度相等。令布料厚度为 :

其中积分域 为布料对应的三角形区域。类似(4.3)的推导过程,我们能得到

类似(4.6.1),(4.6.2)有:

TODO…

7. Rigidbody

可用和deformable的FEM类似方法分析刚体。

由于刚体内任意两点的相对距离不会随时间变化:

因此,我们可以用旋转矩阵 描述刚体的转动:

(7.1)对时间求导有:

其中 为该点线速度。将(-2.5)带入(7.2)可得:

令广义坐标 带入得:

利用公式(7.3)可计算刚体内任意一点在世界空间下的线速度 对整个刚体积分,有:

为了方便研究,取reference space下的坐标为 ,质心坐标 满足 ,则:

将(7.5)带入(7.4)得:

其中:

为刚体转动惯量

为刚体质量。带入(7.5)得:

由于刚体无形变,弹性势能为0。因此,系统潜在能

带入(1.3)得(这公式我不会推,直接抄结论):

其中 为系统外力, 为系统外力矩。

得到 的显式更新公式:

由于 有:

则:

9.Rigidbody Contact

对于单刚体系统,其速度更新公式为:

整理后有:

其中:

对于双刚体系统当两刚体相撞时有:

撞击点, 同理。

撞击处的应力 其中, 为力度大小, 为撞击点法线的反方向。

转到reference space,求解reference space下的力矩:

将该力矩转到world space有:

则对刚体A除了外力 和外力矩 ,应存在撞击处的力和力矩。将 带入(9.1)中有:

由(7.3)有:

的Jacobian 矩阵。对任意广义速度 有:

将(9.3)带入(9.2)中得到:

对于多刚体系统

每个刚体有多个接触点,(9.5)可以变形为:

其中A为研究的当前刚体, 为刚体 的第个接触点。刚体 在点i接触

(9.6)需满足约束:

则:

对单个接触点i将其与其它接触点分离有:

其中 为系统未约束速度,而 为系统约束产生的速度其中 为系统除接触点i外对刚体A产生的约束力

将公式(7.3)带入约束(9.7.2)得:

将公式(9.9)带入约束(9.10)得:

,$\gamma_i = (g_i^B)^T(\dot q_B^+\Delta t M_B^{-1} f_i^B) + (g_i^A)^T (\dot q_A^ + \Delta t M_A^{-1} f_i^A)$ 则约束(9.7)可变为:

解得:

对于整个系统,可利用Projected Gauss-Seidel法求解线性方程组(9.11.2):

  1. 对系统所有contact,初始化
  2. 进入循环,直到 满足 约束(9.11)
    1. 循环遍历每个contact点:
      1. 根据当前 计算,
      2. , 带入(9.12)中,计算

关于Gauss-Seidel详细说明可以看Appx 4

Appx 2. Time Derivative of Rotation Matrix

绕轴 旋转的质点 的线速度为:

定义向量 为质点的角速度 有:

写成矩阵相乘形式带入(-2.2)中有:

解该常微分方程,得:

( )

则当 极小时:

对任意时刻:

(-2.4)有:

Appx 4 Gauss-Seidel and Rigidbody Contact Solving

TODO: