分享一个射线检测AABB包围盒的算法

最近在学习工作中偶然发现一个射线检测AABB包围盒的算法,经过测试,发现与传统的检测算法有着比较好的性能,这里给大家分享一下。原始博文在这里:

A Ray-Box Intersection Algorithm and Efficient Dynamic Voxel Rendering (JCGT)

首先介绍下传统的射线检测包围盒算法,将AABB看作三组平行平面的交集,对每个坐标轴计算射线与一对平行平面的交点参数t,然后通过比较t值确定有效的相交区间。具体步骤包括:计算每个轴向上的t_min和t_max,然后取所有轴向上的最大t_min和最小t_max,如果最大t_min小于等于最小t_max,则表示相交。具体代码如下所示:

    /// <summary>
    /// 检测射线是否与bounds
    /// </summary>
    /// <param name="boundsMin">bounds最小坐标(本地坐标)</param>
    /// <param name="boundsMax">bounds最大坐标(本地坐标)</param>
    /// <param name="rayOrigin">射线起点</param>
    /// <param name="rayDir">射线方向</param>
    /// <param name="distance">入射点到射线起点的距离</param>
    /// <returns></returns>
    public bool CheckBounds_AI( Vector3 boundsMin, Vector3 boundsMax, Vector3 rayOrigin, Vector3 rayDir, out float distance)
    {
        distance = 0;

        // 初始化交点的最近和最远距离
        float tMin = float.MinValue;
        float tMax = float.MaxValue;

        // 分别处理三个轴
        for (int i = 0; i < 3; i++)
        {
            // 处理方向分量为0的情况(射线平行于平面)
            if (Mathf.Approximately(rayDir[i], 0))
            {
                // 如果起点在平面外则不相交
                if (rayOrigin[i] < boundsMin[i] || rayOrigin[i] > boundsMax[i])
                    return false;
            }
            else
            {
                // 计算与两个平面的交点参数
                float t1 = (boundsMin[i] - rayOrigin[i]) / rayDir[i];
                float t2 = (boundsMax[i] - rayOrigin[i]) / rayDir[i];

                // 确保t1是近交点,t2是远交点
                if (t1 > t2) (t1, t2) = (t2, t1);

                // 更新交点范围
                if (t1 > tMin) tMin = t1;
                if (t2 < tMax) tMax = t2;
            }
        }

        // 检查是否相交
        bool intersects = tMin <= tMax && tMax >= 0;

        // 如果相交,取最近的交点距离(考虑射线起点在盒内的情况)
        if (intersects) distance = Mathf.Max(tMin, 0);

        return intersects;
    }

新的算法主要是同时计算三轴的 t_min 和 t_max,消除传统方法中的逐轴顺序处理,将除法转换为乘法(预存方向倒数),并通过掩码操作处理方向为零的情况。将传统的6次除法减小到3次除法,减少了分支判断,故性能有较大提升。整理后的脚本如下:

/// <summary>
/// 检测bounds 
/// 通过进入点和离开点的长度进行判断是否与bounds相交
/// 如果相交或者在bounds内 dstA 肯定小于 dstB
/// </summary>
/// <param name="boundsMin">bounds最小坐标(本地坐标)</param>
/// <param name="boundsMax">bounds最大坐标(本地坐标)</param>
/// <param name="rayOrigin">射线起点</param>
/// <param name="rayDir">射线方向</param>
/// <param name="dstA">射线进入bounds的点 到射线起点的长度</param>
/// <param name="dstB">射线离开bounds的点 到射线起点的长度</param>
/// <returns></returns>
public bool CheckBounds_NVIDIA(Vector3 boundsMin, Vector3 boundsMax, Vector3 rayOrigin, Vector3 rayDir, out float dstA, out float dstB)
{
    rayDir.x = 1 / rayDir.x;
    rayDir.y = 1 / rayDir.y;
    rayDir.z = 1 / rayDir.z;

    Vector3 d1 = boundsMin - rayOrigin;
    d1.x *= rayDir.x;
    d1.y *= rayDir.y;
    d1.z *= rayDir.z;

    Vector3 d2 = boundsMax - rayOrigin;
    d2.x *= rayDir.x;
    d2.y *= rayDir.y;
    d2.z *= rayDir.z;

    Vector3 min = d1;
    if (min.x > d2.x) min.x = d2.x;
    if (min.y > d2.y) min.y = d2.y;
    if (min.z > d2.z) min.z = d2.z;

    Vector3 max = d1;
    if (max.x < d2.x) max.x = d2.x;
    if (max.y < d2.y) max.y = d2.y;
    if (max.z < d2.z) max.z = d2.z;

    //进入bounds点到射线起点的距离
    dstA = Mathf.Max(Mathf.Max(min.x, min.y), min.z);
    //离开bounds点到射线起点的距离
    dstB = Mathf.Min(max.x, Mathf.Min(max.y, max.z));
    //如果有相交的话 dstA 肯定小于 dstB
    return dstA < dstB;
}

为了验证两个算法的性能,在unity中,每种算法执行100w次,用同一射线和同一bounds进行相交检测,得出算法耗时输出在控制台上,单位为毫秒:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值