Unity 内置物理引擎 PhysX 简易使用指南

作者:LouisLiu
2023-10-29
4 2 0

引言

笔者最近在对一个物理向玩法的游戏设计进行预研,需要高度拟真的游戏环境支撑玩法。在此过程中,尝试使用 Unity 内置的 PhysX 物理引擎对游戏环境进行拟真向物理模拟。而预研时发现,对内置物理引擎的配置有一定的学习门槛,故在此分享研究成果,希望能帮助大家快速搭建真实准确的游戏物理环境。

PhysX

PhysX,读音与 Physics 相同,是一套由 AGEIA(音译为“阿吉亚”或“奥加”)公司开发的物理运算引擎,也是世界三大物理运算引擎之一。另外两款是 Havok 和 Bullet。

2008 年,在 Intel 收购了物理引擎界的领军者 Havok 后,Nvidia 也收购了排名第二的 AGEIA,正式将 PhysX 技术划入旗下。NVIDIA PhysX 承袭自 AGEIA PhysX,但 Nvidia 在此基础上推出了 NVIDIA PhysX 物理加速,并将 PhysX 物理加速功能移植到 NVIDIA GPU 中,用户不必额外购买 PhysX 物理加速卡就能享受到 PhysX 物理加速功能。

目前,NVIDIA 已经开源了最新的 PhysX 5.1 SDK,总计包含 66.2 万行代码、文档和相关资产。PhysX 物理引擎源码可以通过以下 Github 链接下载:https://github.com/NVIDIA-Omniverse/PhysX

开始前的几点说明

  • 物理组件的使用:本指南不会对组件的使用和基础概念进行说明,请在掌握 Unity 物理组件的基础概念和使用方法后再参考;关于 Unity 物理组件的使用,网络上有大量教程可以学习,不在此赘述;本文主要讨论 3D 场景中的物理引擎使用,2D 物理引擎与 3D 物理引擎的使用类似,可类比参考。
  • 物理概念的简化:出于篇幅和方便理解等原因,本文涉及物理概念的表达皆做了简化处理,若需要更精准的描述,可以参考物理领域相关资料。
  • 物理拟真实验的背景:笔者选择了台球的运动模拟对物理引擎进行使用预研,本文中的实验皆在此环境中进行,现象的描述也皆为台球运动模拟环境中的表现。

引擎参数配置

Unity Editor 开启引擎配置页面路径:

Edit -> Project Settings… -> Physics

属性功能
Gravity使用 x、y 和 z 轴设置应用于所有刚体组件的重力。对于真实的重力设置,应将负数应用于 y 轴。重力以每平方秒的世界单位定义。
注意:如果增大重力,可能还需要增大 Default Solver Iterations 值以保持稳定的接触。
Default Material设置在没有为单独碰撞体分配材质的情况下需要使用的默认物理材质。
Bounce Threshold设置速度值。如果两个碰撞对象的相对速度低于此值,则它们在碰撞后不会相互反弹。此值还可以减少抖动,因此不建议将其设置为非常低的值。
Default Max Depenetration VelocityDefine the default value for the maximum depenetration velocity (velocity that the solver can set to a body while trying to pull it out of overlap with the other bodies).
Sleep Threshold设置一个全局能量阈值;当低于该阈值时,非运动刚体(即不受物理系统控制的刚体)可进入睡眠状态。刚体处于睡眠状态不会每帧更新,因此可减少资源消耗。如果刚体的动能除以其质量低于此阈值,该刚体将作为睡眠候选者。
Default Contact Offset设置碰撞检测系统用于产生碰撞接触的距离。该值必须为正,如果设置得太接近零,可能导致抖动。默认情况下,该值设置为 0.01。仅当碰撞体的距离小于其接触偏移值的总和时,碰撞体才产生碰撞接触。
Default Solver Iterations定义 Unity 在每个物理帧运行的解算器进程数。解算器是小型物理引擎任务,决定了许多物理交互,例如关节运动或管理重叠刚体组件之间的接触。
此设置会影响解算器输出的质量,如果使用非默认的 Time.fixedDeltaTime,或者配置要求极高,建议更改此属性。通常,此属性用于减少关节或接触引起的抖动。
Default Solver Velocity Iterations设置解算器在每个物理帧中执行的速度进程数。解算器执行的进程越多,刚体反弹后产生的退回速度的精度就越高。如果遇到连接的刚体组件或布娃娃在碰撞后移动太多的问题,请尝试增加此值。
Queries Hit Backfaces如果希望物理查询(例如 Physics.Raycast)检测是否命中 MeshColliders 的背面三角形,请启用此选项。默认情况下会禁用此设置。
Queries Hit Triggers如果希望物理命中测试(例如射线投射、SphereCasts 和 SphereTests)在与标记为触发器的碰撞体相交时返回命中,请启用此选项。单个射线投射可以覆盖此行为。默认情况下会启用此设置。
Enable Adaptive Force启用此选项可启用自适应作用力。自适应作用力会影响通过一堆对象传递力的方式,可提供更真实的行为。默认情况下会禁用此设置。
Contacts Generation选择接触生成方法。

