顾大北 发布的文章

[scode type="blue"]如果行内公式没渲染请刷新页面[/scode]

理论力学 | 最小作用量原理

在理论力学中,欧拉-拉格朗日方程是求解系统运动方程的核心工具。它将经典力学问题转化为变分问题,通过最小作用量原理得到系统的运动规律。

拉格朗日函数

拉格朗日函数 $ L $ 是一个多元函数,只要给出广义坐标 $q$ 、广义速度 $\dot q$ 和时间 $ t $ 它可以表征任意一个力学系统。换句话说只要你告诉我系统的广义坐标、广义速度和时间,我就能用一个拉格朗日函数完全描述这个系统的动力学行为。拉格朗日函数可以理解为系统在每一时刻的“运动净收益”。

$$ L(q,\dot q,t)=T-V $$

其中

  • $ T $是动能
  • $ V $是势能

Q:为什么拉格朗日函数等于动能减去势能?

A:猜的!对没错就是猜的,最初它是一个经验性构造,这样定义后发现它是自洽的所有已知力学系统都符合。

作用量 (Action)

作用量 $ S $ 是一个标量泛函,它将一条轨迹 $q(t)$ (函数)映射到一个数值,即量化了运动轨迹。

$$ S = \int_{t1}^{t2}L(q,\dot q,t)dt $$

最小作用量原理

自然界中,一个物理系统在两个时刻之间的实际运动路径,是使作用量 $ S $ 取极值(通常是极小值)的那条路径即真实的运动轨迹使作用量达到极值。不太严谨的说法:一个物理系统的运动路径仅会采取“运动总收益“最小的情况。

欧拉-拉格朗日方程

根据最小作用量原理:一个物理系统在两个时刻之间的实际运动路径,是使作用量 $ S $ 取极值(通常是极小值)的那条路径。我们可以获得一种不同于牛顿力学的解决物理系统的方法,即通过求得作用量的极小值。而不是受力分析等传统力学方法。

求作用量的极值

由于作用量是一个泛函,所以求极值需要用到变分法。即我们需要对作用量求一阶变分。

变分法

类比于微分,如果一个函数 $f(x)$ 在 $x_0$ 处取得极值,则对于任意 $\delta x$有

$$ f(x_0+\delta x) -f(x_0)=0+o(\delta x) $$

换句话说,一阶微小变化不会改变函数值

同样的对于一个泛函对所有满足固定端点条件,假设泛函 $ S $ 在 $q(t)$ 处取得极值,我们引入一个微小的变化函数 $\delta {q(t)}$,将路径 $q(t)$变为 $q(t)+\delta q(t)$,如果此时作用量的值与原路径下的作用量的值相同(即对路径做微小扰动后的作用量进行一阶变分为零),那么 $q(t)$ 就是极值轨迹,也就是物理系统的真实运动轨迹。

所以我们对作用量引入一个微小扰动 $\delta q$

$$ S = \int_{t1}^{t2}L(q+\delta q,\dot q+\delta \dot q,t)dt $$

对其求一阶变分

令 $\delta q = \epsilon \eta $ 并且 $ \eta (t_1)=\eta (t_2)=0$ 则

$$ S = \int_{t1}^{t2}L(q+\epsilon \eta ,\dot q+\epsilon \dot \eta ,t)dt $$

一阶变分定义:泛函对扰动参数 $ \epsilon$ 的导数在 $ \epsilon =0$ 处的取值。

$$ \delta S = \left.\frac{d}{d\epsilon} S\right|_{\varepsilon=0} = \int_{t1}^{t2}\left.\frac{L(q+\epsilon \eta ,\dot q+\epsilon \dot \eta ,t)}{d\epsilon}\right|_{\varepsilon=0}dt= \int_{t1}^{t2}\left(\frac{\partial L}{\partial q}\eta+\frac{\partial L}{\partial\dot q}\dot\eta\right)dt $$

$$ \delta S = \int_{t1}^{t2}\left(\frac{\partial L}{\partial q}\eta\right) dt+\int_{t1}^{t2}\left (\frac{\partial L}{\partial \dot q}\dot \eta\right) dt $$

对第二项使用分部积分

$$ \delta S =\int_{t1}^{t2}\left(\frac{\partial L}{\partial q}\eta\right) dt+\left .\frac{\partial L}{\partial\dot q} \eta \right |_{t_1}^{t_2}-\int_{t1}^{t2}\frac{d}{dt}\left(\frac{\partial L}{\partial\dot q}\right)\eta dt $$

由于 $ \eta (t_1)=\eta (t_2)=0$ 所以第二项等于零

$$ \delta S =\int_{t1}^{t2}\left(\frac{\partial L}{\partial q}-\frac{d}{dt}\left (\frac{\partial L}{\partial\dot q}\right)\right)\eta dt $$

根据最小最用量原理 最用量的一阶变分为0,由于微小扰动不能为0值函数(不然就相当于没有扰动),所以必须是

$$ \frac{\partial L}{\partial q}-\frac{d}{dt}\left (\frac{\partial L}{\partial\dot q}\right)=0 $$

这就是欧拉-拉格朗日方程

对于 $s$ 个自由度的系统,在最小作用量原理中有 $s$ 个不同的函数 $ q_i(t)$ 所以我们会得到 $s$ 个欧拉-拉格朗日方程:

