【从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数据对齐
技术对比表
| 引擎 | 核心算法 | 适用场景 | 性能特点 |
|---|---|---|---|
| Unity | GJK+EPA | 游戏/VR | 硬件加速优化 |
| Bullet | GJK+SAT | 机器人/仿真 | 开源可定制 |
| Havok | Voronoi+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物理系统开始探索游戏物理】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

303

被折叠的 条评论
为什么被折叠?



