Unity版本的代码还在施工中,项目地址:https://github.com/Mustenaka/AdaptorPhysX

PBD计算架构

PBD(Position-Based Dynamics)是一种基于位置的动力学方法,主要用于模拟弹性和塑性物体的动态行为,对于此算法,需要有如下的一些计算的步骤进行迭代。

  1. 初始化粒子的位置和速度:
    • 设定模拟的初始状态,包括粒子的位置和速度。这可以通过用户输入、初始条件或其他手段来实现。
  2. 计算约束:
    • 定义和计算用于约束的公式。约束可以包括弹簧约束、距离约束、角度约束等。这些约束用于保持物体的形状和结构。
  3. 迭代求解约束:
    • 通过迭代的方式求解约束,使得粒子的位置满足约束条件。迭代求解是PBD的关键步骤,通常进行多次迭代以达到稳定的模拟效果。
  4. 更新粒子位置:
    • 根据迭代求解后的约束,更新粒子的位置。这可以使用显式或隐式积分方法,常见的有Euler方法、Verlet方法、隐式欧拉方法等。
  5. 碰撞处理:
    • 处理碰撞,确保粒子不穿越物体表面。这可能涉及到检测碰撞、反应力的应用等步骤。
  6. 应用外部力:
    • 如果有外部力(如重力、风力等),则将这些力应用于粒子,更新粒子的速度。
  7. 渲染:
    • 将模拟结果渲染出来,以便可视化模拟效果。这可以通过将粒子位置传递给图形引擎进行渲染,或者使用其他可视化手段。
  8. 循环或终止:
    • 重复上述步骤,或者在达到模拟结束条件时终止模拟。

初始化

初始化数据结构应该包含至少以下数据结构。

  1. 所有粒子的索引集合——List<Particle> particles
  2. 迭代时间的配置,类似于FixedUpdate的每秒执行X次,往往称之为时间微分——dt
  3. 粒子的上一时刻位置——prevPosition
  4. 粒子的本时刻位置——nowPosition
  5. 粒子的下一时刻位置——nextPosition
  6. 累计加速度——acc
  7. 约束函数组——constraint
  8. 粒子质量——mass
  9. 刚度——stiffness
  10. 阻尼——damping
  11. 重力等环境系数——gravity
  12. 外力和——fext
特殊内容

粒子构建方式(独立开一个篇章),需要构建一个关系,粒子的移动变化会影响到模型的外部结构变化。

计算约束

计算约束一般有如下类型约束,在不同的非刚体式物理表达中,需要应用不同的约束

  1. 距离约束(Distance Constraints):
    • 用于保持两个粒子之间的距离。这可以模拟物体的形状和结构。
  2. 角度约束(Angle Constraints):
    • 用于约束粒子之间的角度,以保持物体的角度或形状。这在模拟柔软物体时特别有用。
  3. 弯曲约束(Bend Constraints):
    • 用于描述物体中多个粒子之间的曲率或弯曲程度。适用于柔软的物体或绳索等。
  4. 体积保存约束(Volume Preservation Constraints):
    • 用于保持物体的体积。这对于模拟流体或柔软组织等情况很有用。
  5. 碰撞约束(Collision Constraints):
    • 用于防止粒子穿越表面,确保物体之间不发生碰撞。这通常需要与外部的碰撞检测结合使用。
  6. 固定点约束(Fixed Point Constraints):
    • 将某些粒子的位置固定在空间中的特定位置,用于模拟物体的固定部分,如固定点的刚性支撑。
  7. 法向量约束(Normal Constraints):
    • 用于保持表面的法向量方向。这对于确保柔软物体的表面平滑性很有帮助。
  8. 拉伸约束(Stretching Constraints):
    • 用于防止物体的拉伸或挤压。这可以模拟材料的弹性性质。

