【Unity】碰撞检测算法及框架实现
背景
硕士期间研究课题是海洋生物数字孪生,基于各类Boids改进的算法里会有大量的海洋鱼类在三维空间中运动,鱼类之间会有互相感知的过程,同一帧里需要对许多行为进行决策判定,例如同伴鱼、食物、捕食者、栖息地等等。因此打算研究下有什么空间加速算法能够避免暴力迭代,减少开销。既然研究了这个,顺便也把碰撞检测也给弄懂了,想一次性弄的透彻一些,所以基于各个算法实现了一套碰撞检测框架。文章大纲和[1]这篇类似,包含了碰撞检测和空间加速算法。但是对各个算法的原理会简单概括,不对细节进行冗余介绍,但是会对各个算法的优缺点进行总结,并且主要侧重于框架的搭建。
碰撞检测
碰撞体类型定义
所有2D碰撞体继承自Collider2D。
- AABBCollider2D(继承自BoxCollider2D)
- BoxCollider2D
- SphereCollider2D
- PolygonCollider2D
所有3D碰撞体继承自Collider。
- AABBCollider(继承自BoxCollider)
- BoxCollider
- SphereCollider
- MeshCollider
Collider2D和Collider不互相干涉。
各种碰撞检测算法
AABB算法(Axis-Aligned Bounding Box)

OBB/SAT算法(Oriented Bounding Box / Seperating Axis Theorem)
OBB不赘述了,是简化成了四条边的SAT
- 分离轴定理:两个凸多边形之间如果不相交,那么必定存在一条线可以将两者进行分割。这条线称之为分割线(Seperating Line)
- 分离轴定理推论:两个凸多边形之间如果不相交,那么必定存在一条轴,让两多边形落在上面的投影线段不相交。这条轴称之为分离轴(Seperating Axis)
所以只要找到了那一条分离轴,就必定可以判断出两个凸多边形不相交。
但是这样找很难找,遍历的范围是无穷的,所以就有了简化的证明方法,来缩小搜索范围(重要推论)
- 重要推论:两个凸多边形之间如果不相交,那么必定存在一条平行于这两个多边形中的某一条边的分割线,同时有一条垂直于该分割线的分割轴,让两多边形落在上面的投影线段不相交。

其实AABB、OBB、SAT我认为都有个共同点,它们都是:只要寻找到一个轴,落在上面的投影没有交叉(Amin > Bmax || Amax < Bmin),则整体都不会有相交。
GJK算法(Gilbert–Johnson–Keerthi 人名缩写)
定理(基本概念)
- 两个多边形相交 <=> 则它们的闵可夫斯基差集必然包括原点(零向量)
这条定理的原因可以看下面的闵可夫斯基差集的基本概念 - A和B是凸多边形 => A和B的闵可夫斯基差集也一定是凸多边形
闵可夫斯基差集(基本概念)
图形A和图形B中的所有点坐标两两相减,得到的新集合叫闵可夫斯基差集。比如图形A有3个点,图形B有4个点,那得到的结果一共有12个点。

上图中只给了各个顶点相减,但是实际上把A上的所有点和B上的所有点相减所得到的点,一定会被包含在右图形成的凸包当中,证明略去。那么也可以看出来,如果A和B有相交的部分,那它们相减一定等于零向量,也就代表右图的闵可夫斯基差集中一定包含原点。于是问题就转化成了判断凸多边形内是否含某个点,而凸多边形一定可以拆成多个三角形,所以最终问题的最最核心的点就是:
①求三角形内部是否包含原点
②按什么顺序在这个凸多边形中去划三角形,一步步接近原点。
单纯形(Simplex,基本概念)
K维单纯形就是包含K+1个点的凸多面体。二维下的单纯形就是三角形,三维下的单纯形的就是四面体。
Support函数(基本概念)
输入为方向d,输出是该方向上凸多边形的最远顶点的位置。

