1. 碰撞检测基础
1.1 什么是碰撞检测?
碰撞检测,说白了就是判断两个物体有没有「碰到一起」。
我刚开始做游戏引擎时,觉得这玩意儿很简单——不就是判断两个形状是否重叠吗?后来才发现,这里面的坑比想象中多得多。
举个例子:一颗子弹以每秒1000米的速度飞向墙壁。在某一帧,子弹还在墙的左边;下一帧,它已经穿到墙的右边了。这就是经典的「隧道效应」。如果你只用简单的重叠判断,子弹就「穿模」了。
所以,碰撞检测不仅仅是「有没有碰到」,还包括「什么时候碰到」「在哪里碰到」「碰到的法线方向是什么」。这些信息,后续的碰撞响应机制全都要用。
1.2 为什么它如此重要?
没有碰撞检测的世界是什么样的?
- 游戏里:角色会掉到地板下面,子弹会穿过敌人,汽车会互相「穿模」而过。
- 物理仿真里:刚体会无限重叠,布料会穿透自身,流体粒子会乱飞。
- 机器人领域:机械臂会撞到障碍物,AGV小车会「卡」在墙里。
我记得有一次做机器人仿真项目,碰撞检测没处理好,机械臂直接「穿」过了工作台。幸好是在仿真环境里,要是真机……嗯,那维修费够我吃半年泡面了。
1.3 应用领域一览
| 领域 | 典型场景 | 精度要求 | 实时性要求 |
|---|---|---|---|
| 游戏 | 角色碰撞、子弹命中、物理交互 | 中等(可接受近似) | 极高(60fps以上) |
| 物理仿真 | 刚体破碎、布料模拟、流体交互 | 高 | 中等 |
| 机器人 | 路径规划、避障、抓取 | 极高 | 取决于应用 |
| CAD/CAM | 装配干涉检查、碰撞分析 | 极高 | 低(离线计算) |
你想想看,游戏里角色踩到地面,你不需要精确到微米级的碰撞点;但机器人抓取精密零件,差0.1毫米可能就报废了。这就是不同领域对碰撞检测的「容忍度」差异。
1.4 基本数学工具
做碰撞检测,数学是绕不开的。但别怕,常用的就三个工具:向量、点积、叉积。
1.4.1 向量
向量就是有方向的线段。在碰撞检测里,我们用它来表示位置、速度、法线方向。
// 二维向量
struct Vector2 {
float x, y;
};
// 三维向量
struct Vector3 {
float x, y, z;
};
// 向量减法:计算从A到B的方向
Vector3 direction = B - A;
我个人习惯把向量想象成「箭头」。箭头指向哪,力就往哪使。碰撞法线就是一个垂直于碰撞面的向量,它告诉物体「你应该往哪个方向弹开」。
1.4.2 点积
点积的计算公式很简单:a · b = |a| * |b| * cos(θ)。但在实际项目中,我更常用它的几何意义——判断两个向量的「朝向关系」。
// 点积
float dot(Vector3 a, Vector3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// 判断两个物体是否相向而行
float relativeVelocity = dot(velocityA - velocityB, normal);
if (relativeVelocity < 0) {
// 正在靠近,需要处理碰撞
}
1.4.3 叉积
叉积的结果是一个垂直于两个输入向量的新向量。它的长度等于两个向量围成的平行四边形面积。
// 叉积(三维)
Vector3 cross(Vector3 a, Vector3 b) {
return Vector3(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
// 计算三角形的法线
Vector3 edge1 = B - A;
Vector3 edge2 = C - A;
Vector3 faceNormal = normalize(cross(edge1, edge2));
叉积在碰撞检测里最常用的场景就是计算法线。比如一个三角形面片,用两条边做叉积,就能得到垂直于这个面的法线方向。
- 向量:表示位置、速度、方向
- 点积:判断朝向、计算投影长度
- 叉积:计算法线、计算面积
1.5 知识体系总览
下面这张图,是我做碰撞检测时脑子里始终绷着的一根弦。它把整个知识体系串了起来:
这张图把碰撞检测拆成了四个维度:定义、重要性、应用领域、数学工具。我个人习惯在开始一个新项目前,先对着这张图捋一遍思路——「我要检测什么?精度要求多高?用什么数学工具?」想清楚了再动手,能少走很多弯路。