技术博客

连续碰撞检测:Godot Physics 与 Jolt 的差异

第 65 / 149 篇 · 2025-08-22 · godot · 物理引擎碰撞检测Jolt

第 2 篇拆了 step 流程,收尾处说过:高速穿墙不是求解器的锅,是离散积分的采样问题。这篇展开——先给精确的判据,再对比两个引擎的解法。你会发现它们的思路几乎相反:一个事后钳位移,一个事前预生成接触。

判据:什么时候会隧穿

离散积分每步把 body 从 $x_t$ 直接挪到 $x_{t+1} = x_t + v\,\Delta t$,中间的轨迹不存在。设物体特征尺寸为 $s$(球的直径、盒子的最小边),穿墙条件是:

$$ \|v\| \, \Delta t \;>\; s + d_{\text{wall}} $$

$v\Delta t$ 一步跨过自身尺寸加墙厚。代入数字:子弹 300 m/s、60Hz 物理帧,一步 5 米——别说墙,一层楼都穿过去了。而求解器只看到 $x_t$(没碰)和 $x_{t+1}$(已经在墙另一侧,AABB 都不交了)这两个合法状态,中间穿墙这件事在数据里根本没发生过。所以所有 CCD 方案的本质都是同一个:让"中间状态"以某种形式出现在求解器视野里。

Godot:扫掠 + 钳制

Godot Physics 提供两档连续碰撞(PhysicsServer3D::CCDMode):

$Bullet.continuous_cd_mode = RigidBody3D.CCD_MODE_CAST_SHAPE  # 或 CAST_RAY

机制:积分前,拿预测位移对 broad phase 做一次扫掠查询,找到沿途最早的接触 fraction $t^*$;把位移钳到 $x_t + t^* \cdot v\Delta t$,法向速度按接触处理(消去或按恢复系数反弹)。CAST_RAY 从质心发一条射线,最便宜但只代表质心轨迹——大物体的边缘照样穿;CAST_SHAPE 对整个形状做扫掠,准但贵,一次扫掠相当于若干次窄相求交。

局限也清楚:只处理平动。旋转造成的扫掠(快速旋转的细长杆)不在覆盖范围内;而且钳制是"截断式"的——$t^*$ 之后这段位移直接丢弃,高速连穿两个薄障碍时只有第一个被处理。

Jolt:speculative contact + linear cast 双轨

Jolt 的默认防线是 speculative contact(推测接触):step 前把每个 body 的 AABB 沿预测位移 $v\Delta t$ 膨胀一个 margin,凡是落在这个"预测包络"内的接触提前生成——尽管物体实际还没碰到。这些接触带着负穿透深度进入求解器,迭代时令法向速度满足"本步结束时不穿透"的约束,等于求解器替你把速度钳到恰好停在接触面上。

好处是它和现有求解管线无缝:不截断位移(本步内多次碰撞都能处理)、天然覆盖连续接触(堆叠的箱子一直是 speculative 的受益者)、不引入新的代码路径。代价是接触数变多——每个高速 body 周围一圈"尚未发生"的接触都占约束位,margin(Jolt 的 mSpeculativeContactDistance)设大了白白增加求解成本。

第二道轨是 linear cast(body_set_motion_quality(LinearCast)):真正的扫掠,用保守推进(conservative advancement)沿路径迭代求最早命中。精度高于 speculative(speculative 对高速+深穿透组合可能欠约束),成本高于 speculative,一般只给子弹、炮弹这种正经高速体开。

第四个变量:物理帧率

提 physics/common/physics_ticks_per_second 到 120/144,$\Delta t$ 减半,$v\Delta t$ 同步减半——很多"需要 CCD"的场景直接退回安全区。成本是物理总耗时翻倍(step 次数翻倍),但物理通常不是帧时间大头时这笔账划算。它和 CCD 不互斥:144Hz + speculative 能覆盖绝大多数弹道。

选型决策

场景 建议
子弹/炮弹类高速小物体 Jolt:LinearCast;Godot Physics:CAST_SHAPE(CAST_RAY 只在物体小、速度快时凑合)
一般刚体偶发穿墙 先提物理帧率到 120,大概率症状消失
堆叠/连锁碰撞不稳 不是 CCD 问题,回去查 solver 迭代数(第 2 篇)
高速粒子穿墙 粒子碰撞是采样式的,思路同 CCD:要么降速度要么提 amount 密度,没有现成开关

一个共通的排查提示:穿墙问题的复现率随帧率变化(60Hz 稳穿、30Hz 必穿、144Hz 不穿),那就是采样问题;与帧率无关的穿透,那是 margin/容差设置问题,别走 CCD 这条路。

离散积分的隧穿问题与三种解法(单步内) 离散 vΔt 跨过厚度,碰撞漏检 扫掠钳制 扫掠求最早命中,位移钳到接触点 speculative 预测包络内预生成接触,求解器限速 Godot:CCD_MODE_CAST_RAY / CAST_SHAPE;Jolt:speculative(默认)+ MotionQuality::LinearCast(高速体)

本文机制描述基于 Godot 4.x servers/physics_3d(CCD 两档为 PhysicsServer3D::CCDMode)与 Jolt 5.x(mSpeculativeContactDistance、EMotionQuality::LinearCast、保守推进)。