Unity物理引擎底层原理:从数值积分到约束解决,彻底解决物理抖动与穿透 1. 项目概述物理引擎游戏世界的基石在Unity里做游戏物理效果的真实感是决定玩家沉浸度的关键一环。一个物体从空中落下两个角色碰撞后弹开一堆箱子被炸飞后散落一地——这些看似简单的现象背后是一套极其复杂的数学和计算系统在支撑这就是物理引擎。很多开发者尤其是刚接触Unity不久的朋友往往满足于使用Rigidbody、Collider这些组件调调参数就能让东西动起来。但一旦遇到“角色卡墙”、“物体穿透”、“物理抖动”或者想实现一些非标准的物理行为比如布娃娃的撕裂、软体碰撞时就会感到束手无策只能上网搜索零散的“解决方案”知其然不知其所以然。这篇笔记是我在系统梳理Unity物理引擎底层脉络时的总结。它不教你如何调一个刚体的质量也不教你怎么设置碰撞层。那些是手册里的内容。我想聊的是更底层的东西数值积分如何决定物体运动的“手感”碰撞检测如何从“可能碰撞”精确到“接触点”约束解决又如何让一堆纠缠在一起的物体最终稳定下来理解这些你才能从“物理引擎的使用者”转变为“物理问题的解决者”。当你再遇到诡异的物理Bug时你的第一反应不再是盲目试参数而是能像侦探一样根据现象推测是积分误差、检测遗漏还是约束迭代不足并给出精准的修复方案。这对于开发需要精细物理交互的游戏如平台跳跃、赛车、物理解谜或仿真应用如数字孪生至关重要。2. 物理引擎核心三支柱一个动态世界的诞生一个完整的物理模拟循环可以抽象为三个核心步骤它们环环相扣共同编织出动态的虚拟世界。我们可以把这个过程想象成制作一部停格动画你需要决定角色下一帧的位置积分检查他们会不会碰到一起检测如果碰到了就调整他们的姿势避免穿帮解决。2.1 数值积分预测物体的未来物理引擎的核心任务是求解牛顿第二定律F m * a。我们知道受力(F)和质量(m)就能算出加速度(a)。但引擎最终需要更新的是物体的位置(x)和速度(v)。从加速度到位置需要通过积分来完成。然而在连续的时间中积分对我们计算机来说是做不到的我们只能进行离散的“数值积分”。最著名、也是Unity默认采用的方法是显式欧拉法。它的思想非常直接新速度 v1 当前速度 v0 加速度 a * 时间步长 Δt 新位置 x1 当前位置 x0 新速度 v1 * 时间步长 Δt注意位置更新用的是新速度v1。这种方法的优点是计算简单速度快。但它的稳定性很差就像用一根长杆子推一个重箱子用力稍猛或时间步长(Δt)稍大箱子就容易“飞出去”模拟会发散。这就是为什么在Unity中如果你把Fixed Timestep固定时间步长设得太大或者给物体一个瞬间的巨大力物体可能会以不可思议的速度消失。为了提高稳定性更高级的游戏和仿真中会使用韦尔莱积分法或龙格-库塔法。韦尔莱积分不直接存储速度而是存储上一帧的位置通过当前位置和上一帧位置来隐式地计算速度对保守系统能量大致守恒有更好的稳定性。而四阶龙格-库塔法通过在一个时间步长内进行四次采样和加权平均大大减少了截断误差精度最高但计算量也是欧拉法的四倍。实操心得Unity让你选择积分器的地方很隐蔽。在Edit - Project Settings - Physics中Solver Type选项下的Integrator默认是Explicit Euler。对于大多数游戏这足够了。但如果你在做高精度仿真发现能量无故增加物体越晃越快可以尝试切换到RungeKutta4或RungeKutta2。注意这会给CPU带来显著压力务必进行性能测试。2.2 碰撞检测从粗略到精确的排查在一个充满成百上千物体的场景中每帧都对每两个物体进行精确的相交测试比如两个复杂网格的三角面遍历是计算自杀。因此碰撞检测被设计成一个多阶段、由粗到细的管道。第一阶段宽相位检测这个阶段的目标是快速找出所有“可能发生碰撞”的物体对并剔除那些明显不可能碰撞的。Unity主要使用基于轴对齐包围盒的算法。每个物体都有一个Axis-Aligned Bounding Box这是一个始终与世界坐标轴对齐的、能包裹住物体的最小长方体。判断两个AABB是否相交只需要比较它们在x、y、z轴上的最大最小值计算代价极低。 Unity内部会使用空间分割数据结构来加速查询比如动态AABB树。它会把所有物体的AABB组织成一棵二叉树查询时能快速排除大量不在同一空间的物体。这就是为什么即使场景中有上千个静态物体性能影响也很小的原因。第二阶段窄相位检测经过宽相位筛选后剩下的物体对进入窄相位。这里要进行精确的几何相交测试。根据碰撞体的类型算法不同基础形状间如球体-球体、盒-盒、胶囊-胶囊都有直接高效的数学公式进行判断。凸网格间使用分离轴定理。其核心思想是如果能找到一条轴使得两个物体在该轴上的投影不重叠那么它们就一定没有碰撞。需要测试的轴包括每个物体的每个面的法线以及每条边的叉积方向。非凸网格或复杂网格通常分解为多个凸体凸分解后再检测或者使用更通用的算法如GJK算法。GJK算法通过迭代寻找两个凸体之间的最近点通过判断这两个点是否重合或距离小于阈值来检测碰撞效率很高。第三阶段接触点生成确定碰撞发生后还需要计算接触信息穿透深度、接触点位置、接触法线。这些信息是后续解决碰撞的依据。例如对于两个立方体的碰撞可能生成1个或多个接触点如面接触、边接触。EPA算法常与GJK配合使用在GJK判断碰撞后EPA会沿着单纯形的边向外扩展直到找到穿透深度和接触法线。注意事项复杂的网格碰撞器Mesh Collider在窄相位检测时开销巨大尤其是非凸的。一个黄金法则是永远不要对移动的物体使用非凸的Mesh Collider。对于角色使用胶囊体对于车辆使用复合的盒体和圆柱体对于复杂静态场景可以将其烘焙成更简单的凸体组合或使用层次化的碰撞体。2.3 约束解决让世界重归秩序检测到碰撞后物体已经相互穿透了。约束解决的任务就是修正这种非法状态并模拟碰撞后的响应反弹、摩擦。这本质上是一个求解约束条件的问题。最常见的约束就是“两个刚体不能相互穿透”。1. 冲量法这是最直观的方法。当检测到穿透时引擎会计算一个瞬间的冲量J施加在两个物体上将它们沿着接触法线方向“推开”并依据恢复系数弹性计算反弹速度依据摩擦系数计算切向速度的衰减。 冲量J -(1 e) * (相对速度 · 接触法线) / (有效质量)。其中e是恢复系数有效质量包含了两个物体的质量和转动惯量。 这个过程在一个时间步长内可能会对同一对物体进行多次迭代Solver Iterations以更准确地解决多个约束相互影响的情况比如一堆盒子堆在一起。2. 位置修正冲量法修正了速度但穿透可能仍然存在特别是当时间步长较大或物体质量差异巨大时。因此引擎通常还会进行一步位置修正直接将物体沿着接触法线方向移动一个微小距离以消除穿透。在Unity中这对应于Solver Type中的Projected Gauss-Seidel方法。PGS迭代地处理每个约束逐步将误差穿透投影掉。3. 连续碰撞检测对于高速运动的物体比如子弹在离散的时间步长内它们可能从A点直接“穿越”到B点中间没有与障碍物发生重叠导致宽相位和窄相位检测全部失效。这就是“隧道效应”。 为了解决这个问题需要使用连续碰撞检测。CCD不是检测两个静态形状是否相交而是检测两个物体从上一帧到当前帧的运动扫掠体是否相交。对于高速小物体如子弹Unity的Rigidbody组件上可以开启Continuous或Continuous Dynamic模式其背后可能就是基于扫掠体或射线投射的近似CCD。踩坑实录一堆物体堆叠时疯狂抖动或爆炸怎么办首先增加Physics设置中的Solver Iteration Count默认6次给约束解决更多计算时间。其次检查物体的质量比是否过于悬殊比如一个质量1的盒子放在质量0.001的盒子上尽量让相互作用的物体质量级相近。最后可以适当增加Contact Offset接触偏移和Solver Velocity Iterations让系统更早、更柔和地处理接触。3. Unity物理引擎的架构与实战调优理解了三大支柱我们再来看看Unity是如何将它们封装起来的以及如何在实战中驾驭它。3.1 Unity物理引擎的层级视图Unity的物理系统是一个典型的面向组件架构Collider组件定义物体的几何形状用于碰撞检测。它是个“感知器”没有质量。Rigidbody组件定义物体的物理属性质量、阻力、转动阻力并使其受物理引擎驱动。它是个“驱动器”。一个物体要有物理行为至少需要Collider Rigidbody静态物体可以只有Collider。Physics Manager全局设置大脑。位于Edit - Project Settings - Physics。这里控制着世界的全局参数如重力、默认材质、层碰撞矩阵、求解器迭代次数、时间步长等。绝大多数底层调优都在这里进行。物理材质附加在Collider上定义摩擦力和反弹力恢复系数的具体数值曲线。它决定了碰撞的“表面特性”。引擎在每一帧的FixedUpdate周期内注意不是Update完成以下工作收集所有活动Rigidbody上的力和扭矩。进行数值积分更新物体的速度和角速度临时。执行宽相位和窄相位碰撞检测生成接触点列表。基于接触点和物理材质构建速度约束和位置约束。迭代运行求解器PGS计算满足所有约束所需的冲量和位置调整。应用冲量更新速度应用位置修正。将最终的位置和旋转同步到Transform组件在FixedUpdate之后Update之前。3.2 关键参数深度解析与调优指南知道原理后调整参数就不再是玄学。以下是几个最关键参数的解析1. Fixed Timestep默认0.02秒50Hz。这是物理模拟的“心跳”。值越小模拟越精细越能捕捉快速交互但CPU开销越大。不要盲目调小。对于大多数60FPS的游戏0.0167秒60Hz是个平衡点。如果你的游戏物理简单甚至可以尝试0.033秒30Hz。一个常见误区是认为调小它能解决所有抖动问题实际上在约束解决不充分的情况下更小的步长可能让迭代收敛更慢。2. Solver Iteration Count / Velocity Iterations这是每帧物理更新中用于解决约束和接触的迭代次数。这是解决复杂接触堆叠、链条、布料稳定性最重要的参数。默认6次可能不够。现象一堆盒子堆不高就塌了角色在墙角抽搐关节连接的部分剧烈抖动。对策逐步提高此值8 12 16。观察效果。注意每增加一次迭代所有约束都会多计算一遍性能线性增长。通常12次能解决大部分中度复杂场景。3. Contact Offset / Skin Width这是一个非常微小但重要的值。它定义了一个围绕在碰撞体表面的“外层皮肤”。当两个物体的距离小于这个偏移量时引擎就认为它们“即将接触”或“已经接触”并开始应用排斥力而不是等到真正穿透。适当增加这个值比如从0.01增加到0.05可以极大地提高堆叠稳定性和减少抖动因为它给了求解器一个缓冲空间来提前处理接触而不是在已经深度穿透后暴力修正。4. Collision Detection ModeDiscrete离散检测。默认模式性能最好但有隧道效应。Continuous连续检测。防止该物体高速运动时穿过其他静态物体。开销中等。Continuous Dynamic连续动态检测。防止该物体与其他标记为Continuous或Continuous Dynamic的物体相互穿透。开销最大。实战策略只给高速小物体子弹、抛射物和关键角色玩家使用Continuous。给所有静态障碍物使用Discrete即可。滥用Continuous Dynamic会严重拖累性能。3.3 性能优化实战策略物理引擎是CPU杀手优化至关重要。策略一分层管理用好Unity的Layer Collision Matrix。让不需要相互碰撞的物体层彻底忽略对方如子弹层和特效层。这能在宽相位就剔除大量无效检测对。策略二简化碰撞几何这是最重要的优化。用简单的原始碰撞体Box, Sphere, Capsule组合来近似复杂形状。对于移动的物体这是铁律。对于静态关卡可以考虑使用Mesh Collider的凸分解选项。手动在3D建模软件中创建低模的碰撞体网格。使用Unity的Physics Preprocessor工具或第三方工具生成更高效的碰撞体。策略三休眠机制Rigidbody组件有休眠功能。当一个物体速度近乎为零且一段时间没有外力作用时它会进入休眠状态。休眠的物体几乎不消耗物理计算资源。确保你的物体在静止后能顺利休眠。检查是否有关节、持续的力或脚本在阻止它休眠。策略四控制物理更新范围对于大型开放世界可以使用物理LOD。远离摄像头的物体可以降低其物理更新频率通过脚本控制Rigidbody的更新或者用更简单的碰撞体代替。4. 高级话题与常见疑难杂症排查当你掌握了基础一些更深入的问题和古怪的Bug就会进入视野。4.1 关节与约束构建机械系统关节是物理引擎中“约束”的直观体现。Unity提供了铰链关节、固定关节、弹簧关节等。它们本质上都是在物体间定义了某种运动限制如只能绕某个轴旋转、保持固定距离等。在底层关节被转化为约束方程和碰撞约束一起送入同一个求解器迭代求解。常见问题关节系统剧烈抖动或断裂。原因关节约束的求解优先级和迭代次数不足。排查首先确保连接关节的两个物体都有Rigidbody且质量比合理。然后在关节组件的属性中尝试调高Solver Iteration Count和Solver Velocity Iterations这些是覆盖全局设置的局部参数。对于弹簧关节过高的弹簧力或过低的阻尼也会导致不稳定。4.2 射线与查询与物理世界交互Physics.Raycast,Physics.OverlapSphere这些API是主动向物理世界发起查询。它们会触发从宽相位到窄相期的检测流程但不会触发约束解决因为只是查询不修改状态。性能消耗取决于查询的范围和复杂度。技巧频繁的射线检测如每帧对多个物体是性能热点。尽量使用RaycastCommand或Job System进行批处理异步查询特别是在ECS架构或大量查询时。4.3 典型Bug与排查清单遇到物理问题可以按以下清单逐项排查现象可能原因排查步骤与解决方案物体高速穿透隧道效应1. 为高速物体Rigidbody设置Collision Detection为Continuous。2. 增加物理更新频率减小Fixed Timestep。3. 使用射线投射在运动前进行预测。堆叠物体抖动、爆炸约束求解不充分1. 增加全局Solver Iteration Count首要。2. 增加Contact Offset。3. 检查并调整相互堆叠物体的质量避免差异过大100倍。4. 为堆叠物体使用更简单的碰撞体形状。角色在斜坡上打滑或卡住摩擦设置不当或碰撞体问题1. 检查角色碰撞体建议用胶囊体是否足够平滑盒体在斜坡上表现差。2. 调整物理材质的动/静摩擦力。3. 对于角色控制器考虑使用CharacterController组件或自己基于射线检测实现而非纯物理驱动。施加力后物体运动缓慢或不动质量过大或阻力过大1. 检查Rigidbody的Mass属性默认值可能不合理如一个默认Cube质量是1相当于1公斤。根据感觉调整通常小型物体在0.1-10之间。2. 检查Drag线性阻力和Angular Drag旋转阻力是否设得过高。物理模拟看起来“卡顿”或不流畅Fixed Update与渲染帧率不同步1. 这是正常现象。物理以固定步长运行渲染以可变帧率运行。Unity会插值渲染。2. 确保Rigidbody的Interpolation设置为Interpolate基于上一帧物理结果平滑插值或Extrapolate预测下一帧这能极大改善视觉流畅度。移动平台上的物体滑落父子关系与物理坐标冲突1. 不要将动态Rigidbody物体设置为移动平台另一个Rigidbody的子物体。物理引擎和Transform系统会冲突。2. 正确做法让移动平台带动其上的物体可以通过在接触时给物体施加速度或使用固定关节Fixed Joint临时连接。4.4 超越内置物理何时需要考虑其他方案Unity内置的NVIDIA PhysX引擎非常强大但并非银弹。需要确定性模拟网络同步的物理游戏如RTS、赛车竞技要求所有客户端计算结果完全一致。浮点数计算顺序、编译器优化都可能带来非确定性。这时可能需要寻找确定性的物理库或对PhysX进行封装和定点数改造。超大规模实体模拟成千上万的独立物理实体如大量粒子、破碎物。内置物理的GameObject开销巨大。应考虑使用Unity DOTS物理它基于ECS架构利用Burst编译器和Job System进行多线程并行计算性能可提升数个数量级。特殊材质效果如布料、软体、流体。Unity有Magica Cloth 2、Obi等优秀的第三方资产。它们通常使用基于位置动力学或粒子系统等特殊算法与内置刚体物理解耦。物理引擎是游戏开发中最深邃的子系统之一。从简单的积分公式到复杂的约束求解每一步都充满了权衡速度与精度、稳定性与性能、简单与真实。理解这些底层脉络并不能让你立刻写出一个比Unity更好的物理引擎但它给了你一张地图。当你的游戏世界出现物理异常时这张地图能指引你快速定位问题所在——是在积分阶段步长太大是在检测阶段形状太复杂还是在求解阶段迭代不足这种从现象直抵本质的调试能力正是资深开发者与初学者之间的一道分水岭。我的建议是在下次遇到物理问题时先别急着搜索“Unity 物体抖动怎么办”而是打开Project Settings中的Physics面板结合这篇文章里的原理像一个系统工程师一样有根据地去调整和实验。你会发现很多问题其实都有迹可循而解决它们的过程本身就是对游戏世界运作规律的一次深刻理解。