가장 효율적인 AABB 대 광선 충돌 알고리즘


53

AABB 대 광선 충돌 탐지에 대해 알려진 '가장 효율적인'알고리즘이 있습니까?

나는 최근 Arvo의 AABB 대 Sphere 충돌 알고리즘을 우연히 발견했으며 이와 비슷한 주목할만한 알고리즘이 있는지 궁금합니다.

이 알고리즘의 조건 중 하나는 광선 원점에서 충돌 지점까지의 거리에 대한 결과를 쿼리하는 옵션이 필요하다는 것입니다. 거리를 반환하지 않는 또 다른 더 빠른 알고리즘이있는 경우 이것을 게시하는 것 외에도 해당 알고리즘을 게시하면 실제로 도움이 될 것입니다.

함수의 반환 인수가 무엇인지, 그리고 거리 또는 '충돌이없는'경우를 반환하는 방법을 설명하십시오. 예를 들어, 거리에 대한 출력 매개 변수와 부울 리턴 값이 있습니까? 또는 단순히 거리가없는 플로트를 반환하고 충돌이없는 경우 -1 값을 반환합니까?

(모르는 사람 : AABB = 축 정렬 경계 상자)


나는 틀릴지도 모르지만 여전히이 알고리즘으로 오 탐지를 얻을 것이라고 생각합니다. 3 축을 확인할 때 모든 모서리가 같은면에 있으면 충돌이없는 것입니다. 그러나 3 축 모두 양쪽에 점이 있고 여전히 충돌이없는 상태를 유지할 수있는 것처럼 보입니다. 나는 일반적으로 입 / 출구 거리가 세 개의 슬래브에서 겹치는 지 확인하여 확실하게 알 수 있습니다. 기하학적 도구 사이트에서 가져온 것입니다.
Steve H

거리 쿼리 조건이 필요한 이유는 무엇입니까? 거리가 필요하지 않은 경우에 대해 더 빠른 알고리즘이 있다면 그것에 대해 알고 싶지 않습니까?
sam hocevar

글쎄요, 실제로는 아닙니다. 충돌이 발생하는 거리를 알아야합니다.
SirYakalot

실제로 나는 당신이 옳다고 생각합니다, 나는 질문을 편집 할 것입니다.
SirYakalot

4
다른 스레드에 게시 한대로 여기에는 이러한 유형의 알고리즘에 대한 유용한 리소스가 있습니다. realtimerendering.com/intersections.html
Tetrad

답변:


22

John Amanatides와 함께 Raytracer에서 유비쿼터스하게 사용되는 raymarching algorithm (DDA)을 개발 한 Andrew Woo는 "Fast Ray-Box Intersection" (대체 소스)을 작성 했습니다. 그래픽 보석, 1990, PP에 출판되었다). 395-396를. DDA와 같이 그리드 (예 : 복셀 볼륨)를 통한 통합을 위해 특별히 제작되지 않고 (zacharmarz의 답변 참조),이 알고리즘은 대부분의 3D에서 발견되는 전형적인 다면체 세계와 같이 균등하게 세분화되지 않은 세계에 적합합니다. 계략.

이 접근법은 3D를 지원하며 선택적으로 후면 컬링을 수행합니다. 이 알고리즘은 DDA에 사용 된 것과 동일한 통합 원칙에서 파생되므로 매우 빠릅니다. 자세한 내용은 원본 Graphics Gems 볼륨 (1990)에서 확인할 수 있습니다.

Ray-AABB에 대한 다른 많은 접근법은 realtimerendering.com 에서 찾을 수 있습니다. .

편집 : GPU 및 CPU 모두에서 바람직한 대안의 분기없는 접근 방식은 여기 에서 찾을 수 있습니다 .


아! 당신이 날 이겼어요, 오늘 아침에 왔어요. 좋은 발견!
SirYakalot

반갑습니다. 또한 이런 종류의 알고리즘을 비교해 보는 것이 좋습니다 . (다른 곳에서 이와 같은 공식적인 목록이 있지만 지금은 찾을 수 없습니다.)
Engineer

기사는 여기
bobobobo

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"는 3 개의 좌표가 모두 최소 인 AABB의 모서리이고 "rb"는 최대 좌표의 반대쪽 모서리입니다. 출력 매개 변수 "t"는 원점에서 교차점까지의 벡터 길이입니다.
zacharmarz

함수 정의는 어떻게 생겼습니까? 광선에서 충돌이 발생한 거리를 알아낼 수 있습니까?
SirYakalot

1
그렇다면 교차점을 반환하지만 그 교차점이 음수 인 알고리즘은 무엇을 의미합니까? tmin은 때때로 음수로 반환됩니다.
SirYakalot

1
아, 원점이 상자 안에있을 때
SirYakalot

14

아무도 여기에 알고리즘을 설명하지 않았지만 그래픽 보석 알고리즘 은 다음과 같습니다.

  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

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

교차점은 [tnear, tfar] 사이입니다.
Jeroen Baert

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

반환 값을 어떻게 해석 할 수 있습니까? 원점과 교차점 사이의 유클리드 거리와 같은 것입니까?
Ferdinand Mü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

이것은 2D뿐입니다. 또한 코드는 zacharmarz와 같이 잘 생각되지 않으므로 나누기 및 테스트 횟수를 줄입니다.
sam hocevar

0

Tavian의 브랜치리스 슬래브 방법을 언급 한 사람이 없다는 사실에 놀랐습니다.

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;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.