算法过程
第一个三角形的构建:
(1)第一个点:先随机选一个方向direction(例如朝右或朝两个图形的中心点连成的向量),找到direction方向下图形A最远的点和-direction方向下图形B最远的点,相减得到的向量值作为第一个点的坐标。
(2)第二个点:选取-direction方向(在有的文章视频里,也会选择从第一个点指向原点的方向),找到-direction方向下图形A最远的点和direction方向下图形B最远的点,相减得到的向量值作为第二个点的坐标。
(3)第三个点:选取第一个点和第二个点连线的垂直向量作为新的方向,且这个垂直向量是指向原点在的那一侧的。同样找到这个方向下图形A最远的点和反方向下图形B最远的点,相减得到的向量值作为第三个点的坐标。

原点没有被包含进三角形,则寻找下一个三角形:
如图,判断线段①③和线段②③哪一侧包含了原点。如果在①③的外侧,则往右找新的最远的点重新连三角形,如果在②③的外侧,则往左找新的最远的点连三角形。

每个点在被加入的过程中同时考虑:在当前寻找方向direction下图形A和反方向下图形B的差所在的点,也就是闵可夫斯基差所形成的凸多边形在这个方向下的最远坐标。它是否超过了原点,如果没有超过,直接可以判断无法相交,这也是这个算法中唯一的无相交的结束条件。例如第一个三角形构建过程中的第三步,见下图中分析。

实现效果
相关算法
判断一个点在一条线的顺时针侧(右)还是逆时针侧(左)

判断一个点是否在一个三角形内
面积法

向量法
无论三角形的连线方向是顺逆时针。只要点在三条边的同侧,就代表点在三角形内部。
判断一个图形是凹是凸
定义1:凸多边形是指如果一个多边形的所有边中,任意一条边向两方无限延长成为一直线时,其他各边都在此直线的同旁,那么这个多边形就叫做凸多边形。
定义2:所有的内角都是小于等于180°的角的多边形就是凸多边形。

判断射线是否会穿过一条线段

切割凹多边形为多个凸多边形[5]

碰撞检测算法的切换条件
为了提高效率,在实际运行过程中,根据双方的碰撞体类型,增加了动态的算法切换。加粗代表已实现。
- Sphere:两者都是Sphere时。
- AABB(可被OBB代替):两者都是未发生旋转的Box,或者本身就是限制旋转的AABBBox。
- AABB-圆[3,4]:两者其中一者是未发生旋转的Box,或者本身就是限制旋转的AABBBox,另一者是Sphere。
- OBB(可被SAT代替):两者都是Box,且其中任意一者存在旋转。
- SAT-多边形-多边形:两者都是凸多边形,且两者都不是Sphere。
- SAT-多边形-圆:两者中其中一者是凸多边形,另一者是圆形。
- GJK:两者中其中一者是凸多边形。
- EPA:两者中其中一者是凸多边形。
以上SAT、GJK、EPA三选一 - 圆框与AABB矩形框(矩形框不旋转):其中一者是未发生旋转的Box,或者本身就是限制旋转的AABBBox。另一者是Sphere。
- 分割SAT:两者其中一者是凹多边形,且两者都不是Sphere。
空间划分加速算法
四叉树
框架设计及搭建
参考
[1] https://blog.csdn.net/weixin_42186870/article/details/136147295
[2] https://zhuanlan.zhihu.com/p/177006015
[3] https://www.zhihu.com/question/266499219/answers/updated
[4] https://www.cnblogs.com/zhjblogs/p/16737750.html
[5] https://blog.csdn.net/qq_42883222/article/details/136858455
附加:Unity的碰撞检测介绍
在Unity 5.5之前,Unity使用SAT来进行碰撞检测。之后Unity推出了PCM(持久接触流形,Persistent Contacts Manifold)方法,会更高效,无用的碰撞点信息会更少。
现在的项目默认都是PCM了,但是也可以在Project Settings里修改:

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】