Legacy Contacts Generation在 Unity 5.5 之前,Unity 使用了基于分离轴定理 (SAT) 的接触生成方法。

PCM 效率更高,但对于较旧的项目,您可能发现继续使用 SAT 会更方便,因为无需略微重新调整物理设置。PCM 可以产生稍微不同的反弹,并且在接触缓冲区中出现的无用接触更少(即在 Collision 实例中得到的传递给 OnCollisionEnterOnCollisionStayOnCollisionExit 的数组)。

升级建议:要迁移使用 Unity 2018.2 或更低版本制作的项目,可能需要更新脚本以便使用代码在复合体中合并面片以及选择接触。

Persistent Contacts Manifold (PCM)每个物理帧生成更少的接触,跨帧共享更多接触数据。PCM 接触生成路径也更准确,并通常在大多数情况下产生更好的碰撞反馈。如需了解更多信息,请参阅有关 Persistent Contact Manifold 的 Nvidia 文档
这是默认值。
Auto Simulation启用此选项可自动运行物理模拟或允许对其进行显式控制。
Auto Sync Transforms启用此选项可在变换组件发生更改时自动将变换更改与物理系统同步。默认情况下会禁用此设置。
Contact Pairs Mode选择要使用的接触对生成类型。

Default Contact Pairs接收来自除运动/运动和运动/静态接触对之外的所有接触对的碰撞和触发事件。

Enable Kinematic Kinematic Pairs接收来自运动/运动接触对的碰撞和触发事件。

Enable Kinematic Static Pairs接收来自运动/静态接触对的碰撞和触发事件。

Enable All Contact Pairs接收来自所有接触对的碰撞和触发事件。
Broadphase Type选择要在物理模拟中使用的粗筛阶段算法。请参阅有关 PhysX SDK刚体碰撞 (Rigid Body Collision) 的 NVIDIA 文档。

Sweep and Prune Broadphase使用“扫掠修剪”粗筛阶段碰撞方法(即沿单个轴排序对象以避免必须检查相距很远的对)。

Multibox Pruning Broadphase多盒修剪使用网格,每个网格单元格执行扫掠修剪。这通常有助于提高性能,例如在平坦世界中有大量游戏对象时。

Automatic Box Pruning该算法与 Multibox Pruning 的算法相似,不同之处在于该算法还可以自动计算世界边界和细分数量。该算法将维护一组网格单元格,并使用常规的“扫掠修剪”方法来计算可能重叠的碰撞体对。该算法通常适用于大型场景,在这样的场景中单个“扫掠修剪”会产生很多额外的误报。
World Bounds使用 Multibox Pruning Broadphase 算法时,定义包围世界的 2D 网格以防止相距遥远的对象相互影响。
仅在 Broadphase Type 设置为 Multibox Pruning Broadphase 时使用此选项。
World Subdivisions2D 网格算法中沿 x 和 z 轴的单元格数。
仅在 Broadphase Type 设置为 Multibox Pruning Broadphase 时使用此选项。
Friction Type选择用于模拟的摩擦力算法。

Patch Friction Type一种基本的强大摩擦力算法,通常可以在较低的解算器迭代次数下产生最稳定的结果。此方法为每对触摸对象仅使用最多四个标量解算器约束。

One Directional Friction Type库仑摩擦力模型的简化版,其中将给定接触点的摩擦力施加在接触法线的交替切线方向上。这需要比面片摩擦更多的解算器迭代,但不如双向模型准确。要使接合体使用此摩擦类型,请将 Solver Type 设置为 Temporal Gauss Seidel