迭代约束求解

  1. 初始化:
    • 在每个时间步长的开始,初始化粒子的位置和速度。这是整个模拟的起点。
  2. 计算约束违反度:
    • 对于每个约束,计算当前粒子位置下约束的违反度。违反度是指当前粒子位置和约束理想状态之间的差异。
  3. 迭代求解:
    • 对于每个约束,通过迭代来逐步减小违反度。迭代的次数可以根据需要调整,更多的迭代通常会导致更精确的结果。
  4. 更新粒子位置:
    • 在每次迭代中,根据当前的约束情况,调整粒子的位置。这通常涉及到应用某种修正步骤,例如将粒子沿着约束法线方向移动。
  5. 碰撞处理:
    • 如果系统中包含碰撞约束,需要在迭代中处理碰撞。这通常涉及到检测碰撞并调整粒子位置,以避免碰撞。
  6. 约束权重和硬度:
    • 对于每个约束,可能需要定义权重和硬度参数,以控制约束的影响程度。这可以在系统中调整,以影响整个模拟的行为。
  7. 收敛判据:
    • 定义何时算法可以停止迭代,即当系统达到稳定状态时。这通常涉及到判定约束是否足够接近理想状态。
  8. 时间步长控制:
    • 可能需要控制时间步长,以确保模拟的稳定性和效率。这可以通过一些数值方法来实现。

Example:在计算迭代中,

public class Particle
{
    public Vector3 position;
    public Vector3 velocity;
}

public class DistanceConstraint
{
    public Particle particle1;
    public Particle particle2;
    public float restDistance; // 目标距离
}

public class PBDConstraintSolver
{
    public void SolveDistanceConstraints(List<DistanceConstraint> distanceConstraints, int iterations)
    {
        for (int iteration = 0; iteration < iterations; iteration++)
        {
            foreach (var constraint in distanceConstraints)
            {
                Vector3 delta = constraint.particle2.position - constraint.particle1.position;
                float currentDistance = delta.magnitude;
                float errorFactor = (currentDistance - constraint.restDistance) / currentDistance;

                // 计算修正向量
                Vector3 correction = 0.5f * errorFactor * delta;

                // 将修正应用到粒子位置
                constraint.particle1.position += correction;
                constraint.particle2.position -= correction;
            }
        }
    }
}

碰撞处理

碰撞处理是整个物理系统编写中相当重要且困难的一个环节,一般来说,碰撞的处理需要具备如下流程

  1. 碰撞检测(Collision Detection):
    • 碰撞检测中,需要先进行一个内外碰撞的区分
      • 内:质点自身和其他质点的碰撞
      • 外:和外部物体的一些碰撞,比如说质点和地板的碰撞等
    • 检查哪些particle或者物体表面发生了碰撞,必要时根据下一帧的位置进行预测
  2. 碰撞响应(Collision Response):
    • 如果发现了碰撞,需要对碰撞进行响应,即调整物体的位置以避免穿越。这可以通过修改碰撞点附近的粒子位置来实现。通常,会在碰撞表面上沿法向量方向移动粒子,使其脱离碰撞状态。
    • 另外,部分碰撞中可以设置Collider Masker碰撞掩码层级,以特殊化处理一些不愿意碰撞的内容。
  3. 迭代处理碰撞:
    • 碰撞处理可能需要在迭代中进行多次,以确保物体在碰撞检测和响应的过程中能够逐步趋于稳定。你可能需要在迭代中多次进行碰撞检测和响应步骤。
    • 迭代过程还要考虑和引擎本身的运行周期相匹配,碰撞的处理属于物理周期,物流周期的计算不能过长,也需要留出视觉渲染周期
    • 部分迭代碰撞处理中还需要考虑合并,有时会产生多个碰撞点,这时候需要进行一些合并(“合批”)的算法设计,将多个相邻的碰撞点合并,以统一碰撞信息
  4. 碰撞约束的集成:
    • 将碰撞约束集成到整体的PBD约束系统中。这通常需要修改PBD算法的求解步骤,以包括内碰撞的影响。在这里,你需要综合考虑距离约束、碰撞约束等不同类型的约束。

