尧图精选

Unity刚体碰撞穿透与抖动:从隧道效应到物理参数调优

🕒 发布时间:2026/9/16 18:33:42 📁 来源:尧图网络
很多人第一次碰到 Rigidbody 碰撞穿透或者碰撞抖动第一反应都是“物理引擎出 bug 了”。尤其当你做一个高速射击玩法子弹在场景里飞来飞去结果直接穿墙而过或者在墙壁上抖成筛子那一刻真的非常崩溃。但做过几轮物理调试之后你会发现绝大多数穿透和抖动都不是引擎的锅而是对 Unity 物理系统的理解偏差以及参数配置不合理导致的连锁反应。这篇文章我就围绕“物体Rigidbody 组件碰撞穿透、碰撞抖动”这个主题把背后的原理、排查思路、参数调法和真实案例完整梳理一遍。内容面向已经能跑通基本 Unity 项目、但被物理问题卡住的开发者也适合那些刚接触物理系统、想提前避坑的新手。1. 先从现象入手穿透和抖动分别是哪种“病”1.1 穿透、抖动视觉上的差异其实很大穿透的表现很直观一个本该发生碰撞的物体直接穿过了另一个碰撞体。比如子弹打向一堵墙结果子弹没停而是穿过去继续飞了。抖动就相对隐蔽一些常见表现是一个物体接触地面或者另一个物体时会出现高频的颤抖、摇晃、上下弹跳像“果冻”一样静止不下来或者物体之间互相推来推去怎么也稳定不住。这两种现象在调试时的感觉完全不一样。穿透通常和“速度”“碰撞检测模式”“碰撞体尺度”关系最大属于物理引擎在时间离散化过程中的盲区。抖动则往往是“力”“材质”“解算参数”共同作用的结果比如碰撞体被压得太深、求解器反复纠偏、摩擦力参与过度等。1.2 这两个问题为什么会凑到一起很多人以为穿透和抖动是两件独立的事大概率不会同时出现在同一个物体上。但当你调完穿透之后很有可能紧接着就发现目标物体抖起来了。原因是解决穿透最常用的手段是开启连续碰撞检测CCDContinuous Collision Detection或者把碰撞体加厚一旦碰撞事件开始被物理引擎正确解算物体的接触压力、反弹速度、摩擦参与度都会发生变化。如果此时物理材质、求解器迭代次数、最大脱离穿透速度这些参数没有跟上刚体就会出现接触瞬间速度突变、无法稳定静止的情况。我在自己的项目里处理高速飞行物时几乎每次都经历了“穿墙 → 开 CCD → 不穿了但开始抖 → 反复调材质和约束 → 最终稳定下来”的过程。所以这篇文章不是单纯列出两种问题的独立解决方案而是把它们的共因拆开讲清楚尤其是那些同时影响穿透和抖动的底层参数。2. 高速穿透的根因物理步长离散化和隧道效应2.1 隧道效应是怎么产生的Unity 的物理模拟并不是连续的它默认每 0.02 秒也就是 1/50 秒做一次物理计算这个周期叫 Fixed Timestep。每个物理帧之间物体的移动轨迹并不会被引擎“拉丝”检测它只会记录每个物理帧开始时和结束时的状态。如果一个高速物体在一个物理帧内移动的距离大于它要穿越的那个碰撞体在移动方向上的厚度物理引擎就可能完全错过这次碰撞。举个例子一颗子弹以 50 米/秒的速度飞行Fixed Timestep 是 0.02 秒那么每个物理帧子弹会移动 1 米。如果墙面碰撞体的厚度只有 0.1 米那么子弹从墙前一帧的位置到下一帧的位置直接跨越了整面墙物理引擎看这两个离散状态都没有发生重叠于是碰撞事件就根本不触发。这个现象在物理引擎领域叫“隧道效应”Tunneling是高速穿透最核心的机制。2.2 碰撞检测模式Continuous、Dynamic、Speculative 到底怎么选Unity 的 Collision Detection 提供了几种模式很多人第一反应是“直接改成 Continuous 就好了”但实际并不是这么简单。四种模式分别是Discrete默认的离散检测开销最低但高速运动时容易穿透。Continuous会把当前刚体对静态碰撞体没有 Rigidbody 或标记为静态的碰撞体做连续扫掠检测防止穿过静态场景物体但对动态刚体的检测仍然是离散的。Continuous Dynamic对静态碰撞体和动态刚体都会做连续检测适合用于高速运动且需要和其他动态物体碰撞的场景开销最大。Continuous Speculative投机性连续碰撞检测比 Continuous Dynamic 开销低会提前推断可能的碰撞并约束运动对于高速运动和需要检测动态物体的情况比较友好但偶尔会出现碰撞提前触发或轻微抖动的情况。我自己的选型经验是这样子弹、高速无人机这类只需要撞墙的场景用 Continuous 就足够。如果子弹还要拦截其他高速运动物体比如击落另一颗导弹那就要考虑 Continuous Dynamic 或者 Continuous Speculative。两个高速动态物体都用 Continuous Dynamic 时性能开销会成倍增加手机上这种场景要谨慎。2.3 CCD 不是万能药薄物体和过小碰撞体依然会出问题开启 CCD 之后很多穿透问题确实解决了但要注意两个例外。第一如果碰撞体本身非常薄比如一面厚度只有 0.01 米的“纸片墙”即便开了 Continuous极端速度下依然可能穿透。第二如果两个动态高速物体相互碰撞只把其中一个设置成 Continuous 是不行的必须至少有一方使用 Continuous Dynamic 或 Speculative否则连续检测不会覆盖到动态-动态组合。另外还有一个容易被忽略的坑如果你开了 CCD但碰撞体本身太小比如一个半径只有 0.005 米的小钢珠那么它在高速度下的扫掠结果会非常不稳定有时会撞到不该撞的东西有时又漏掉该撞的。这种时候最务实的做法不是继续调 CCD而是给碰撞体一个合理的物理尺寸或者在视觉模型外面包一个更符合物理直觉的简化碰撞体比如用胶囊体代替小圆柱、用盒体代替复杂的凸包网格。3. 碰撞抖动的核心来源刚体参数和物理材质没有对症下药3.1 Interpolation 选项乱选反而会加剧抖动讲到抖动很多文档会先让你确认 Rigidbody 的 Interpolation插值设置。Unity 默认是 None意思是刚体直接使用物理状态的位置不做任何插值平滑。由于物理计算频率是 50Hz而渲染帧率可能是 60Hz、120Hz 甚至更高导致画面中的物体会出现一卡一卡的跳变尤其在较低帧率下特别明显。所以很多项目会设置成 Interpolate插值或者 Extrapolate外推。但这里有个坑如果你的物理计算本身就不稳定比如每帧都在给刚体施加不合理的力插值只能让画面看起来平滑一点点甚至会因为插值目标位置反复跳动让抖动看起来更诡异。我遇到过这样的情况物体明明在物理上应该稳定静止但因为代码在 Update 里每帧修改 transform 位置同时 Rigidbody 又在 FixedUpdate 里被物理引擎推进插值出来的位置就反复抽搐。所以先确认“不要同时用 transform 和物理学控制同一个刚体”再谈插值设置。3.2 Physic Material 的摩擦力、弹力在堆叠场景里会引发连锁反应物理材质Physic Material在碰撞抖动里承担的角色经常被低估。默认的物理材质没有设置任何参数时动态摩擦力Dynamic Friction、静态摩擦力Static Friction为 0.4弹力Bounciness为 0。听起来很合理但在多刚体堆叠场景里默认材质往往会变成抖动的元凶。一个典型场景你搭了一摞纸箱每个箱子都有 Rigidbody 和 Box Collider。箱子接触时如果没有合理的摩擦力组合箱子之间会有微小的滑动物理求解器检测到滑动后会尝试修正速度修正过程中又产生了新的接触压力压力让接触面变形视觉上表现不出来但物理上碰撞体会发生微小重叠然后求解器再次强制分离形成高频循环表现在画面上就是整摞箱子不停颤抖。特别要注意弹力Bounciness这个值如果设置成 0.8 以上物体每次接触后都会产生明显的反弹速度就像乒乓球一样非常容易抖起来。经验做法是需要堆叠稳定的物体静态摩擦力可以适当调高比如 0.6 到 1.0弹力调成 0或者用一个带一点摩擦、零弹力的自定义物理材质。尽量不要在同一个接触面上让两个物体都使用“零摩擦 高弹力”的组合否则你会看到一堆物体互相弹来弹去停不下来。3.3 求解器迭代次数和最大脱离穿透速度抖动的隐形调节旋钮除了材质还有两个参数被大多数人忽略Solver Iterations求解器迭代次数和 Default Max Depenetration Velocity默认最大脱离穿透速度。先说 Solver Iterations。Unity 的物理引擎通过迭代计算的方式来逼近稳定的接触解。迭代次数越高接触解越稳定但 CPU 开销也越大。默认值是 4 组迭代对简单场景够用但对复杂的多刚体堆叠比如 20 个箱子叠在一起就会明显不够物体会像果冻一样不断晃动。调高到 6 或 8 时堆叠稳定性会有质的变化。再说 Max Depenetration Velocity。当两个碰撞体相互穿透时物理引擎会以一定速度把它们“推”开这个速度的默认上限就是刚才提到的值。对于高速穿透后修复的场景如果这个值太小物体从穿透状态恢复的速度太慢看起来像是在墙里卡了很久还伴随着持续的挤压抖动。如果值太大物体会被一下子弹飞视觉上“砰”地弹开。该参数可以在 Project Settings - Physics 里全局调整也可以在 Rigidbody 组件上单独覆盖。实际项目中我一般会全局保持在默认值附近遇到个别高速穿透的场景才会单独给刚体提高上限然后配合弹力参数一起调。4. Fixed Timestep穿透和抖动共用的总闸4.1 Fixed Timestep 到底在管什么上面已经提到Unity 默认 0.02 秒执行一次物理模拟。这个值决定了物理解算的“帧率”也就是 FixedUpdate 被调用的频率。很多人以为 FixedUpdate 只是和 Update 不同的回调而且只是“固定时间间隔调用”却忽略了它其实就是物理解算的节拍器。如果 Fixed Timestep 缩短物理计算会变得更加频繁。假设固定时间步长从 0.02 秒降到 0.01 秒一个以 50 米/秒飞行的子弹每个物理步长移动的距离就从 1 米降为 0.5 米。穿透变少碰撞检测失败的概率也随之降低。反过来如果某个场景因为物理性能吃紧你把 Fixed Timestep 从 0.02 调大到了 0.03那么同等速度下物体每个物理帧移动得更远穿透概率就会增大。4.2 调小步长的代价不只是性能把 Fixed Timestep 调小确实能直观地改善穿透但代价不是免费的。更小的步长意味着每秒更多的物理计算次数单位时间里 CPU 的物理开销成比例上升。比如从 0.02 调到 0.015物理计算量提升了大约 33%。在移动平台上这可能直接吃掉很多帧预算导致游戏整体帧率下降。更麻烦的是Fixed Timestep 调小后物体的默认最大速度、求解器迭代等参数感知上会变“迟钝”有时需要同步调整刚体的速度相关参数才能获得一致的手感。我自己的习惯是尽量不靠调小 Fixed Timestep 来解决穿透。只有当一个场景里大量高速物体同时存在开 CCD 已经让性能不堪重负时才会考虑将步长从 0.02 降到 0.016 或 0.012并且针对这种设置重新跑一遍所有物理相关测试。4.3 从时间步长角度反向设计物理参数如果你已经决定用某个 Fixed Timestep那么在配置物体速度、碰撞体厚度时就应该把步长考虑进来。一个简单的检查公式最大安全移动距离 最小碰撞体厚度。如果你设定了某个物体的最大速度是 20 米/秒使用默认步长 0.02 秒那它每帧移动 0.4 米。这就需要你所有会被它撞到的碰撞体厚度大于 0.4 米才比较稳妥。如果厚度不足要么限制速度要么把碰撞体做大要么改用 CCD。这条“步长 × 速度”的推算思路在关卡设计和物理交互设计阶段非常有用。不要等问题出现之后再狂调参数而是先定好物理边界再配置角色和子弹的速度能省掉大量排查时间。5. 一次完整排查复盘高速子弹穿墙继而后坐力抖动的解决过程5.1 现象描述与初始判断我在一个非对称射击项目中遇到了典型的穿透加抖动问题玩家发射一颗速度约 45 米/秒的子弹目标是场景中的一面厚度 0.08 米的薄墙。子弹默认使用 Discrete 检测结果有大约 30% 概率直接穿过墙面。为了修穿透我把子弹的 Collision Detection 改成 Continuous穿透确实消失了但紧接着出现了新的问题——子弹命中墙面以后没有干脆地停下而是在墙面附近来回抖动有时过了一两秒才彻底静止甚至偶尔会被轻微弹回。5.2 逐步排查的完整链路我第一步怀疑的是物理材质。当时子弹和墙面都用的默认材质弹力为 0按理说不应该出现反弹。但我把子弹命中时打印的碰撞信息拿出来看发现子弹在接触墙面的瞬间速度方向被翻转了一部分这说明产生了不小的反弹速度。我又检查子弹的 Physic Material发现它虽然弹力为 0但使用了默认的 combine 方式Average当子弹材质与另一个材质的弹力产生混合时默认材质并没有完全抵消掉弹力。于是我把子弹材质改成弹力为 0、Combine Mode 改为 Minimum这样无论和什么材质混合弹力都会被压到最低。第二步我去检查 Rigidbody 的 Max Depenetration Velocity。穿透修复后子弹在接触瞬间其实被墙“推”开了一小段距离。由于默认的脱离穿透速度上限较低子弹被推开的初始速度不够导致它陷入“被压进墙 → 慢慢被推出来 → 弹簧一样来回”的状态。我给这颗子弹的 Rigidbody 单独设了一个更高的 Max Depenetration Velocity抖动幅度明显下降但还没完全消失。第三步我看求解器迭代次数。子弹高速撞墙是一个瞬间大冲击默认的 4 组迭代在高速穿深比较大的情况下解算质量确实不够。我把全局 Solver Iterations 从 4 调到 6同时把子弹的 Rigidbody 里“Solver Iterations”也做了单独覆盖设置成 8。经过这三步调整子弹命中后基本稳定在墙面上不再抖动。5.3 修复方案与验证结果最终保留下来的配置是子弹 Rigidbody 的 Collision Detection 保持 Continuous。子弹使用自定义物理材质动态摩擦力 0.8、静态摩擦力 1.0、弹力 0、Combine Mode 设为 Minimum。子弹 Rigidbody 单独覆盖 Max Depenetration Velocity 为 10。子弹 Rigidbody 单独覆盖 Solver Iterations 为 8。固定时间步长保持默认 0.02不额外调小。经过这个组合调整我连续测试了 200 多次射击子弹穿透率降到 0命中后也无明显抖动。关键点在于我意识到穿透修复不是终点而是物理接触参数调整的起点。当你把一个高速物体从一个“永远不会碰撞”的状态改成“终于会碰撞”的状态它和场景之间瞬间多了大量接触事件如果这些接触事件的解算质量不够新问题自然就会冒出来。6. 踩过多次坑之后留下的避坑清单6.1 写代码时的几个物理铁律第一条不要通过修改 transform 来控制一个带有 Rigidbody 的物体。哪怕你只是想在撞击后把物体挪开一点也尽量用 rb.MovePosition 或者直接修改 velocity避免 transform 和物理引擎在位置归属上互相打架。这个问题在很多人做“拖动物体”功能时最容易触发表现就是物体疯狂抖动。第二条力的施加要和物理节拍对齐。AddForce、AddTorque、改变 velocity 的代码尽量放在 FixedUpdate 中。放到 Update 里的话力的施加频率会绑定渲染帧率在 60Hz 渲染下和 50Hz 物理节拍下会产生数据错位轻则抖动重则物体飞行轨迹完全不对。第三条高速物体的碰撞体要尽量简化。不要给高速物体挂一个高精度 Mesh Collider然后指望它跟另一个动态物体做 Continuous Dynamic 碰撞。最好用封装好的凸包碰撞体比如 Box、Sphere、Capsule它们在做扫掠计算时的稳定性远高于复杂网格。6.2 常用参数配置速查表场景建议做法注意点高速物体撞击静态场景开启 Continuous配合较厚碰撞体薄墙仍需加物理厚度或者换 CDD高速物体撞击动态物体至少一方使用 Continuous Dynamic 或 Speculative性能开销高移动端谨慎多刚体堆叠提高 Solver Iterations 到 6-8材质零弹力摩擦适当不要用高弹力材质物体抖动停不下来检查 Update/FixedUpdate 混用、Interpolation 设置优先修正代码逻辑穿透修复后墙面弹飞调低弹力、提高 Max Depenetration Velocity两者需要配合测试6.3 排查穿透和抖动问题的标准思路当你再次碰到穿透或者抖动我建议不要直接钻进参数里乱调而是先按顺序回答四个问题第一个问题这个物体是否真的需要 Rigidbody很多“抖动”其实是静态物体和动态物体之间互相干扰导致的如果你只是想让一个物体被子弹推走完全可以把它设置为 Kinematic然后脚本控制位移。第二个问题碰撞体的尺寸是否和视觉表现匹配不少穿透发生在视觉模型和碰撞体差异过大的情况下比如子弹视觉很长、碰撞体却是个小圆球看起来应该撞到了物理上却根本没接触。第三个问题物理步长下的移动距离是否越过碰撞体厚度用前面提到的“速度 × 0.02”公式算一下如果越界优先考虑开 CCD 或者改碰撞体厚度而不是盲目降速。第四个问题接触发生后解算参数是否支撑得住如果穿透已经修复但出现抖动就去检查弹力、摩擦、Solver Iterations、Max Depenetration Velocity 这几个值按照子弹案例的思路逐个调整。把这几个问题过完大部分穿透和抖动问题都能找到根因。特别值得一提的是物理材质和求解器参数是很多人最后才想起去动的地方但它们往往是修复“穿透修复后遗症”的关键。我遇到很多开发者在论坛上问“为什么开了 CCD 之后物体反而开始抽风”答案基本上都指向这两块。最后再分享一个小技巧调试这类问题的时候建议在做实验的同时把 Time.timeScale 降低到 0.2 左右用慢动作观察物体接触瞬间的速度变化和位置偏移很多在正常速度下看不清的穿透前兆和抖动源头慢放状态下都会暴露得非常清楚。等你调好参数再把 timeScale 恢复原状。这个办法成本极低却能在排查过程中省掉大量反复试错的次数。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →