【原理】[碰撞检测]中的近点查询ClosestPoint

【从Unity物理系统开始探索游戏物理】专栏-直达

近点查询是碰撞检测中的核心功能,用于计算两个几何对象表面最近点的位置及距离‌。其实现原理通常基于支持映射(Support Mapping)和单纯形(Simplex)算法,通过迭代逼近目标点‌。例如在3D矩形检测中,通过投影到各轴并计算边界约束来定位最近点‌。

支持该功能的物理引擎

  • Unity Physics‌提供Physics.ClosestPoint方法,可计算碰撞体表面与给定位置的最接近点‌,其底层基于NVIDIA PhysX引擎的GJK算法实现‌。
  • Bullet‌开源物理引擎,通过btGjkEpaSolver类实现最近点计算,支持连续碰撞检测(CCD)。
  • ODE‌开放动力学引擎的dCollide函数包含最近点计算模块,适用于刚体动力学场景。
  • Havok‌商用引擎中通过hkcd库提供精确的最近点查询,常用于3A游戏开发‌

技术发展历史

  • 1988年‌:GJK算法首次提出,通过Minkowski和与单纯形迭代实现凸体最近点计算‌。
  • 2000年代‌:物理引擎集成优化算法,如Bullet在2003年加入GJK-EPA混合算法提升精度。
  • 2020年代‌:Unity DOTS Physics采用ECS架构优化查询性能,支持多线程最近点计算‌.

不同物理引擎的ClosestPoint实现对比

Unity Physics (基于PhysX)‌

  • 实现方式‌:通过Physics.ClosestPoint方法调用PhysX的GJK算法,支持凸体与凸体的最近点计算‌。对于非凸体,自动分解为凸体组合处理。

  • 特点‌:

    • 内置对AABB/OBB的优化分支,减少迭代次数
    • 支持连续碰撞检测(CCD)中的最近点预测
    csharp
    // 示例:计算碰撞体表面与点的最近距离
    float distance = Vector3.Distance(point, collider.ClosestPoint(point));
    

Bullet‌

  • 实现方式‌:通过btGjkEpaSolver类实现,采用分离轴定理(SAT)与GJK混合算法‌。对三角形网格等复杂几何体支持Minkowski和计算。

  • 特点‌:

    • 开源实现允许自定义最近点精度阈值
    • 提供btCollisionWorld接口批量查询‌
    cpp
    btGjkEpaSolver solver;
    btVector3 closestPoint;
    solver.getClosestPoints(shapeA, shapeB, transformA, transformB, &closestPoint);
    

Havok‌

  • 实现方式‌:通过hkcd库的hkClosestPoint函数,采用Voronoi区域划分加速查询‌。对建筑级模型优化明显。
  • 特点‌:
    • 支持多线程并行计算最近点
    • 集成ICP算法用于SLAM数据对齐‌

技术对比表

引擎核心算法适用场景性能特点
UnityGJK+EPA游戏/VR硬件加速优化
BulletGJK+SAT机器人/仿真开源可定制
HavokVoronoi+ICP建筑/城市级AR多线程高精度

PhysX和Unity中的具体实现原理

在PhysX和Unity中,ClosestPoint检测的核心实现原理基于GJK(Gilbert-Johnson-Keerthi)算法,通过迭代计算两个凸体之间的最近距离。

PhysX的实现原理

PhysX作为Unity的底层物理引擎,其ClosestPoint检测采用Minkowski差空间计算。算法通过以下步骤实现:

  • 初始化单纯形‌:从任意方向开始,构建包含原点的最小单纯形
  • 迭代搜索‌:在每次迭代中计算支撑点,更新单纯形
  • 收敛判断‌:当单纯形包含原点或距离小于阈值时停止

Unity中的具体实现

Unity通过Collider.ClosestPoint方法封装了PhysX的功能。该方法接收一个空间点作为输入,返回碰撞体表面上距离最近的点。关键特性包括:

  • 基于碰撞体表面计算,而非体积内部
  • 支持所有类型的碰撞体(Box、Sphere、Mesh等)
  • 自动处理凸体分解优化

示例代码解析

csharp
public class ShowClosestPoint : MonoBehaviour {
    public Vector3 location;

    public void OnDrawGizmos() {
        var collider = GetComponent<Collider>();
        if (!collider) return;

        Vector3 closestPoint = collider.ClosestPoint(location);
        Gizmos.DrawSphere(location, 0.1f);
        Gizmos.DrawWireSphere(closestPoint, 0.1f);
    }
}

此示例演示了如何可视化最近点检测1:

  • location表示查询的原始位置
  • closestPoint是碰撞体表面上计算得到的最接近点
  • 通过Gizmo球体直观显示两点位置关系

算法核心数学原理

线段最近点计算展示了GJK的基础组件:

cpp
void x_math_closest_points_on_lines(const vector3& p1, const vector3& d1,
                                   const vector3& p2, const vector3& d2,
                                   f32& s, f32& t, vector3& cp1, vector3& cp2)
{
// 计算两条线段参数化表示下的最近点
    vector3 p21 = p2 - p1;
    f32 denom = d1d1.Dot(d2d2) - d1d2.Dot(d2d1);

    if(x_abs(denom) < 1e-6f) {
// 处理平行线段特殊情况
        s = 0.0f; t = 0.0f;
        cp1 = p1; cp2 = p2;
        return;
    }

    s = (p21.Dot(d1d2) - p21.Dot(d2d1)) / denom;
    t = (p21.Dot(d1d1) - p21.Dot(d1d2)) / denom;

    cp1 = p1 + d1 * s;// 第一条线段上的最近点
    cp2 = p2 + d2 * t;// 第二条线段上的最近点
}

该函数通过参数化方法精确计算两条线段间的最接近点,是GJK算法中单纯形更新的基础


【从Unity物理系统开始探索游戏物理】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淡海水

感谢支持 共同进步 好运++

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值