Two Directional Friction Type与单向模型一样,但同时在两个切线方向上施加摩擦力。这需要更多的解算器迭代次数,但更精确。对于具有许多接触点的情况,比面片摩擦的成本更高,因为这会应用于每个接触点。要使接合体使用此摩擦类型,请将 Solver Type 设置为 Temporal Gauss Seidel
Enable Enhanced Determinism无论角色是否存在,场景中的模拟都是一致的(前提是游戏以确定的顺序插入角色)。此模式牺牲了一些性能来确保这种额外的确定性。
Enable Unified Heightmaps启用此选项可以像处理网格碰撞一样处理地形碰撞。
Solver Type选择要用于模拟的 PhysX 解算器。

Projected Gauss Seidel默认 PhysX 解算器。

Temporal Gauss Seidel该解算器提供更好的收敛性和更好的高质量比处理方式,在校正穿透时可以最大限度减小引入的能量,并可提高关节抗过度拉伸的能力。使用默认解算器在模拟过程中遇到一些不稳定的行为时,使用这种解算器通常会有帮助。
Layer Collision Matrix定义基于层的碰撞检测系统的行为方式。选择碰撞矩阵中的哪些层与其他层交互(勾选相应层即可)。
Cloth Inter-Collision
Distance定义每个互碰撞的布料粒子的包裹球体的直径。Unity 确保这些球体在模拟过程中不会重叠。Distance 属性的值应小于配置中的两个粒子之间的最小距离。如果距离较大,则布料碰撞可能违反某些距离约束并导致抖动。
Stiffness互碰撞的布料粒子之间的分离冲力的强度。此值由布料解算器进行计算,应足以保持粒子分离。

文档链接:https://docs.unity3d.com/cn/current/Manual/class-PhysicsManager.html


笔者在台球物理模拟中配置的几个重要参数:

  • Bounce Threshold:调低速度阈值,使台球在很低的速度下仍然进行碰撞。
  • Default Contact Offset:若基于 Unity 引擎长度单位“米”,则场景中台球直径为 0.05715 米,碰撞触发距离需要调小,增加小型物体碰撞计算的精确度。
  • Default Max Angular Speed: 特别注意,Unity 文档疏忽了对此条的说明,导致测试中球体滚动速度一直不拟真。最后排查才发现有最大角速度限制,需要调高最大角速度,模拟出真实情况。

其它项也会影响物理拟真质量,但在此台球实验中不明显,故不多作描述。读者可以根据项目实际需要,进一步调整其它参数。

质量与体积

物理引擎中,质量与体积是物体最基本的物理属性,如果要配置相对真实的物理环境,建议根据真实比例设置质量与体积参数,这样可以更好地模拟物体的真实表现。未来加入的物体也可以以真实世界为基准进行配置,形成统一稳定的物理环境。

质量

查询现实世界资料,台球的质量为 170g=0.17kg。物理引擎中质量单位为 kg,因此设置 rigidbody 组件中的 Mass:0.17

体积

Unity 引擎中的默认长度单位是米(m),也就是说,在引擎中默认创建的球体,直径为 1 米。查询现实世界一颗台球的直径为 57.15mm=0.05715m,在 Unity 中缩放球体 scale 为:X:0.05715,Y:0.05715,Z:0.05715,以使游戏世界中的球体与现实世界球体大小匹配,球体 collider 组件会自动缩放成此大小。

至此,游戏中台球的质量和体积完成了与现实世界的对应。

摩擦力

PhysX 使用库伦摩擦模型,分为静摩擦力和滑动摩擦力。

简单来说,静摩擦力为两个相互接触的物体保持静止时互相之间的摩擦力,超过最大静摩擦力则会发生滑动;滑动摩擦力为两个相互滑动的物体之间的阻力。

现实世界中,摩擦力的大小与压力成正比,其系数为摩擦系数。在物理学中,我们通过摩擦系数描述两物体接触面的光滑程度:

F=μ*N(N 为垂直于接触面的正压力,F 为摩擦力,μ 为摩擦系数)

在 Unity 引擎中,我们通过 Physics Material 中的 Dynamic Friction 和 Static Friction 来描述物体的光滑程度。

那么问题就出现了,现实世界都是用两种材料在某种环境下的摩擦系数来表示光滑程度,可 Unity 怎么是用每个材料的两个独立参数表达光滑程度呢?其实原因很简单,Unity 引擎不可能把任意两种物质间的摩擦系数都记录下来,然后在两个物体发生摩擦时查询它们的摩擦系数来计算摩擦力,所以只能使用抽象的方法定义物体的摩擦参数:动摩擦参数 Dynamic Friction 和静摩擦参数 Static Friction。当两物体接触时,引擎使用对应的参数计算出近似的摩擦系数,从而进行物理模拟。

