最近在学习工作中偶然发现一个射线检测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进行相交检测,得出算法耗时输出在控制台上,单位为毫秒:


6902

被折叠的 条评论
为什么被折叠?