$$ \frac{\partial L}{\partial q_i}-\frac{d}{dt}\left (\frac{\partial L}{\partial\dot q_i}\right)=0\quad (i=1,2,\ldots,s) $$

[scode type="blue"]如果行内公式没渲染请刷新页面[/scode]

角度约束

在物理引擎中,我们经常需要控制两个物体之间的相对角度,比如机械臂的关节角度或者两个刚体之间的旋转约束。这种约束在现实中就类似用铰链连接两个物体,使它们在某个期望夹角下旋转。

通过之前《物理引擎|基于冲量的约束求解》这篇文章我们知道,实现约束的关键是写出约束方程,然后求出Jacobian矩阵,最后用公式求解冲量。下面我们将详细介绍角度约束的约束方程和Jacobian矩阵的推导。

角度约束的约束方程

假设存在两个物体:物体A物体B,我们希望它们的旋转轴之间保持一个期望夹角 $\theta_d$。

设物体A的参考方向为单位向量 $\mathbf{u}_A$,物体B的参考方向为单位向量 $\mathbf{u}_B$。我们希望这两个向量之间的夹角等于 $\theta_d$,约束方程可以写为:

三维情况下

$$ C = \mathbf{u}_A \cdot \mathbf{u}_B - \cos\theta_d = 0 $$

这里 $\mathbf{u}_A \cdot \mathbf{u}_B = \cos\theta$ 表示当前夹角,$C=0$ 表示夹角正好为期望值。

以下写法也正确,但通常不推荐

$$ C=arccos(\mathbf{u}_A\cdot\mathbf{u}_B)-\theta_d=0 $$

二维情况下

$$ C=\theta_A-\theta_B-\theta_d=0 $$

$\theta_A,\theta_B$ 分别表示物体的角度

Jacobian矩阵

三维推导

为了得到速度约束方程,我们对位置约束方程对时间求导:

$$ \dot C = \nabla C\cdot\dot{\mathbf{q}} = \begin{bmatrix}\frac{\partial C}{\partial \mathbf{u}_A}&\frac{\partial C}{\partial \mathbf{u}_B}\end{bmatrix}\begin{bmatrix}\dot{\mathbf{u}}_A\\\dot{\mathbf{u}}_B\end{bmatrix} $$

$$ \dot C = \begin{bmatrix}\mathbf{u}_B&{\mathbf{u}}_A\end{bmatrix}\begin{bmatrix}\dot{\mathbf{u}}_A\\\dot{\mathbf{u}}_B\end{bmatrix} $$

旋转向量的变化可以用角速度表示:

$$ \dot{\mathbf{u}}_A = \boldsymbol{\omega}_A \times \mathbf{u}_A, \quad \dot{\mathbf{u}}_B = \boldsymbol{\omega}_B \times \mathbf{u}_B $$

代入速度约束:

$$ \dot C = (\boldsymbol{\omega}_A \times \mathbf{u}_A) \cdot \mathbf{u}_B + \mathbf{u}_A \cdot (\boldsymbol{\omega}_B \times \mathbf{u}_B) $$

利用向量恒等式 $(\mathbf{a} \times \mathbf{b}) \cdot \mathbf{c} = \mathbf{a} \cdot (\mathbf{b} \times \mathbf{c})$,可以得到:

$$ \dot C = \boldsymbol{\omega}_A \cdot (\mathbf{u}_A \times \mathbf{u}_B) + \boldsymbol{\omega}_B \cdot (\mathbf{u}_B \times \mathbf{u}_A) $$

注意 $\mathbf{u}_B \times \mathbf{u}_A = - (\mathbf{u}_A \times \mathbf{u}_B)$,于是速度约束可以写成矩阵形式:

$$ \dot C = \begin{bmatrix} \mathbf{u}_A \times \mathbf{u}_B & -(\mathbf{u}_A \times \mathbf{u}_B) \end{bmatrix} \begin{bmatrix} \boldsymbol{\omega}_A \\ \boldsymbol{\omega}_B \end{bmatrix} = \mathbf{J} \cdot \boldsymbol{\Omega} $$

其中

$$ \mathbf{J} = \begin{bmatrix} \mathbf{u}_A \times \mathbf{u}_B & -(\mathbf{u}_A \times \mathbf{u}_B) \end{bmatrix}, \quad \boldsymbol{\Omega} = \begin{bmatrix} \boldsymbol{\omega}_A \\ \boldsymbol{\omega}_B \end{bmatrix} $$

为了保证程序通用性一般写作

$$ \mathbf{J} = \begin{bmatrix}0& \mathbf{u}_A \times \mathbf{u}_B & 0&-(\mathbf{u}_A \times \mathbf{u}_B) \end{bmatrix}, \quad \mathbf{V} = \begin{bmatrix}\mathbf{v}_A\\ \boldsymbol{\omega}_A \\\mathbf{v}_B\\ \boldsymbol{\omega}_B \end{bmatrix} $$

二维推导

为了得到速度约束方程,我们对位置约束方程对时间求导:

$$ \dot C =\nabla C\cdot\dot {\mathbf{q}}=\begin{bmatrix}\frac{\partial C}{\partial \theta_A}&\frac{\partial C}{\partial \theta_B}\end{bmatrix}\begin{bmatrix}\frac{d}{dt}\theta_A&\frac{d}{dt}\theta_B\end{bmatrix}^T=\begin{bmatrix}1&-1\end{bmatrix}\begin{bmatrix}\omega_A&\omega_B\end{bmatrix}^T $$

为了保证程序通用性一般写作

$$ \dot C =\begin{bmatrix}0&1&0&-1\end{bmatrix}\begin{bmatrix}\mathbf{v}_{A}\\ \omega_A \\ \mathbf{v}_{B} \\ \omega_B\end{bmatrix} $$

其中

$$ \mathbf{J} = \begin{bmatrix} 0 & 1 & 0 & -1 \end{bmatrix}, \quad \mathbf{V} = \begin{bmatrix}\mathbf{v}_{A}\\ \omega_A \\ \mathbf{v}_{B} \\ \omega_B\end{bmatrix} $$

[scode type="blue"]如果行内公式没渲染请刷新页面[/scode]

距离约束

在物理引擎中我们常常需要限制两个物体(或者点)之间的相对相对距离,这种效果在现实中就类似拿一根固定长度的刚体丝线连接两个物体上的某个点,这就不得不再次用到约束了。

通过之前《物理引擎中的约束》这篇文章我们了解到我们只需要写出我们需要的约束方程,然后找出Jacobian矩阵就可以通过之前那篇文章最后导出的公式求解冲量了。接下来我们将介绍解释碰撞约束的约束方程和推导Jacobian矩阵。

距离约束的约束方程

假设存在两个物体:物体A物体B。我们要将 物体A 上的点$\mathbf{P}_A$连接到 物体B 上的点$\mathbf{P}_B$上。

我们的目的是使得这两个点之间的距离(在欧几里得度量下)在某个期望值 $d$下保持不变。为了实现这一目标,那么我们的位置约束方程可以这么写:

$$ C=(\mathbf{P}_A-\mathbf{P}_B)\cdot(\mathbf{P}_A-\mathbf{P}_B) -d^2=0 $$

表示的是两个点之间的距离与期望距离相等

事实上还可以这么写

$$ C=\sqrt{(\mathbf{P}_A-\mathbf{P}_B)\cdot(\mathbf{P}_A-\mathbf{P}_B) -d^2}=0 $$

$$ C=\sqrt{(\mathbf{P}_A-\mathbf{P}_B)\cdot(\mathbf{P}_A-\mathbf{P}_B)}-d = 0 $$

$$ C=\left \| \mathbf{P}_A-\mathbf{P}_B \right \|-d=0 $$

上述表示方式都是正确的,选择哪一种取决于具体的应用场景和个人偏好。第一种方式避免了开方运算,在某些数值计算中更为高效;而下面三种种方式则更加直观,直接表达了距离的概念。接下来采用第一种进行推导。

Jacobian矩阵

还记得我们《物理引擎中的约束》中提到的我们找到位置约束方程后,我们还需要对时间求导,得到速度约束方程。

在这里由于我们只有一个系统所以我们的约束方程退化成了多元函数,Jacobian 矩阵退化成了梯度。

$$ \dot C =\frac{\partial C}{\partial t}=\nabla C\cdot \begin{bmatrix} \dot {\mathbf{P} }_A \\ \dot {\mathbf{P} }_B \end{bmatrix} $$

其中

$$ \nabla C = (\frac{\partial C}{\partial \mathbf{P}_A},\frac{\partial C}{\partial \mathbf{P}_B})=(2(\mathbf{P}_A-\mathbf{P}_B),-2(\mathbf{P}_A-\mathbf{P}_B)) $$

我们需要将其展开,我们知道

$$ \mathbf{P}_A = \mathbf{c}_A + \mathbf{r}_A, \quad \mathbf{P}_B = \mathbf{c}_B + \mathbf{r}_B $$

其中:

  • $\mathbf{c}_A, \mathbf{c}_B$ 是物体质心位置
  • $\mathbf{r}_A, \mathbf{r}_B$ 是点相对于质心的局部偏移

我们知道

$$ \dot{\mathbf{P}}_A = \mathbf{v}_A + \boldsymbol{\omega}_A \times \mathbf{r}_A, \quad \dot{\mathbf{P}}_B = \mathbf{v}_B + \boldsymbol{\omega}_B \times \mathbf{r}_B $$

带入速度约束

$$ \dot{C} = 2 (\mathbf{P}_A - \mathbf{P}_B) \cdot \dot{\mathbf{P}}_A - 2 (\mathbf{P}_A - \mathbf{P}_B) \cdot \dot{\mathbf{P}}_B $$

$$ \dot{C} = 2 (\mathbf{P}_A - \mathbf{P}_B) \cdot \Big( (\mathbf{v}_A + \boldsymbol{\omega}_A \times \mathbf{r}_A) - (\mathbf{v}_B + \boldsymbol{\omega}_B \times \mathbf{r}_B) \Big) $$

