最有效的AABB与Ray碰撞算法


53

是否有已知的“最有效”算法用于AABB与射线碰撞检测?

我最近偶然发现Arvo的AABB vs Sphere碰撞算法,我想知道是否有类似的算法值得关注。

此算法必须满足的条件是,我需要选择查询结果以了解从射线的原点到碰撞点的距离。话虽如此,如果还有另一个更快的算法不返回距离,那么除了发布一个可以返回距离的算法之外,发布该算法的确非常有用。

还请说明函数的return参数是什么,以及如何使用它返回距离或“无冲突”情况。例如,它是否具有用于距离的out参数以及bool返回值?还是只是返回一个带距离的浮点数,而不是-1的无碰撞?

(对于那些不知道的人:AABB =轴对齐的边界框)


我可能是错的,但我认为您仍然可以使用此算法得到误报。正确的是,如果检查3轴时所有角都在同一侧,则不会发生碰撞。但是看来您仍然可以满足以下条件:所有3个轴的两边都有点,并且仍然没有碰撞。我通常检查以确定所有三个平板上的进/出距离是否重叠。来自几何工具网站。
史蒂夫·H

为什么距离查询必须具备条件?如果在不需要距离的情况下,有一种甚至更快的算法,您是否也不想知道?
sam hocevar,2011年

好吧,不,不是真的。我需要知道碰撞发生的距离。
SirYakalot,2011年

实际上我想你是对的,我将编辑问题。
SirYakalot

4
正如我在您的其他主题中所发表的那样,这里有很多关于这些类型的算法的资源:realtimerendering.com/intersections.html
Tetrad

Answers:


22

Andrew Woo与John Amanatides一起开发了在光线追踪器中普遍使用的光线行进算法(DDA),他撰写了“快速光线盒相交”此处另作参考),该出版物发表在Graphics Gems,1990年,第395-396页。该算法不是专门为通过DDA而通过网格(例如,体素体积)进行集成而构建的(请参见zacharmarz的答案),而是特别适合于未均匀细分的世界,例如大多数3D中发现的典型多面体世界游戏。

该方法提供了对3D的支持,并且可以选择进行背面剔除。该算法源自DDA中使用的集成原理,因此非常快。可以在原始的Graphics Gems卷(1990)中找到更多详细信息。

可以在realtimerendering.com上找到许多其他专门针对Ray-AABB的方法。

编辑:可以在这里找到一种替代的,无分支的方法-在GPU和CPU上都将是理想的。


啊!你打败我了,今天早上我才碰到它。很棒的发现!
SirYakalot '12

主席先生,很高兴。我还建议比较在这种基础上找到的所有算法。(其他地方还有更多这样的官方清单,但现在找不到。)
工程师

这篇文章在这里
bobobobo 2012年

1
这里可以找到Woo算法的一个经过充分评论的实现。
工程师

4
您提供的两个链接分别生成“未找到”和“禁止”错误...
liggiorgio

46

我以前在raytracer中使用的是什么:

// r.dir is unit direction vector of ray
dirfrac.x = 1.0f / r.dir.x;
dirfrac.y = 1.0f / r.dir.y;
dirfrac.z = 1.0f / r.dir.z;
// lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
// r.org is origin of ray
float t1 = (lb.x - r.org.x)*dirfrac.x;
float t2 = (rt.x - r.org.x)*dirfrac.x;
float t3 = (lb.y - r.org.y)*dirfrac.y;
float t4 = (rt.y - r.org.y)*dirfrac.y;
float t5 = (lb.z - r.org.z)*dirfrac.z;
float t6 = (rt.z - r.org.z)*dirfrac.z;

float tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
float tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));

// if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us
if (tmax < 0)
{
    t = tmax;
    return false;
}

// if tmin > tmax, ray doesn't intersect AABB
if (tmin > tmax)
{
    t = tmax;
    return false;
}

t = tmin;
return true;

如果返回true,则相交;如果返回false,则不相交。

如果多次使用同一条射线,则可以进行预计算dirfrac(仅在整个相交测试中进行除法)。然后它真的很快。而且,您还有到交点为止的光线长度(存储在中t)。


能否为您的变量名提供一个密钥?
SirYakalot