碰撞处理的方法相对来说很特殊,而且存在很多的特殊情况处理需要去处理,一般来说,极其精确的碰撞处理可以使用GJK进行碰撞检测,并且使用EPA算法进行碰撞嵌入向量运算以实现碰撞推离的效果,这两个算法都是基于计算闵可夫斯基差得来的,但是仅在凸物体中适用,而柔体化的变形,往往会产生凹陷效果,因此这条方案适用于刚体碰撞检测,而柔体不适用。

一个Unity的很著名的柔体算法库Obi采用了Frank-Wolfe的算法进行空间预测来实现碰撞检测处理,这种方法需要处理下一帧预测,并采用了一种利用梯度下降的方法进行碰撞预测求解,这种对于动态物体碰撞静态物体有效果,但对于时刻正在操控者的动态物体碰撞动态物体效果依然不是很好。

在一些简单的碰撞处理方法中,可以采用对每一个质点设置一个影响半径(也有称呼为影响域,Effect area),当其他的碰撞物体的点小于影响半径的时候,将它和粒子中心点连线并且推开,这是一种极其初级的办法,也是最简单的办法。这个思路可以扩展到box、capsule等常见shape上

使用射线求解也是一个不错的思路,用于判断当前直线距离的目的地处是否有目标几何图形,射线求解也可以自己编写Moller算法改进批量处理。这种方法需要设计射线的发出点,一个思路就是实时记录当前物体的上一帧的位置,和当前帧进行相减得到一个方向向量,再利用这个向量进行空间求解,但这种方法也无法应对复杂场景。另外这种方法一旦发生穿模,那么处理就失效了(物体都在射线发出点前了)

有一种思维角度就是利用CloestPoint,时刻获取碰撞发出物体和被碰撞物体的Mesh结构,获取Mesh中两边彼此最近的CloestPoint点,根据这两个点的距离大小判断碰撞,这种方法不精确,而且在穿模之后也需要额外处理穿模后解决方案,但是确是一种较为快速的方法,使用隐含结构来处理甚至可以更快。

另外还有一种处理的思路,就是利用构建八叉树(或者四叉树),进行空间分割维护,以一种近似SAT的思维角度去处理碰撞,这种碰撞的处理往往需要处理效率平衡,因为时刻在维护一个庞大的数据结构

总结方法有:

  • 精确碰撞GJK结合EPA,不推荐非刚体物体,因为有凹面
  • Frank-Wolfe预测方法,不推荐在时刻输入变化中使用
  • 简单碰撞处理,仅适用于不复杂物体,可以作为最低级别处理方法
  • Moller的射线求解方法,如果空间物体很多,性能和射线发出点是一个难点,而且在穿模发生之后,无法解决处理
  • CloestPoint计算Distance方法,精度不如,而且需要人工处理穿模后效果,但是利用隐含结构进行粗略判定是一个有效的方式
  • 构建空间索引(八叉树|死叉树)进行空间分割,实现相对复杂,而且需要根据实际情况出发去特殊设计,同时还要考虑到每一帧的物体变换情况做维护,精度根据算法效率成反比

碰撞的接口——考虑计算力

在设计以复杂物体变化的运动模型时,往往会需要考虑这个碰撞产生的力的影响,在计算时,往往会根据碰撞预测嵌入的深度信息Depth,根据牛顿第三定律所知,力是相互的,因此,在考虑计算碰撞产生力的情况时需要同时考虑发生的两者的情况,根据目标物体的因素,需要做如下考虑

  • 碰撞双方都是刚体,需要考虑能量公式,获得冲量效果,同时根据力在计算接下来的移动位置、速度、加速度
  • 如果碰撞物体一方时刚体,根据NextPosition计算出Depth,再根据Depth计算出F,对于刚体,直接将F解算碰撞物体计算移动位置、速度、加速度等即可。
  • 碰撞双方都是柔性物体,根据碰撞深度,产生一个碰撞力F,再将碰撞点最近的particle进行一个动能传导(往往可以沿着约束传导效果)

 


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注