摩擦系数的计算可以通过 Physics Material 中的 Friction Combine 项进行定制,比如平均值(Average)、取较小值(Minimum)、取较大值(Maximun)、取相乘值(Multiply)。这些模式是物理引擎对现实世界的一种模拟,计算中会产生误差,但在物理引擎中模拟出的摩擦效果是可以接受的。若要算出极为精准的摩擦力,可以考虑用摩擦系数表的方式查询两物体之间的真实摩擦系数进行模拟。 

在台球模拟中,我们知道台球之间摩擦力非常小,近乎于 0,那么设置台球的 Dynamic Friction 和 Static Friction 都为 0,Friction Combine 设为平均值(Average);查询现实世界中球台的滑动摩擦系数约为 0.2,则设置球台的 Dynamic Friction 为 0.4,因为 (台球摩擦参数+球台摩擦参数)/2 = (0+0.4)/2 = 0.2 ;球台的 Static Friction 没有查到具体值,可以设置为比滑动摩擦大一点的 0.45,因为在台球中基本是滚动和滑动运动,此参数不太影响物理模拟。

弹性

物理学中,我们通过弹性系数界定一个物体的弹性,引擎中则使用 Bounciness 设置物体的弹性,它介于 0-1 之间,0 为没有弹性,1 为完全反弹。可以通过 Bounce Combine 项设置弹性模拟的模式。

在台球环境下,一颗球击中另一颗球的物理运动,是沿击中方向动量完全传递的过程,所以需要 Bounciness 为 1 的动量完全传递效果(表现为分离角为 90 度)。

没有滚动的情况下,台球母球与目标球碰撞后总是以 90 度角分离

滚动摩擦力和空气阻力

现实世界里,滚动摩擦力表现为球在地上滚动,然后因滚动摩擦力的作用最终停止的现象。实际原因是球与地面接触后造成地面凹陷,静摩擦力阻止了球无限滚动。

其在物理引擎中的实现则是这样的:有静摩擦力的情况下,给球体施加一个向前滚动的角速度,物体会无限滚动下去。这是因为物理引擎无法精确模拟物体滚动时摩擦导致滚动衰减的情况,所以引擎中引入了角阻力 Angular Drag 的概念,通过这一参数模拟滚动摩擦的现象。因为这是一个不精确但可接受的参数,所以只能对物体进行近似的滚动摩擦模拟。在台球模拟中,我们把台球的 Angular Drag 设为 3,近似模拟台球滚动后受到滚动摩擦力作用而停止的效果。

物理引擎中还引入了 Drag 参数来模拟空气阻力的效果。在台球模拟中,我们忽略空气阻力,所以设置为 0。

施力

物理引擎可以通过 Rigidbody 中的函数进行施加力、施加旋转等操作,也可以通过 Constant Froce 组件施加持续力和旋转,网上有大量资料,在此就不详述了。

现实世界中,球杆击球是一个复杂的物理行为。它不仅仅是针对球的一个点施加一个力,或是通过一个接触面与球体碰撞,而是球杆头击中球后发生形变并推动球前进的复杂物理运动,且击球位置还会大大影响球的运动(产生旋转、跳跃等情况)。

我们应该如何模拟这种力呢?笔者采用的方法是:通过对球同时施加瞬时的力和瞬时的旋转来模拟击球的施力,并配合球杆动画,达到一种仿真的效果。

模拟实验也呈现了令人满意的物理效果,可以表现出台球运动中的真实情况,包括高中低杆击球、侧旋球的拐弯、跳球等技巧的复现。

总结

以上就是笔者通过台球模拟实验总结出的 Unity 物理引擎使用指南。因为研究目的是项目预研,故对引擎的使用并不充分、全面,疏漏之处请各位读者包涵,也欢迎批评指正。

希望此简易使用指南能让各位开发者快速上手物理引擎,实现项目需要的物理模拟效果。



封面:envato 订阅

本文为用户投稿,不代表 indienova 观点。

近期点赞的会员

 分享这篇文章

LouisLiu 

独立游戏制作人 

您可能还会对这些文章感兴趣

参与此文章的讨论

暂无关于此文章的评论。

您需要登录或者注册后才能发表评论

登录/注册