$$ \dot C =2 (\mathbf{P}_A - \mathbf{P}_B)\cdot\mathbf{v}_A+2 \boldsymbol{\omega}_A\cdot( \mathbf{r}_A\times (\mathbf{P}_A - \mathbf{P}_B))-2 (\mathbf{P}_A - \mathbf{P}_B)\cdot\mathbf{v}_B-2 \boldsymbol{\omega}_B\cdot( \mathbf{r}_B\times (\mathbf{P}_A - \mathbf{P}_B)) $$

我们将其写成矩阵的形式

$$ \dot{C} = \mathbf{J} \cdot \mathbf{V} = \begin{bmatrix} 2 (\mathbf{P}_A - \mathbf{P}_B) & 2 (\mathbf{r}_A \times(\mathbf{P}_A - \mathbf{P}_B)) & -2 (\mathbf{P}_A - \mathbf{P}_B) & -2 (\mathbf{r}_B \times(\mathbf{P}_A - \mathbf{P}_B)) \end{bmatrix}\begin{bmatrix} \mathbf{v}_A \\ \boldsymbol{\omega}_A \\ \mathbf{v}_B \\ \boldsymbol{\omega}_B \end{bmatrix} $$

在带入我们的《物理引擎中的约束》最后的得到的方程我们可以解出冲量

[scode type="blue"]如果行内公式没渲染请刷新页面[/scode]
贝塞尔曲线

贝塞尔曲线(Bézier Curve)是一种在计算机图形学,图形设计软件(如 Adobe Illustrator)、动画制作、字体设计等领域中广泛使用的参数曲线,常用于设计平滑的曲线路径。

本文将介绍贝塞尔曲线的基本原理,公式推导。

贝塞尔曲线的基本原理

贝塞尔曲线由一组控制点定义。最常见的是一次、二次和三次贝塞尔曲线。

所以我们只介绍一次、二次和三次贝塞尔曲线。

贝塞尔曲线是通过一组有序控制点定义的参数函数,该函数将参数 t 映射到曲线上的空间点。

一次贝塞尔曲线

在平面中两个点可以确定一条线段,那么我们怎么表示线段上的任意一个点呢?

假设平面中存在任意两个点$\mathbf{P_1},\mathbf{P_2}$(注:这里的点指的是向量)

那么线段$\mathbf{P_2-P_1}$上的任意一个点$\mathbf{P}$的模长应该满足

$$ ||\mathbf{P_1}||\le||\mathbf{P}||\le||\mathbf{P_2}|| $$

且方向与$\mathbf{P_1}$的方向一致

那么$\mathbf{P}$可以由$\mathbf{P_1}$,$\mathbf{P_2-P_1}$和一个参数 $t$ 来决定

$$ \mathbf{P}=\mathbf{P_1}+t(\mathbf{P_2-P_1}),0\le t\le 1 $$

$\mathbf{P_1}$确定初始位置,$t(\mathbf{P_2-P_1})$控制范围

变换一下有

$$ \mathbf{P}=(1-t)\mathbf{P_1}+t\mathbf{P_2},0\le t\le 1 $$

对图形学熟悉的一眼能看出来这个公式是一个线性插值公式,也就是 lerp 表达式

标准lerp表达式为

$$ lerp(a,b,t) = (1-t)a+tb $$

二次贝塞尔曲线

两个点可以确定一个线段,那么三个点就可以确定两个线段,这样我们可以得到两个一次贝塞尔曲线。

假设平面上有三个点$\mathbf{P_1},\mathbf{P_2},\mathbf{P_3}$

那么我们可以的到两个lerp表达式

$$ lerp(\mathbf{P_1},\mathbf{P_2},t),\quad0\le t\le 1 $$

$$ lerp(\mathbf{P_2},\mathbf{P_3},t),\quad0\le t\le 1 $$

给定任意 $t$ 我们可以确定两个点$\mathbf{P_4},\mathbf{P_5}$

当我们 $t$ 取不同的值时$\mathbf{P_4},\mathbf{P_5}$的位置也会随之变化,如果我们将$\mathbf{P_4},\mathbf{P_5}$连接起来我们又会的到一条线段记作 $l$ ,很显然我们又得到一条lerp表达式

$$ lerp(\mathbf{P_4},\mathbf{P_5},t),\quad0\le t\le 1 $$

当我们的 $t$ 从0 到 1 取遍每一个数,那么线段 $l$ 也会随之动起来,当你把所有取不同数得到的 $l$ 画出来你会发现某个点的轨迹是一条曲线。

这个点的轨迹正是(注:$\mathbf{P_4},\mathbf{P_5}$是动点)

$$ lerp(\mathbf{P_4},\mathbf{P_5},t),\quad0\le t\le 1 $$

$$ lerp(lerp(\mathbf{P_1},\mathbf{P_2},t),lerp(\mathbf{P_2},\mathbf{P_3},t),t),\quad0\le t\le 1 $$

我们将表达式写出来

$$ \mathbf{P} = (1-t)[(1-t)\mathbf{P_1}+t\mathbf{P_2}]+t[(1-t)\mathbf{P_2}+t\mathbf{P_3}],\quad0\le t\le 1 $$

化简

$$ \mathbf{P} = (1-t)^2\mathbf{P_1}+2t(1-t)\mathbf{P_2}+t^2\mathbf{P_3},\quad0\le t\le 1 $$

这就是二次贝塞尔曲线

三次贝塞尔曲线

与二次贝塞尔曲线同理,4个点可以确定三条选段,得到三个lerp表达式

,这三个表达式可以确定3个点,这就退化到二次贝塞尔曲线了。

所以通过相同方式的推导我们可以的到三次贝塞尔曲线公式

$$ B(t) = (1-t)^3 P_0 + 3(1-t)^2 t P_1 + 3(1-t) t^2 P_2 + t^3 P_3, \quad t \in [0,1] $$

碰撞约束

在碰撞分离阶段除了使用碰撞法线直接分离物体之外,还有一种广泛用于各大刚体模拟引擎中的方法——碰撞约束。使用碰撞约束可以更加优雅的分离物体。

通过之前《物理引擎中的约束》这篇文章我们了解到我们只需要写出我们需要的约束方程,然后找出Jacobian矩阵就可以通过之前那篇文章最后导出的公式求解冲量了。接下来我们将介绍解释碰撞约束的约束方程和推导Jacobian矩阵。

碰撞约束的约束方程

假设物体A与物体B发生碰撞

想一想我们需要什么效果,我们希望物体A与物体B满足分离状态,我们需要使用符号表示出这个状态。这里再次给出约束方程的含义:约束方程是用于描述一个系统中各部件之间相对运动限制的数学表达式。通过这些方程,我们可以定义哪些运动是允许的,哪些是被禁止的。即当方程中的参数满足约束方程我们就不必调整物体状态,否则我们需要调整物体状态。

所以我们可以这么定义碰撞约束的位置约束方程

$$ C = (\mathbf{P}_a-\mathbf{P}_b)\cdot\mathbf{n}\ge0 $$

这个方程表示两点在法向方向上不能穿透(距离 ≥ 0),但可以分离(距离 > 0)或者接触(距离=0)。还可以这么理解当两个物体穿透的时候$$\mathbf{P}_a-\mathbf{P}_b$$与法线的点积小于0(不满足约束方程)剩下的情况( ≥ 0)就是满足约束的情况了。

找到约束方程后我们还需要找到Jacobian矩阵

Jacobian矩阵

还记得我们《物理引擎中的约束》中提到的我们找到位置约束方程后,我们还需要对时间求导,得到速度约束方程。

对上面得到的碰撞约束方程对时间求一阶导数得到速度约束方程

$$ \dot{C} = \mathbf{n}\cdot\frac{d}{dt}\mathbf{P}_a- \mathbf{n}\cdot\frac{d}{dt}\mathbf{P}_b $$

  • $ \frac{d}{dt}\mathbf{P}_a$就是碰撞点a的速度
  • $ \frac{d}{dt}\mathbf{P}_b$就是碰撞点b的速度

容易得到点a的速度为

$$ \frac{d}{dt}\mathbf{P}_a=\mathbf{v}_a+(\omega_a\times\mathbf{r}_a) $$

容易得到点b的速度为

$$ \frac{d}{dt}\mathbf{P}_b=\mathbf{v}_b+(\omega_b\times\mathbf{r}_b) $$

带入速度约束方程化简有

$$ \dot{C} = \mathbf{n}\cdot\mathbf{v}_a+\omega_a\cdot(\mathbf{r}_a\times\mathbf{n})-\mathbf{n}\cdot\mathbf{v}_b-\omega_b\cdot(\mathbf{r}_b\times\mathbf{n}) $$

我们将其写成矩阵的形式

$$ \dot{C} = \begin{bmatrix} \mathbf{n} & \mathbf{r}_a\times\mathbf{n} & -n &-\mathbf{r}_b\times\mathbf{n} \end{bmatrix}\begin{bmatrix} \mathbf{v}_a \\ \omega_a\\ \mathbf{v}_b \\ \omega_b \end{bmatrix} $$

其中Jacobian就是

$$ J=\begin{bmatrix} \mathbf{n} & \mathbf{r}_a\times\mathbf{n} & -n &-\mathbf{r}_b\times\mathbf{n} \end{bmatrix} $$

注:Jacobian是我们最后应用不同物体冲量的方向例如

a->ApplyImpulseLinear(Vec2(Jacobian[0]*lambda, Jacobian[1]*lambda)); 
a->ApplyImpulseAngular(Jacobian[2]*lambda);                   
b->ApplyImpulseLinear(Vec2(Jacobian[3]*lambda, Jacobian[4]*lambda)); b->ApplyImpulseAngular(Jacobian[5]*lambda);           

求解冲量大小

根据我们的《物理引擎中的约束》最后的得到的方程我们可以解出冲量

$$ \lambda = -(JM^{-1}J^{T})^{-1}(JV) $$

带bias系数的形式

$$ \lambda = -(JM^{-1}J^{T})^{-1}(JV+b) $$

其中V为

$$ V=\begin{bmatrix} v_{ax} \\ v_{ay}\\ \omega_a\\ v_{bx} \\ v_{by} \\ \omega_b \end{bmatrix} $$

M的逆为

$$ M^{-1} = \begin{bmatrix} \frac{1}{m_a} & & & & & \\ & \frac{1}{m_a} & & & & \\ & & \frac{1}{I_a} & & & \\ & & & \frac{1}{m_b} & & \\ & & & & \frac{1}{m_b} & \\ & & & & &\frac{1}{I_b} \end{bmatrix} $$