1
我试图在评论中添加一些解释。因此:“ r”是射线,“ r.dir”是其单位方向向量,“ r.org”是从中发出射线的原点,“ dirfrac”只是一种优化,因为您可以始终将其用于同一条射线(您不必进行除法),它表示1 / r.dir。然后,“ lb”是AABB的拐角,所有3个坐标均为最小,而“ rb”是相对的-拐角为最大坐标。输出参数“ t”是矢量从原点到交点的长度。
zacharmarz 2011年

函数定义是什么样的?是否可以找出碰撞发生在射线上的距离?
SirYakalot

1
那么您的算法返回交集但交集为负数时意味着什么?tmin有时以负数形式返回。
SirYakalot '12

1
啊,那是原点在盒子里面的时候
SirYakalot 2011年

14

这里没有人描述该算法,但是Graphics Gems算法很简单:

  1. 使用射线的方向向量,确定将首先命中 6个候选平面中的3个。如果您的(未归一化的)射线方向向量为(-1、1,-1),则可能会命中的3个平面为+ x,-y和+ z。

  2. 在3个候选平面中,确实找到每个相交的t值。将获得最大 t值的飞机作为被击中的飞机,并检查该击中是否在方框内。文本中的图清楚地表明了这一点:

在此处输入图片说明

我的实现:

bool AABB::intersects( const Ray& ray )
{
  // EZ cases: if the ray starts inside the box, or ends inside
  // the box, then it definitely hits the box.
  // I'm using this code for ray tracing with an octree,
  // so I needed rays that start and end within an
  // octree node to COUNT as hits.
  // You could modify this test to (ray starts inside and ends outside)
  // to qualify as a hit if you wanted to NOT count totally internal rays
  if( containsIn( ray.startPos ) || containsIn( ray.getEndPoint() ) )
    return true ; 

  // the algorithm says, find 3 t's,
  Vector t ;

  // LARGEST t is the only one we need to test if it's on the face.
  for( int i = 0 ; i < 3 ; i++ )
  {
    if( ray.direction.e[i] > 0 ) // CULL BACK FACE
      t.e[i] = ( min.e[i] - ray.startPos.e[i] ) / ray.direction.e[i] ;
    else
      t.e[i] = ( max.e[i] - ray.startPos.e[i] ) / ray.direction.e[i] ;
  }

  int mi = t.maxIndex() ;
  if( BetweenIn( t.e[mi], 0, ray.length ) )
  {
    Vector pt = ray.at( t.e[mi] ) ;

    // check it's in the box in other 2 dimensions
    int o1 = ( mi + 1 ) % 3 ; // i=0: o1=1, o2=2, i=1: o1=2,o2=0 etc.
    int o2 = ( mi + 2 ) % 3 ;

    return BetweenIn( pt.e[o1], min.e[o1], max.e[o1] ) &&
           BetweenIn( pt.e[o2], min.e[o2], max.e[o2] ) ;
  }

  return false ; // the ray did not hit the box.
}

+1进行实际解释(还有图片:)
legends2k 2014年

4

这是我一直在使用的3D ray / AABox交集:

bool intersectRayAABox2(const Ray &ray, const Box &box, int& tnear, int& tfar)
{
    Vector3d T_1, T_2; // vectors to hold the T-values for every direction
    double t_near = -DBL_MAX; // maximums defined in float.h
    double t_far = DBL_MAX;

    for (int i = 0; i < 3; i++){ //we test slabs in every direction
        if (ray.direction[i] == 0){ // ray parallel to planes in this direction
            if ((ray.origin[i] < box.min[i]) || (ray.origin[i] > box.max[i])) {
                return false; // parallel AND outside box : no intersection possible
            }
        } else { // ray not parallel to planes in this direction
            T_1[i] = (box.min[i] - ray.origin[i]) / ray.direction[i];
            T_2[i] = (box.max[i] - ray.origin[i]) / ray.direction[i];

            if(T_1[i] > T_2[i]){ // we want T_1 to hold values for intersection with near plane
                swap(T_1,T_2);
            }
            if (T_1[i] > t_near){
                t_near = T_1[i];
            }
            if (T_2[i] < t_far){
                t_far = T_2[i];
            }
            if( (t_near > t_far) || (t_far < 0) ){
                return false;
            }
        }
    }
    tnear = t_near; tfar = t_far; // put return values in place
    return true; // if we made it here, there was an intersection - YAY
}

