物理引擎学习06-碰撞反馈

原本计划06章是一个碰撞检测的小demo,上手之后才发现,碰撞反馈也是一个非常复杂的话题,所以就单拎出来一章,详细说明。碰撞反馈是基于碰撞检测的结果,将发生接触的物体分离开,同时应用上物理效果,使碰撞表现的接近于自然情况。

本文作者游蓝海。原创不易,未经许可,禁止任何形式的转载。

碰撞反馈

1. 刚体运动

1.1 刚体的概念

刚体,英文称作Rigidbody,是物理世界中的一个基本运动单元,包含了位移、旋转、质量、速度、力等物理属性。在物理引擎中,通常会把刚体看做是一个不可见的点,通过附加上不同的形状,才能与其他物体发生交互。从代码设计的角度而言,就是一种组合模式:一个刚体,包含了若干个形状。

public class Rigidbody
{
	/// 位移
    public Vector2 position;
    /// 移动速度
    public Vector2 velocity;
    /// 质量
    public float mass;
    /// 质量的倒数。方便除法计算,对于静态物体,可以认为质量无限大,invMass=0,所有作用力相乘结果都是0。
    public float invMass;
    /// 持续作用力。持续影响移动速度
    public Vector2 force;
    /// 脉冲力。仅影响移动速度一次
    public Vector2 forceImpulse;

    /// 旋转角度
    public float rotation;
    /// 角速度。单位是度,计算作用力的时候,切记要转换成弧度。
    public float angleVelocity;
    /// 角动量。相当于旋转质量
    public float inertial;
    /// 角动量倒数。方便除法计算
    public float invInertial;
    /// 扭矩力。持续的影响旋转速度
    public float torque;
    /// 扭矩脉冲力。仅影响旋转速度一次
    public float torqueImpulse;

    /// 摩擦力
    public float fraction = 0.3f;
    /// 形状集合
    public List<Shape> shapes;
}

1.2 形状的概念

形状(Shape),在一些物理引擎里也叫碰撞体(Collider),描述了物体的轮廓信息。常用的2D形状有圆形、线段、多边形,三角形和矩形也可以归为多边形。

1.3 牛顿三大定律

详细内容可以参考百科-牛顿运动定律,这里仅简单回顾一下。

  1. 匀速运动。S = S0 + v * t
  2. 加速度。a = F / m; v = v0 + a * t
  3. 力是相互的。F' = -F

1.3 线性运动

主要影响物体的位移和位移的速度。

velocity += force * invMass * dt;
position += velocity * dt;

1.4 旋转运动

主要影响物体的角度和旋转速度。

angleVelocity += torque * invInertial * dt;
rotation += angleVelocity * dt;

2. 碰撞反馈

2.1 碰撞接触信息

两个形状是否发生碰撞,可以使用GJK算法进行检测。当检测到碰撞后,再使用EPA算法,计算穿透方向和穿透深度。至于碰撞接触点,简单起见,可以使用求最近距离的方式,从闵可夫斯基差集多边形的边上计算得到最近点。

if (!isCollision)
{
    computeClosetPoint(simplex.getSupport(0), simplex.getSupport(1));
}
else
{
    queryEPA();
    computeClosetPoint(currentEpaEdge.a, currentEpaEdge.b);
}
...
void getContacts(List<ContactInfo> list)
{
     Edge e = gjk.currentEpaEdge;
     ContactInfo a = new ContactInfo
     {
         point = gjk.closestOnA, // 接触点
         normal = e.normal, // 穿透方向
         penetration = e.distance, // 穿透深度
     };

     list.Clear();
     list.Add(a);
 }

2.2 反作用力

两个物体发生碰撞后,会产生反作用力将两个物体分离开。作用力和反作用力是相互的,两者大小相等,方向相反。碰撞分离的关键点,就是如何求得作用力,然后改变物体的运动速度和旋转角度。

已知两个物体碰撞后在某时刻的速度和质量,可以根据动量定律,求得该时刻的动量dF。以下公式是box2d中采用的动量计算公式:

Δ F ⃗ = m Δ v ⃗ Δ v ⃗ = v ⃗ 1 − v ⃗ 2 + u ⃗ 1 − u ⃗ 2 u ⃗ = r ⃗ t   w m = 1 k k = 1 m 1 + 1 m 2 + 1 I 1 q 1 + 1 I 2 q 2 q n = r 2 − ∣ r ⃗ ⋅ n ⃗ ∣ 2 q t = r 2 − ∣ r ⃗ ⋅ t ⃗ ∣ 2 \begin{aligned} & \Delta \vec F = m \Delta \vec v \\ & \Delta \vec v = \vec v_1 - \vec v_2 + \vec u_1 - \vec u_2 \\ & \vec u = \vec r_t \, w \\ & m = \frac{1}{k} \\ & k = \frac{1}{m_1} + \frac{1}{m_2} + \frac{1}{I_1} q_1 + \frac{1}{I_2} q_2 \\ & q_n = r^2 - |\vec r \cdot \vec n|^2 \\ & q_t = r^2 - |\vec r \cdot \vec t|^2 \end{aligned} ΔF =mΔv Δv =v 1v 2+u 1u 2u =r twm=k1k=m11+m21+I11q1+I21q2qn=r2r n 2qt=r2r t 2

  • dv是该时刻两个物体上的碰撞点的相对速度,包含线性移动速度v和旋转速度u之和。
  • u是碰撞点的旋转速度,而不是刚体的旋转速度。点的旋转速度为点在单位时间沿着圆周转过的弧长,设点到中心的半径为r,刚体转速为w弧度/秒,则单位时间转过的弧长为: u = r * w。而旋转方向,就是点到中心向量的切线方向;
  • m是该时刻的有效质量。这个我还没有弄清楚,如果有懂的朋友,不妨留言说明一下;
  • q是点的旋转惯性。也是没有理解。注意,旋转惯性在法线方向和切线方向不一样,需要分开计算。

为了方便计算,沿着穿透向量方向,可以将作用力分解为平行和垂直的两个分量,分别记作法线方向n和切线方向t。法线方向主要体现为反弹力,切线方向主要体现为摩擦力。

Δ F n = max ⁡ ( m n Δ v n , 0 ) Δ v n = Δ v ⃗ ⋅ n ⃗ Δ F t = c l a m p ( m t Δ v t , − μ Δ F n , μ Δ F n ) Δ v t = Δ v ⃗ ⋅ t ⃗ \begin{aligned} & \Delta F_n = \max(m_n \Delta v_n, 0) \\ & \Delta v_n = \Delta \vec v \cdot \vec n \\ & \Delta F_t = clamp(m_t \Delta v_t, -\mu \Delta F_n, \mu \Delta F_n) \\ & \Delta v_t = \Delta \vec v \cdot \vec t \\ \end{aligned} \\ ΔFn=max(mnΔvn,0)Δvn=Δv n ΔFt=clamp(mtΔvt,μΔFn,μΔFn)Δvt=Δv t

作用力Fn初始的时候是指向穿透方向的,也就是正数,反作用力是-Fn。碰撞的过程中受力是连续发生变化的,作用力会由大变小,直到变为0。发起碰撞的刚体,其速度会逐步减小至0,然后开始反向运动,当反作用力为0的时候,其速度也就达到了最大值。整个碰撞过程中,Fn的值是需要限定到0以上的,否则又会把物体拉回来。

作用力Ft会让物体侧滑,而滑动就要受到摩擦力的影响。u = 1 - fraction,当摩擦系数fraction=0的时候,侧滑作用力达到最大;当摩擦系数fraction = 1的时候,没有侧滑作用力。

2.3 速度改变

发起碰撞的刚体body1,受到反向作用力;被碰撞的刚体body2,受到正向作用力。

// 法线和切线方向力之和
Vector2 F = normal * Fn + tangent * Ft;
// 刚体中心点到碰撞接触点的向量
Vector2 r1 = point - body1.position;
Vector2 r2 = point - body2.position;

body1.velocity += -F / body1.m;
body1.angleVelocity += cross(-F, r1) * body1.angleVelocity / body1.I;

body2.velocity += F / body2.m;
body2.angleVelocity += cross(F, r2) * body2.angleVelocity / body2.I;

2.4 重叠分离

当两个物体发生重叠,在没有运动速度,或者速度很小的时候,是无法依靠碰撞反馈来将两个物体分开的。比如,通过代码直接设置了物体坐标,导致物体间发生了重叠。针对这种情况,需要增加一个分离速度,将发生重叠的物体“挤”出去。

设重叠的深度为s,则分离速度为:v = s / dt,在计算碰撞作用力的时候,叠加上这样一个偏移速度,就可以产生一个挤出力。但是,当时间dt很小的时候,这个分离速度将会变的很大,看起来很不真实。可以通过给公式增加一些系数,来灵活控制挤出速度。
Δ F n = max ⁡ ( m n ( Δ v n + v b ) , 0 ) v b = β max ⁡ ( 0 , s − δ ) Δ t , ( β 是 缩 放 系 数 , δ 是 可 容 忍 的 穿 透 深 度 ) \Delta F_n = \max(m_n(\Delta v_n + v_b), 0) \\ v_b = \frac{ \beta \max(0, s - \delta)}{\Delta t}, \\ (\beta是缩放系数,\delta是可容忍的穿透深度) ΔFn=max(mn(Δvn+vb),0)vb=Δtβmax(0,sδ),(βδ穿)

碰撞分离

3. 范例

本章Demo使用Unity3D引擎开发,Demo工程已上传github: https://github.com/youlanhai/learn-physics/tree/master/Assets/06-seperation

4. 参考

  1. Erin Catto, GDC 2006 Physics Tutorial
  2. Erin Catto, How Do Physics Engines Work
  3. box2d-lite 轻量级的物理引擎
  4. box2d物理引擎文档
  5. chipmunk物理引擎文档

本系列文章会和我的个人公众号同步更新,感兴趣的朋友可以关注下我的公众号:游戏引擎学习。扫下面的二维码加关注:

游戏引擎学习

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值