JV是一个数,$JM^{-1}J^{T}$也是一个数,所以求出来$\lambda$也是一个数我们的公式是正确的

注:这里可以使用高斯-赛德尔(Gauss-Seidel)方法求解线性方程组

Baumgarte 稳定性方法(Baumgarte Stabilization Method) 是一种常用于刚体动力学中处理约束系统稳定性的数值方法,特别是在使用Lagrange 乘子法求解带有约束的系统时。它的主要目标是防止数值积分过程中因误差累积导致的约束漂移(constraint drift)问题。

如果不使用 Baumgarte 稳定性方法 直接应用碰撞约束求解出来的冲量可能会导致在数值积分中由于舍入误差或逼近误差,约束无法完全维持。最后导致系统状态随时间逐渐“漂移”出满足约束的区域。

Baumgarte 稳定性方法核心思想

Baumgarte 方法通过引入一个微分项来“惩罚”偏离约束的行为,从而稳定系统。

在碰撞约束中我们一般使用

$$ \dot C = JV = 0 $$

表示一个速度约束方程

利用 Baumgarte 修正,构造一个新的方程:

$$ \dot C = JV + b = 0 $$

其中

$$ b = \frac{\beta}{\Delta t}C $$

  • $\Delta t为时间步长$
  • $\beta$是一个系数,可以跟据你需要的修正速度调整,大小在0到1之间,越大修正的越快

这一项我们通常叫做bias系数

引入bias系数后我们约束求解的冲量结果为

$$ \vec {\lambda}_{imp} = (JM^{-1}J^T)^{-1}-(JV_0+b) $$

优点

  • 简单易实现,只需对加速度约束项做改写。
  • 适用于显式或半隐式积分方法。
  • 可用于任意类型的约束系统(位置、速度等)。

缺点

  • 参数选择敏感。若 $\beta$ 选得不合适,可能导致系统刚性太强或振荡。
  • 并不是完全消除漂移,而是“压制”它;如果时间步过大仍可能不稳定。

碰撞响应

​ 在游戏物理引擎中两个物体在碰撞检测阶段结束后一般下一阶段就是要处理碰撞了,也就是碰撞处理阶段,碰撞处理阶段可以细分成两个阶段,先是经过分离阶段,再就是碰撞响应了。

1

​ 碰撞响应要处理的就是如何实现类似现实世界中的弹性碰撞效果,对此我们需要更新物体碰撞后的运动状态,使得物体的运动状态与现实世界的相似。

传统冲量法

​ 还记得我们高中学习物理是怎么解决这类碰撞问题的吗?我们通常使用动量定理那一套方法,所以在这里我们还是使用这套方法来处理。

通过碰撞检测,我们可以得到以下信息:

  • 碰撞点位置:$\mathbf{p}_a,\mathbf{p}_b$
  • 碰撞法线:$\mathbf{n}$
  • 穿透深度:$depth$

并且我们还具有物体此时物体的动力学状态。

  • 质心位置:$\mathbf{p}_A,\mathbf{p}_B$
  • 线速度:$\mathbf{v}_{Alinear},\mathbf{v}_{Blinear}$
  • 角速度:$\mathbf{\omega}_A,\mathbf{\omega}_B$
  • 质量:$\mathbf{m}_A,\mathbf{m}_B$
  • 转动惯量:$\mathbf{I}_A,\mathbf{I}_B$
  • 恢复系数:$e$

如果我们能计算出碰撞时的冲量,那么我们就可以通过冲量来改变速度而达成目的。

令A,B的广义速度为

$$ \mathbf v_A =\mathbf{v}_{Alinear}+\mathbf{\omega}_A\times{\mathbf{r_a}}\\ \mathbf v_B =\mathbf{v}_{Blinear}+\mathbf{\omega}_B\times{\mathbf{r_b}} $$

其中

$$ \mathbf{r_a}=\mathbf{p_a}-\mathbf{p_A}\\ \mathbf{r_b}=\mathbf{p_b}-\mathbf{p_B} $$

根据动量定理以及角动量定理有

$$ J_A \mathbf n= m_A\mathbf{v}^{'}_{Alinear}-m_A\mathbf{v}_{Alinear} \\ $$

$$ J_B\mathbf n = m_B\mathbf{v}^{'}_{Blinear}-m_B\mathbf{v}_{Blinear} \\ $$

$$ J_A\mathbf n\times{\mathbf r_a} = I_A\mathbf{\omega}^{'}_{A}-I_A\mathbf{\omega}_{A} \\ $$

$$ J_B\mathbf n\times{\mathbf r_b} = I_B\mathbf{\omega}^{'}_{B}-I_B\mathbf{\omega}_{B} $$

由牛顿第三定律可知

$$ J_A = - J_B $$

$$ J = J_A = -J_B $$

则碰撞后的广义速度为

$$ \mathbf v_A^{'} =\mathbf{v}^{'}_{Alinear}+\mathbf{\omega}_A^{'}\times{\mathbf{r_a}} \\ $$

$$ \mathbf v_B ^{'}=\mathbf{v}^{'}_{Blinear}+\mathbf{\omega}_B^{'}\times{\mathbf{r_b}} $$

联立动量定理、牛顿第三定律、碰撞后的广义速度可得

$$ \mathbf v_A^{'} =\mathbf{v}_{A}+\frac{J\mathbf{n}}{m_A}+(\mathbf I_A^{-1}(J\mathbf{n\times{r_a}}))\times \mathbf{r_a} \\ $$

$$ \mathbf v_B^{'} =\mathbf{v}_{B}-\frac{J\mathbf{n}}{m_B}-(\mathbf I_B^{-1}(J\mathbf{n\times{r_b}}))\times \mathbf{r_b} $$

由于

$$ e = \frac{(\mathbf v_A^{'} -\mathbf v_B^{'})\cdot\mathbf n}{(\mathbf v_B -\mathbf v_A)\cdot\mathbf n} \\ $$

$$ (\mathbf v_A^{'} -\mathbf v_B^{'})\cdot\mathbf n = -e({\mathbf v_A -\mathbf v_B})\cdot\mathbf n $$

将碰撞后的广义速度两式子相减带入恢复系数的式子中有

$$ (\frac{J\mathbf n}{m_A}+\frac{J\mathbf n}{m_B}+(\mathbf{I}_A^{-1}(J\mathbf n\times{\mathbf r_a}))\times{\mathbf r_a}+(\mathbf{I}_B^{-1}(J\mathbf n\times{\mathbf r_b}))\times{ \mathbf r_b})\cdot\mathbf n=-(1+e)(\mathbf{v}_a-\mathbf{v}_b)\cdot\mathbf n $$

由于我们只改变碰撞法线方向上的冲量所以我们在两边点乘上碰撞法线即可,然后化简得到

$$ J = \frac{-(1+e)(\mathbf{v}_A-\mathbf v_B)\cdot{\mathbf{n}}}{\frac{1}{m_A}+\frac{1}{m_B}+\mathbf{n}\cdot((\mathbf{I}_A^{-1}(\mathbf {r_a}\times {\mathbf{n}}))\times{\mathbf r_a}+(\mathbf{I}_B^{-1}(\mathbf {\mathbf r_b}\times {\mathbf{n}}))\times{\mathbf r_b})} $$

注:需要用到混合积(标量三重积)

特别的,在二维情况下

$$ J = \frac{-(1+e)(\mathbf{v}_A-\mathbf v_B)\cdot{\mathbf{n}}}{\frac{1}{m_A}+\frac{1}{m_B}+\mathbf{n}\cdot(\frac{(\mathbf {r_a}\times {\mathbf{n}})^2}{I_A}+\frac{(\mathbf {r_b}\times {\mathbf{n}})^2}{I_B})} $$

摩擦力效果

​ 要实现摩擦力的效果需要将上公式中的 $\mathbf n$ 全部替换为$\mathbf n$的正交单位向量即可

碰撞约束的响应(弹性碰撞)

​ 在碰撞约束完成后物体不会穿透其他物体,但是也不会发生弹性碰撞,所以我们在碰撞约束中的Baumgarte稳定性方法系数bias上加上 $e(\mathbf v_A-\mathbf v_B)\cdot \mathbf n$ 即可

即bias为

$$ b = \frac{\beta}{\Delta t}C+e(\mathbf v_A-\mathbf v_B)\cdot \mathbf n $$

摩擦力效果

​ 要实现摩擦力的效果需要将上约束方程中的 $\mathbf n$ 全部替换为$\mathbf n$的正交单位向量即可

引用

https://math.stackexchange.com/questions/4501575/full-derivation-of-impulse-formula-for-collision-response

[scode type="blue"]如果行内公式没渲染请刷新页面[/scode]

广义坐标

在介绍约束之前先介绍一下分析力学的一个概念:广义坐标

广义坐标是一组用来描述一个物理系统位形(即系统中所有质点的位置状态)的独立变量。它们不必是传统笛卡尔坐标 $(x, y, z)$,而是可以根据系统的约束和对称性灵活选择,只要能够唯一确定系统状态即可。

广义坐标通常用 $q_1,q_2,...q_n$ 来表示

例如:

双摆:广义坐标:$(\theta_1, \theta_2)$。总共有两个自由度,即两个角度,系统的所有质点位置都能通过这两个角度确定。

对广义坐标对时间求导得到的就是广义速度

位置约束方程

位置约束方程是指对系统中物体或点的空间位置施加的限制条件的方程。它定义了系统在构型空间中必须满足的几何关系。

在数学上,一个位置约束是一个标量函数$C(\mathbf{q})$,它依赖于系统的广义坐标 $\mathbf{q}$,并要求方程满足:

$$ C(\mathbf{q}) = 0 $$

如果约束函数包含了多个约束方程,那么此时约束函数是一个向量值函数

这意味着系统必须处于某个特定的几何或物理配置中

例如:

  • 如果 $q$ 表示二维平面上的一个点$(x,y)$,且这个点满足$C(\mathbf{q})=C(x,y)=x^2+y^2-r^2=0$,则这个位置约束方程表示的就是将这个点约束在以$(x,y)$为圆心 $r$ 为半径的圆上。
  • 如果 $q$ 表示三维空间中的两个点$\mathbf{P}_A$和$\mathbf{P}_B$,且满足$C(\mathbf{q})=C(\mathbf{P}_A,\mathbf{P}_B)=(\mathbf{P}_A-\mathbf{P}_B)^2-d^2=0$,则这个位置约束方程描述了两点间距离固定为 $d$ 的刚性杆。

虽然位置约束定义了我们想要的状态,但在模拟中,我们通常不直接求解位置约束方程,有以下原因

  1. 位置约束方程通常不是线性函数求解起来非常困难,我们需要一种能够解决所有问题的方法,
  2. 数值积分后通常不满足位置约束方程,在约束求解后直接改变位置会导致严重的抖动问题
  3. 不适配我们的冲量方法

所以为了解决这些麻烦,我们通常使用速度约束方程。

速度约束方程

速度约束方程是通过对位置约束对时间求导得到的,它描述了系统在满足位置约束的前提下,其广义速度 $\dot {\mathbf{q}}$ 必须满足的条件。

$$ \dot C = \frac{d}{d t}C(\mathbf{q}) $$

因为广义坐标是关于时间 $t$ 的向量值函数即

$$ \mathbf{q}(t) =(q_1(t),q_2(t),...,q_n(t)) $$

很显然位置约束方程是个复合函数我们用链式法则展开

$$ \dot C = \nabla C \cdot \dot{ \mathbf{q}} $$

当约束函数是向量值函数时(即包含了多个约束时)

$$ \dot C = \mathbf{J} C \cdot \dot{ \mathbf{q}} $$

此时我们就得到了速度约束方程

$$ \dot C = \nabla C \cdot \dot{ \mathbf{q}}=0 $$

或者

$$ \dot C = \mathbf{J} C \cdot \dot{ \mathbf{q}}=0 $$

接下来将会以二维视角讲解如何进行冲量求解

质量矩阵

在刚体动力学中,当研究某个点的动力学行为(如外力作用点)时,施加一个力不仅会引起平动加速度,还可能引起角加速度(特别是当力的作用点偏离质心时)。为了统一描述这种平动与转动的耦合响应,我们需要一个能够综合反映物体惯性特性的数学结构。

因此,我们引入广义质量矩阵(Generalized Mass Matrix),它将质量 $m$ 和转动惯量 $I$ 统一组织在一个矩阵中,用于连接广义力与广义加速度。

$$ M=\begin{bmatrix} m & & & \\ & m & & \\ & & I & \\ \end{bmatrix} $$

我们可以写成分块矩阵的形式

$$ M=\begin{bmatrix} M_a & \\ & I_a \end{bmatrix} $$

动量矩阵

动量矩阵就可以表示为

$$ P=MV=\begin{bmatrix} m & & & \\ & m & & \\ & & & I\end{bmatrix} \begin{bmatrix} v_x \\ v_y \\ \omega \\ \end{bmatrix} $$

或者

$$ P_a=MV=\begin{bmatrix} M_a & & \\ & &I_a \end{bmatrix} \begin{bmatrix} \vec{v_a}\\ \omega_a \\ \end{bmatrix} $$

牛顿第二定律

牛顿第二定理就可以写作

$$ F=MA=\begin{bmatrix} m & & \\ & m & \\ & & I \end{bmatrix}\begin{bmatrix} a_x \\ a_y \\ \alpha \end{bmatrix}=\begin{bmatrix} M_a & \\ & I\end{bmatrix}\begin{bmatrix} \vec{a} \\ \alpha \end{bmatrix} $$

冲量求解

下面我们从数值积分和动力学原理出发,推导出用于求解约束冲量的线性方程组。

我们的目的是更新速度使得满足速度约束方程

使用常微分方程数值方法中学到的前向欧拉法一阶数值积分(其实就是我们高中学的速度-加速度关系的离散形式

$$ V_t= V_0+A \Delta t $$

其中

  • $\Delta t$是物理帧
  • $V_t$是更新后的速度
  • $V_0$是更新前的速度

我们要得到更新后的速度,我们只需求得A就可以了!

使用牛顿第二定律

$$ A=M^{-1}F $$

代入速度更新公式

$$ V_t=V_0+M^{-1}F\Delta t $$

在分析力学中,约束力可表示为:

$$ F=\mathbf{J}^T\mathbf{\lambda}_f =\mathbf{J}^T\begin{bmatrix} \lambda_x \\ \lambda_y \\ \lambda\omega \end{bmatrix} $$

其中

  • $\mathbf{J}$是速度约束的雅可比矩阵
  • $\mathbf{\lambda}_f$是拉格朗日乘子向量,表示约束力的大小(在一个约束的情况下是一个标量)

代入有速度更新公式中

$$ V_t=V_0+M^{-1}\mathbf{J}^T\lambda_f\Delta t=V_0+M^{-1}\mathbf{J}^T\lambda_{imp} $$

我们将速度约束方程写成如下形式

$$ \dot C = \mathbf{J}V_t=0 $$

将速度更新方程代入

$$ \mathbf{J}(V_0+M^{-1}\mathbf{J}^T\lambda _{imp})=0 \\ $$

$$ \mathbf{J}M^{-1}\mathbf{J}^T\lambda _{imp}=-\mathbf{J}V_0 $$

这正是我们需要的一次线性方程方程组,满足

$$ A\vec x=\vec b $$

我们只需要求解该其次方程组的解即可得到更新所需的冲量