Unity版本的代码还在施工中,项目地址:https://github.com/Mustenaka/AdaptorPhysX
PBD计算架构
PBD(Position-Based Dynamics)是一种基于位置的动力学方法,主要用于模拟弹性和塑性物体的动态行为,对于此算法,需要有如下的一些计算的步骤进行迭代。
- 初始化粒子的位置和速度:
- 设定模拟的初始状态,包括粒子的位置和速度。这可以通过用户输入、初始条件或其他手段来实现。
- 计算约束:
- 定义和计算用于约束的公式。约束可以包括弹簧约束、距离约束、角度约束等。这些约束用于保持物体的形状和结构。
- 迭代求解约束:
- 通过迭代的方式求解约束,使得粒子的位置满足约束条件。迭代求解是PBD的关键步骤,通常进行多次迭代以达到稳定的模拟效果。
- 更新粒子位置:
- 根据迭代求解后的约束,更新粒子的位置。这可以使用显式或隐式积分方法,常见的有Euler方法、Verlet方法、隐式欧拉方法等。
- 碰撞处理:
- 处理碰撞,确保粒子不穿越物体表面。这可能涉及到检测碰撞、反应力的应用等步骤。
- 应用外部力:
- 如果有外部力(如重力、风力等),则将这些力应用于粒子,更新粒子的速度。
- 渲染:
- 将模拟结果渲染出来,以便可视化模拟效果。这可以通过将粒子位置传递给图形引擎进行渲染,或者使用其他可视化手段。
- 循环或终止:
- 重复上述步骤,或者在达到模拟结束条件时终止模拟。
初始化
初始化数据结构应该包含至少以下数据结构。
- 所有粒子的索引集合——List<Particle> particles
- 迭代时间的配置,类似于FixedUpdate的每秒执行X次,往往称之为时间微分——dt
- 粒子的上一时刻位置——prevPosition
- 粒子的本时刻位置——nowPosition
- 粒子的下一时刻位置——nextPosition
- 累计加速度——acc
- 约束函数组——constraint
- 粒子质量——mass
- 刚度——stiffness
- 阻尼——damping
- 重力等环境系数——gravity
- 外力和——fext
特殊内容
粒子构建方式(独立开一个篇章),需要构建一个关系,粒子的移动变化会影响到模型的外部结构变化。
计算约束
计算约束一般有如下类型约束,在不同的非刚体式物理表达中,需要应用不同的约束
- 距离约束(Distance Constraints):
- 用于保持两个粒子之间的距离。这可以模拟物体的形状和结构。
- 角度约束(Angle Constraints):
- 用于约束粒子之间的角度,以保持物体的角度或形状。这在模拟柔软物体时特别有用。
- 弯曲约束(Bend Constraints):
- 用于描述物体中多个粒子之间的曲率或弯曲程度。适用于柔软的物体或绳索等。
- 体积保存约束(Volume Preservation Constraints):
- 用于保持物体的体积。这对于模拟流体或柔软组织等情况很有用。
- 碰撞约束(Collision Constraints):
- 用于防止粒子穿越表面,确保物体之间不发生碰撞。这通常需要与外部的碰撞检测结合使用。
- 固定点约束(Fixed Point Constraints):
- 将某些粒子的位置固定在空间中的特定位置,用于模拟物体的固定部分,如固定点的刚性支撑。
- 法向量约束(Normal Constraints):
- 用于保持表面的法向量方向。这对于确保柔软物体的表面平滑性很有帮助。
- 拉伸约束(Stretching Constraints):
- 用于防止物体的拉伸或挤压。这可以模拟材料的弹性性质。
迭代约束求解
- 初始化:
- 在每个时间步长的开始,初始化粒子的位置和速度。这是整个模拟的起点。
- 计算约束违反度:
- 对于每个约束,计算当前粒子位置下约束的违反度。违反度是指当前粒子位置和约束理想状态之间的差异。
- 迭代求解:
- 对于每个约束,通过迭代来逐步减小违反度。迭代的次数可以根据需要调整,更多的迭代通常会导致更精确的结果。
- 更新粒子位置:
- 在每次迭代中,根据当前的约束情况,调整粒子的位置。这通常涉及到应用某种修正步骤,例如将粒子沿着约束法线方向移动。
- 碰撞处理:
- 如果系统中包含碰撞约束,需要在迭代中处理碰撞。这通常涉及到检测碰撞并调整粒子位置,以避免碰撞。
- 约束权重和硬度:
- 对于每个约束,可能需要定义权重和硬度参数,以控制约束的影响程度。这可以在系统中调整,以影响整个模拟的行为。
- 收敛判据:
- 定义何时算法可以停止迭代,即当系统达到稳定状态时。这通常涉及到判定约束是否足够接近理想状态。
- 时间步长控制:
- 可能需要控制时间步长,以确保模拟的稳定性和效率。这可以通过一些数值方法来实现。
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;
}
}
}
}
碰撞处理
碰撞处理是整个物理系统编写中相当重要且困难的一个环节,一般来说,碰撞的处理需要具备如下流程
- 碰撞检测(Collision Detection):
- 碰撞检测中,需要先进行一个内外碰撞的区分
- 内:质点自身和其他质点的碰撞
- 外:和外部物体的一些碰撞,比如说质点和地板的碰撞等
- 检查哪些particle或者物体表面发生了碰撞,必要时根据下一帧的位置进行预测
- 碰撞检测中,需要先进行一个内外碰撞的区分
- 碰撞响应(Collision Response):
- 如果发现了碰撞,需要对碰撞进行响应,即调整物体的位置以避免穿越。这可以通过修改碰撞点附近的粒子位置来实现。通常,会在碰撞表面上沿法向量方向移动粒子,使其脱离碰撞状态。
- 另外,部分碰撞中可以设置Collider Masker碰撞掩码层级,以特殊化处理一些不愿意碰撞的内容。
- 迭代处理碰撞:
- 碰撞处理可能需要在迭代中进行多次,以确保物体在碰撞检测和响应的过程中能够逐步趋于稳定。你可能需要在迭代中多次进行碰撞检测和响应步骤。
- 迭代过程还要考虑和引擎本身的运行周期相匹配,碰撞的处理属于物理周期,物流周期的计算不能过长,也需要留出视觉渲染周期
- 部分迭代碰撞处理中还需要考虑合并,有时会产生多个碰撞点,这时候需要进行一些合并(“合批”)的算法设计,将多个相邻的碰撞点合并,以统一碰撞信息
- 碰撞约束的集成:
- 将碰撞约束集成到整体的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 条评论