什么是tneartfar
tekknolagi 2015年

交点在[tear,tfar]之间。
Jeroen Baert 2015年

3

这是我用于GPU的上述优化版本:

__device__ float rayBoxIntersect ( float3 rpos, float3 rdir, float3 vmin, float3 vmax )
{
   float t[10];
   t[1] = (vmin.x - rpos.x)/rdir.x;
   t[2] = (vmax.x - rpos.x)/rdir.x;
   t[3] = (vmin.y - rpos.y)/rdir.y;
   t[4] = (vmax.y - rpos.y)/rdir.y;
   t[5] = (vmin.z - rpos.z)/rdir.z;
   t[6] = (vmax.z - rpos.z)/rdir.z;
   t[7] = fmax(fmax(fmin(t[1], t[2]), fmin(t[3], t[4])), fmin(t[5], t[6]));
   t[8] = fmin(fmin(fmax(t[1], t[2]), fmax(t[3], t[4])), fmax(t[5], t[6]));
   t[9] = (t[8] < 0 || t[7] > t[8]) ? NOHIT : t[7];
   return t[9];
}

这个转换团结的使用,并且它比内置bounds.IntersectRay更快gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

如何解释返回值?它像原点和交点之间的欧几里得距离吗?
FerdinandMütsch

到盒子的距离是多少?
jjxtra

1

