应使用Bullet等成熟物理库而非手写碰撞算法,关键在于正确调用API:确保performDiscreteCollisionDetection、合理配置CCD参数、设置collision filtering组与掩码,并在contactTest前完成刚体注册与标志配置。

物理碰撞检测不是从零手写,而是用成熟库封装逻辑
直接用 C++ 手写 GJK、SAT 或连续碰撞检测(CCD)不仅工作量巨大,而且极易出错——哪怕只是浮点误差处理不当,就会导致穿模或卡死。工业级引擎(Unity、Unreal)底层也依赖 Bullet、PhysX 或 Havok 这类经过十年以上验证的物理库。你真正要做的,是把它们的 C++ API 用对,而不是重造轮子。
常见错误现象:Bullet 中 btCollisionWorld::contactTest 返回空,但物体明明相交——大概率是没调用 btCollisionWorld::performDiscreteCollisionDetection(),或者刚体未加入世界;PhysX 的 PxScene::collide() 不触发回调,常因未设置 PxFilterShader 或遗漏 PxSceneFlag::eENABLE_CCD。
- 先确认你是否真需要自研:仅做简单 AABB 检测?用
std::abs(a.min.x - b.max.x) 就够;要做角色与斜坡交互、布料撕裂?必须上Bullet或PhysX -
Bullet更适合学习和轻量集成(纯 C++、无闭源依赖),PhysX在复杂关节和性能上有优势,但需遵守 NVIDIA 许可条款 - 所有库都要求:刚体必须有非零质量(
btRigidBodyConstructionInfo中mass > 0)才参与动力学碰撞;静态物体设为mass = 0,但必须显式调用setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT)
如何用 Bullet 实现一次可靠的手动碰撞查询
很多教程只讲“添加刚体→步进模拟”,但游戏里常需在帧内主动查“这个射线/球体/盒子是否碰到了某物体”,这时不能等 stepSimulation,得用 btCollisionWorld 的离散检测接口。
关键步骤:
- 确保
btCollisionWorld已初始化,并持有你关心的btCollisionObject*(比如场景中的障碍物) - 构造临时检测对象:用
btSphereShape(0.1f)表示子弹,btBoxShape(btVector3(1,1,1))表示玩家碰撞体 - 调用
world->contactTest(&testObject, contactResultCallback),其中contactResultCallback必须继承btCollisionWorld::ContactResultCallback并重写addSingleResult - 注意:该调用不修改世界状态,也不产生接触约束,仅返回当前几何位置下的穿透信息;若需物理响应(如反弹),仍要走完整 simulation 流程
典型坑:btCollisionWorld::contactTest 默认忽略 CF_NO_CONTACT_RESPONSE 标志的对象;若你的障碍物被标记为不可响应,结果永远为空。
刚体运动时漏碰撞?必须开 CCD 并控制时间步长
高速移动的小物体(如子弹、刀光)在单帧内跨越多个物体,标准离散检测会直接跳过——这就是“隧道效应”。解决它不是靠提高帧率,而是启用连续碰撞检测(CCD)。
在 Bullet 中:
- 给高速刚体设置
rigidBody->setCcdMotionThreshold(0.001f)(位移阈值)和rigidBody->setCcdSweptSphereRadius(0.05f)(包裹球半径) - 这两个值必须合理:太小导致性能暴跌(每帧做大量扫掠计算),太大则 CCD 不触发;建议子弹用
0.005f / 0.01f,角色用0.02f / 0.1f -
PhysX对应的是rigidBody->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true)和rigidBody->setCcdAdvanceCoefficient(0.5f) - CCD 仅对 dynamic 刚体有效;kinematic 刚体需手动插值 + 多次
contactTest模拟
一个易忽略点:CCD 不保证 100% 防止隧道,尤其当物体速度超过 swept sphere radius × simulation substeps 时仍可能失效——此时只能降低速度或增大包裹球。
自定义碰撞过滤:避免子弹打穿队友或 UI 元素
默认情况下,Bullet 把所有刚体两两配对检测,效率低且逻辑混乱。你需要用碰撞过滤(collision filtering)精准控制哪些物体能互撞。
核心是 btBroadphaseProxy 的 m_collisionFilterGroup 和 m_collisionFilterMask:
- 定义组:用 bit 表示,如
const short FILTER_PLAYER = 1 ,FILTER_ENEMY = 1 ,FILTER_BULLET = 1 - 设置过滤:对玩家刚体,设
group = FILTER_PLAYER,mask = FILTER_ENEMY | FILTER_BULLET(只响应敌人和子弹) - 对子弹刚体,设
group = FILTER_BULLET,mask = FILTER_PLAYER | FILTER_ENEMY | FILTER_WORLD - 务必在创建
btCollisionObject后、加入 world 前完成设置,否则无效
别用 btCollisionWorld::getPairCache()->setInternalGhostPairCallback 来绕过过滤——那是为 ghost object 设计的,滥用会导致接触点丢失或内存泄漏。
最麻烦的其实是 UI 碰撞体:它们通常用 kinematic 刚体+极小质量模拟,但若忘记在 mask 中排除 FILTER_UI,鼠标点击就可能误触发物理反馈。这种细节,调试时很难一眼看出。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











