You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中分离轴定理(SAT)在世界轴附近碰撞检测失效问题排查

问题根源与修复方案

你的问题主要来自浮点数精度处理不当和冗余/误差轴的引入,以下是具体问题和修复步骤:

1. 浮点数直接比较是否为零

直接用axis == Vector3.zero或axis != Vector3.zero判断向量是否为零是浮点数编程的典型错误——由于精度限制,几乎不会有完全等于零的浮点向量,这会导致无效轴被加入测试,或者有效轴被错误跳过。

2. 微小叉积轴的归一化误差

当OBB轴与AABB轴接近平行时,叉积结果是模长极小的向量,直接归一化会引入巨大的数值误差,这类轴属于冗余轴(因为两个包围盒在平行轴上的投影已经被OBB/AABB自身的轴测试覆盖),应该直接跳过。

3. 区间重叠判断的精度容错

直接用严格小于判断区间是否分离,会因为浮点数计算的微小误差(尤其是顶点靠近世界原点时),导致本该重叠的区间被误判为分离。

4. 顶点投影的累积误差(可选优化)

遍历8个顶点计算投影极值会引入多次浮点运算的累积误差,改用中心+半长的方式计算投影区间,不仅效率更高,精度也更稳定。


修改后的完整代码

public bool IntersectAABB(AABBObstacle aabb)
{
    // 替换为你的OBB世界中心、半长和旋转数据
    Vector3 obbCenter = this.center;
    Vector3 obbExtents = this.extents;
    Quaternion obbRotation = this.rotation;

    // 替换为AABB的世界中心和半长
    Vector3 aabbCenter = aabb.center;
    Vector3 aabbExtents = aabb.extents;

    // 获取包围盒的轴
    Vector3[] aabbAxes = new Vector3[] { Vector3.right, Vector3.up, Vector3.forward };
    Vector3[] obbAxes = new Vector3[]
    {
        obbRotation * Vector3.right,
        obbRotation * Vector3.up,
        obbRotation * Vector3.forward
    };

    List<Vector3> axesToTest = new List<Vector3>();
    axesToTest.AddRange(aabbAxes);
    axesToTest.AddRange(obbAxes);

    // 生成叉积轴,过滤微小向量避免误差
    const float smallVectorThreshold = 1e-12f;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Vector3 axis = Vector3.Cross(obbAxes[i], aabbAxes[j]);
            float sqrMag = axis.sqrMagnitude;
            if (sqrMag > smallVectorThreshold)
            {
                // 手动归一化,避免normalized方法的额外开销
                axesToTest.Add(axis / Mathf.Sqrt(sqrMag));
            }
        }
    }

    // 测试所有分离轴
    foreach (Vector3 axis in axesToTest)
    {
        if (IsSeparatingAxis(axis, obbCenter, obbExtents, obbAxes, aabbCenter, aabbExtents))
        {
            return false;
        }
    }

    return true;
}

private bool IsSeparatingAxis(Vector3 axis, 
                              Vector3 obbCenter, Vector3 obbExtents, Vector3[] obbAxes,
                              Vector3 aabbCenter, Vector3 aabbExtents)
{
    const float smallVectorThreshold = 1e-12f;
    if (axis.sqrMagnitude < smallVectorThreshold)
        return false;

    // 计算OBB在轴上的投影区间(中心+半长方式,无顶点遍历)
    float obbCenterProj = Vector3.Dot(obbCenter, axis);
    float obbHalfLength = Mathf.Abs(Vector3.Dot(obbAxes[0], axis)) * obbExtents.x +
                          Mathf.Abs(Vector3.Dot(obbAxes[1], axis)) * obbExtents.y +
                          Mathf.Abs(Vector3.Dot(obbAxes[2], axis)) * obbExtents.z;
    float obbMin = obbCenterProj - obbHalfLength;
    float obbMax = obbCenterProj + obbHalfLength;

    // 计算AABB在轴上的投影区间
    float aabbCenterProj = Vector3.Dot(aabbCenter, axis);
    float aabbHalfLength = Mathf.Abs(Vector3.Dot(Vector3.right, axis)) * aabbExtents.x +
                           Mathf.Abs(Vector3.Dot(Vector3.up, axis)) * aabbExtents.y +
                           Mathf.Abs(Vector3.Dot(Vector3.forward, axis)) * aabbExtents.z;
    float aabbMin = aabbCenterProj - aabbHalfLength;
    float aabbMax = aabbCenterProj + aabbHalfLength;

    // 带容差的分离判断,处理浮点数精度误差
    const float tolerance = 1e-6f;
    return obbMax + tolerance < aabbMin || aabbMax + tolerance < obbMin;
}

关键修改说明

  • 替换零向量判断:用sqrMagnitude替代直接等于比较,阈值设为1e-12f(适配单精度浮点数的精度范围)。
  • 过滤微小叉积轴:仅保留模长足够大的叉积轴,避免归一化引入的误差。
  • 中心+半长计算投影:彻底避免顶点遍历的累积误差,尤其在顶点靠近世界原点时效果明显。
  • 容差化区间判断:加入1e-6f的容差,允许微小的计算误差,避免误判分离。

内容的提问来源于stack exchange,提问作者Thibaut Lagoda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:40:10