您可能要研究的一件事是在两个单独的缓冲区中栅格化边界框的正面和背面。将x,y,z值渲染为rgb(这最适合于边界框在(0,0,0)处有一个角而在(1,1,1)处具有相反角的边界框。

显然,此方法用途有限,但我发现它非常适合渲染简单的体积。

有关更多详细信息和代码:

http://www.daimi.au.dk/~trier/?page_id=98


1

这是我一直在使用的Line vs AABB代码:

namespace {
    //Helper function for Line/AABB test.  Tests collision on a single dimension
    //Param:    Start of line, Direction/length of line,
    //          Min value of AABB on plane, Max value of AABB on plane
    //          Enter and Exit "timestamps" of intersection (OUT)
    //Return:   True if there is overlap between Line and AABB, False otherwise
    //Note:     Enter and Exit are used for calculations and are only updated in case of intersection
    bool Line_AABB_1d(float start, float dir, float min, float max, float& enter, float& exit)
    {
        //If the line segment is more of a point, just check if it's within the segment
        if(fabs(dir) < 1.0E-8)
            return (start >= min && start <= max);

        //Find if the lines overlap
        float   ooDir = 1.0f / dir;
        float   t0 = (min - start) * ooDir;
        float   t1 = (max - start) * ooDir;

        //Make sure t0 is the "first" of the intersections
        if(t0 > t1)
            Math::Swap(t0, t1);

        //Check if intervals are disjoint
        if(t0 > exit || t1 < enter)
            return false;

        //Reduce interval based on intersection
        if(t0 > enter)
            enter = t0;
        if(t1 < exit)
            exit = t1;

        return true;
    }
}

//Check collision between a line segment and an AABB
//Param:    Start point of line segement, End point of line segment,
//          One corner of AABB, opposite corner of AABB,
//          Location where line hits the AABB (OUT)
//Return:   True if a collision occurs, False otherwise
//Note:     If no collision occurs, OUT param is not reassigned and is not considered useable
bool CollisionDetection::Line_AABB(const Vector3D& s, const Vector3D& e, const Vector3D& min, const Vector3D& max, Vector3D& hitPoint)
{
    float       enter = 0.0f;
    float       exit = 1.0f;
    Vector3D    dir = e - s;

    //Check each dimension of Line/AABB for intersection
    if(!Line_AABB_1d(s.x, dir.x, min.x, max.x, enter, exit))
        return false;
    if(!Line_AABB_1d(s.y, dir.y, min.y, max.y, enter, exit))
        return false;
    if(!Line_AABB_1d(s.z, dir.z, min.z, max.z, enter, exit))
        return false;

    //If there is intersection on all dimensions, report that point
    hitPoint = s + dir * enter;
    return true;
}

0

这似乎类似于zacharmarz发布的代码。
我从克里斯特·埃里克森(Christer Ericson)的书《实时碰撞检测》(Real-Time Collision Detection)中的“ 5.3.3将射线或线段与盒子相交”下获得了此代码。

// Where your AABB is defined by left, right, top, bottom

// The direction of the ray
var dx:Number = point2.x - point1.x;
var dy:Number = point2.y - point1.y;

var min:Number = 0;
var max:Number = 1;

var t0:Number;
var t1:Number;

// Left and right sides.
// - If the line is parallel to the y axis.
if(dx == 0){
    if(point1.x < left || point1.x > right) return false;
}
// - Make sure t0 holds the smaller value by checking the direction of the line.
else{
    if(dx > 0){
        t0 = (left - point1.x)/dx;
        t1 = (right - point1.x)/dx;
    }
    else{
        t1 = (left - point1.x)/dx;
        t0 = (right - point1.x)/dx;
    }

    if(t0 > min) min = t0;
    if(t1 < max) max = t1;
    if(min > max || max < 0) return false;
}

// The top and bottom side.
// - If the line is parallel to the x axis.
if(dy == 0){
    if(point1.y < top || point1.y > bottom) return false;
}
// - Make sure t0 holds the smaller value by checking the direction of the line.
else{
    if(dy > 0){
        t0 = (top - point1.y)/dy;
        t1 = (bottom - point1.y)/dy;
    }
    else{
        t1 = (top - point1.y)/dy;
        t0 = (bottom - point1.y)/dy;
    }

    if(t0 > min) min = t0;
    if(t1 < max) max = t1;
    if(min > max || max < 0) return false;
}

// The point of intersection
ix = point1.x + dx * min;
iy = point1.y + dy * min;
return true;

这是2d,是吗?
SirYakalot,2011年

这只是2D,是的。而且,代码的想法不像zacharmarz的那样好,因为它需要减少划分和测试的次数。
sam hocevar 2011年

0

我很惊讶地看到没有人提到塔维安的无分支平板法

bool intersection(box b, ray r) {
    double tx1 = (b.min.x - r.x0.x)*r.n_inv.x;
    double tx2 = (b.max.x - r.x0.x)*r.n_inv.x;

    double tmin = min(tx1, tx2);
    double tmax = max(tx1, tx2);

    double ty1 = (b.min.y - r.x0.y)*r.n_inv.y;
    double ty2 = (b.max.y - r.x0.y)*r.n_inv.y;

    tmin = max(tmin, min(ty1, ty2));
    tmax = min(tmax, max(ty1, ty2));

    return tmax >= tmin;
}

完整说明:https : //tavianator.com/fast-branchless-raybounding-box-intersections/


0

当射线原点位于AABB内时,我已经在@zacharmarz答案中添加了处理方法。在这种情况下,tmin为负且在射线之后,因此tmax是射线与AABB之间的第一个交点。

// r.dir is unit direction vector of ray
dirfrac.x = 1.0f / r.dir.x;
dirfrac.y = 1.0f / r.dir.y;
dirfrac.z = 1.0f / r.dir.z;
// lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
// r.org is origin of ray
float t1 = (lb.x - r.org.x)*dirfrac.x;
float t2 = (rt.x - r.org.x)*dirfrac.x;
float t3 = (lb.y - r.org.y)*dirfrac.y;
float t4 = (rt.y - r.org.y)*dirfrac.y;
float t5 = (lb.z - r.org.z)*dirfrac.z;
float t6 = (rt.z - r.org.z)*dirfrac.z;

float tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6));
float tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6));

// if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us
if (tmax < 0)
{
    t = tmax;
    return false;
}

// if tmin > tmax, ray doesn't intersect AABB
if (tmin > tmax)
{
    t = tmax;
    return false;
}

// if tmin < 0 then the ray origin is inside of the AABB and tmin is behind the start of the ray so tmax is the first intersection
if(tmin < 0) {
  t = tmax;
} else {
  t = tmin;
}
return true;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.