引言

前置阅读:建议先阅读向量距离与检测

相交检测 (Intersection Test) 是物理引擎的心脏。 它回答了“子弹是否击中敌人?”、“玩家是否站在地面上?”等问题。

1. 射线检测 (Raycasting)

射线由一个起点 \(O\) 和一个方向 \(D\) 定义:\(R(t) = O + D \cdot t\) (\(t \ge 0\))。

射线与球体 (Ray vs Sphere)

球体方程:\(|P - C|^2 = r^2\)。 将射线方程代入球体方程,会得到一个关于 \(t\)一元二次方程\[ at^2 + bt + c = 0 \]

Ray Sphere Intersection
  • 如果 \(\Delta = b^2 - 4ac < 0\): 无解 (未击中)。
  • 如果 \(\Delta = 0\): 一个解 (擦边)。
  • 如果 \(\Delta > 0\): 两个解 (穿过,一个入口 \(t_1\),一个出口 \(t_2\))。

通常我们只关心最小的正解 \(t\),那是最近的击中点。

射线与平面 (Ray vs Plane)

将射线代入平面方程 \(\vec{n} \cdot P + d = 0\)\[ \vec{n} \cdot (O + D \cdot t) + d = 0 \] 解出 \(t\): \[ t = \frac{-(\vec{n} \cdot O + d)}{\vec{n} \cdot D} \]

注意分母 \(\vec{n} \cdot D\): - 如果接近 0,说明射线与平面平行,无交点。 - 如果 > 0,说明射线从背面射出(通常不检测)。

2. AABB 包围盒 (Axis-Aligned Bounding Box)

这是最简单也是最快的碰撞检测形状。 它由两个点定义:\(min(x,y,z)\)\(max(x,y,z)\)。 “轴对齐”意味着盒子不能旋转,永远平行于世界坐标轴。

AABB vs AABB 检测

两个 AABB 相交,当且仅当它们在 X、Y、Z 三个轴上都重叠

bool Intersect(AABB a, AABB b) {
  return (a.min.x <= b.max.x && a.max.x >= b.min.x) &&
  (a.min.y <= b.max.y && a.max.y >= b.min.y) &&
  (a.min.z <= b.max.z && a.max.z >= b.min.z);
}

这是物理引擎中宽阶段 (Broad Phase) 碰撞检测的核心算法,用于快速剔除不可能相撞的物体。

3. 射线与 AABB (Slab Method)

将 AABB 看作是三组平行的平面(X轴一对,Y轴一对,Z轴一对)。 射线必须穿过所有三组“板 (Slab)”的重叠区域才算击中盒子。 这通常涉及计算 \(t_{min}\)\(t_{max}\) 在三个轴上的最大值和最小值。

完整的 Slab Method 伪代码

bool RayIntersectsAABB(Vector3 origin, Vector3 dir, AABB box, out float tMin) {
    float tmin = float.NegativeInfinity;
    float tmax = float.PositiveInfinity;
    
    for (int i = 0; i < 3; i++) {  // X, Y, Z 三个轴
        if (Mathf.Abs(dir[i]) < 1e-6f) {
            // 射线平行于该轴的 slab
            if (origin[i] < box.min[i] || origin[i] > box.max[i])
                { tMin = 0; return false; }
        } else {
            float invD = 1.0f / dir[i];
            float t1 = (box.min[i] - origin[i]) * invD;
            float t2 = (box.max[i] - origin[i]) * invD;
            if (t1 > t2) (t1, t2) = (t2, t1); // 确保 t1 < t2
            tmin = Mathf.Max(tmin, t1);
            tmax = Mathf.Min(tmax, t2);
            if (tmin > tmax) { tMin = 0; return false; }
        }
    }
    tMin = tmin;
    return tmin >= 0;
}

扩展方向:OBB 与胶囊体

AABB 的限制是不能旋转。对于旋转过的物体,可以使用: - OBB (Oriented Bounding Box):带旋转的包围盒,检测需要用分离轴定理 (SAT),比 AABB 复杂但更精确。 - 胶囊体 (Capsule):线段 + 半径,非常适合角色碰撞体。检测实质是”两个线段的最短距离 < 半径之和”。 - 凸包 (Convex Hull):使用 GJK 算法检测两个凸体的相交,是物理引擎窄阶段 (Narrow Phase) 的核心。

4. 点在三角形内 (Point in Triangle)

如何判断一个点 \(P\) 是否在三角形 \(ABC\) 内部? 这需要用到重心坐标 (Barycentric Coordinates)

任意点 \(P\) 都可以表示为三角形三个顶点的加权和: \[ P = uA + vB + wC \] 其中 \(u + v + w = 1\)

如果 \(P\) 在三角形内部,那么必须满足: \[ 0 \le u, v, w \le 1 \]

游戏中的应用

  • 地形高度计算: 玩家站在地形网格上,已知 \(x, z\),如何求 \(y\)?先找到所在的三角形,计算重心坐标,然后用重心坐标插值三个顶点的高度。
  • 纹理映射: 光栅化阶段,GPU 使用重心坐标来插值 UV 坐标和颜色。

总结

  • 射线检测: 用于射击、视线判断、鼠标拾取。
  • AABB: 用于粗略的物理碰撞和视锥体剔除。
  • 球体: 用于简单的范围触发器。
  • 重心坐标: 用于地形高度和插值。

复杂的网格碰撞(Mesh Collider)通常太慢,游戏开发中会尽量用组合的 AABB 或球体来近似。


< 上一篇: 距离与检测 | 回到目录 | 下一篇: 运动学基础 >

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。