najbardziej wydajne algorytmy kolizji AABB vs Ray


53

Czy istnieje znany „najbardziej wydajny” algorytm wykrywania kolizji AABB vs Ray?

Niedawno natknąłem się na algorytm kolizji AABB vs Sfera Arvo i zastanawiam się, czy istnieje podobny podobny algorytm.

Warunkiem tego algorytmu jest to, że muszę mieć możliwość zapytania wyniku o odległość od początku promienia do punktu zderzenia. Powiedziawszy to, jeśli istnieje inny, szybszy algorytm, który nie zwraca odległości, to oprócz opublikowania takiego, który to robi, również wysłanie tego algorytmu byłoby bardzo pomocne.

Podaj również, jaki jest argument return funkcji i jak go używasz do zwracania odległości lub przypadku braku kolizji. Na przykład, czy ma parametr out dla odległości, a także wartość powrotu bool? czy po prostu zwraca liczbę zmiennoprzecinkową wraz z odległością względem wartości -1 dla braku kolizji?

(Dla tych, którzy nie wiedzą: AABB = Wyrównana oś ograniczająca)


Mogę się mylić, ale myślę, że nadal będziesz mieć fałszywe alarmy przy tym algorytmie. Masz rację, że jeśli wszystkie rogi są po tej samej stronie podczas sprawdzania 3 osi, nie ma kolizji. Ale wydaje się, że nadal możesz mieć stan, w którym wszystkie 3 osie mają punkty po obu stronach i nadal nie mają kolizji. Na ogół sprawdzam, czy odległości wejścia / wyjścia pokrywają się na wszystkich trzech płytach, aby się upewnić. Pochodzi z witryny narzędzi geometrycznych.
Steve H

Dlaczego musi istnieć warunek zapytania o odległość? Jeśli istnieje jeszcze szybszy algorytm dla przypadku, gdy nie potrzebujesz odległości, czy też nie chcesz o tym wiedzieć?
sam hocevar,

nie, nie bardzo. Muszę wiedzieć, z jakiej odległości dochodzi do zderzenia.
SirYakalot,

właściwie chyba mam rację, zredaguję pytanie.
SirYakalot,

4
Jak napisałem w innym wątku, tutaj jest dobry zasób dla tego rodzaju algorytmów: realtimerendering.com/intersections.html
Tetrad

Odpowiedzi:


22

Andrew Woo, który wraz z Johnem Amanatidesem opracował algorytm raymarchingowy (DDA) powszechnie stosowany w raytracerach, napisał „Fast Ray-Box Intersection” ( tutaj alternatywne źródło ), który został opublikowany w Graphics Gems, 1990, ss. 395-396. Algorytm ten nie jest budowany specjalnie do integracji przez siatkę (np. Objętość wokseli) w postaci DDA (patrz odpowiedź zacharmarz), szczególnie nadaje się do światów, które nie są równomiernie podzielone, takich jak typowy świat wielościanów w większości 3D Gry.

Podejście to zapewnia obsługę 3D i opcjonalnie eliminuje backface. Algorytm wywodzi się z tych samych zasad integracji, które są stosowane w DDA, więc jest bardzo szybki. Więcej szczegółów można znaleźć w oryginalnym tomie Graphics Gems (1990).

Wiele innych podejść do Ray-AABB można znaleźć na stronie realtimerendering.com .

EDYCJA: Alternatywne podejście bez rozgałęzień - które byłoby pożądane zarówno na GPU, jak i CPU - można znaleźć tutaj .


ah! pobiłaś mnie do tego, właśnie go spotkałem dziś rano. Świetne znalezisko!
SirYakalot,

Przyjemność, sir. Sugeruję również porównanie wszelkich algorytmów, które znajdziesz na tej podstawie. (Istnieje wiele oficjalnych list takich jak ta gdzie indziej, ale nie można ich teraz znaleźć.)
Inżynier

Artykuł jest tutaj
bobobobo

1
Dobrze skomentowana implementacja algorytmu Woo można znaleźć tutaj .
Inżynier

4
Dwa podane linki generują odpowiednio błędy „Nie znaleziono” i „Zabronione” ...
liggiorgio

46

Czego używałem wcześniej w moim raytracerze:

// 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;

Jeśli to zwraca wartość true, to przecina się, jeśli zwraca false, to nie przecina się.

Jeśli użyjesz tego samego promienia wiele razy, możesz wykonać obliczenia wstępne dirfrac(tylko podział w całym teście przecięcia). A potem jest naprawdę szybko. I masz również długość promienia do skrzyżowania (przechowywane w t).


czy można podać klucz do tego, co oznaczają nazwy zmiennych?
SirYakalot

1
Próbowałem dodać wyjaśnienie w komentarzach. Więc: „r” to promień, „r.dir” to wektor kierunku jednostki, „r.org” to pochodzenie, z którego strzelasz promieniem, „dirfrac” to tylko optymalizacja, ponieważ możesz go zawsze używać dla tego samego promienia (nie musisz robić podziału), a to oznacza 1 / r.dir. Wtedy „lb” jest narożnikiem AABB ze wszystkimi 3 współrzędnymi minimalnymi, a „rb” jest przeciwny - narożnik z maksymalnymi współrzędnymi. Parametr wyjściowy „t” to długość wektora od początku do przecięcia.
zacharmarz

jak wygląda definicja funkcji? Czy można ustalić odległość, na której doszło do zderzenia na promieniu?
SirYakalot,

1
więc co oznacza twój algorytm, gdy zwraca przecięcie, ale przecięcie to ma liczbę ujemną? tmin jest czasami zwracany jako liczba ujemna.
SirYakalot,

1
ah, to kiedy pochodzenie znajduje się w pudełku
SirYakalot,

14

Nikt nie opisał tutaj algorytmu, ale algorytm Graphics Gems jest po prostu:

  1. Za pomocą wektora kierunku promienia określ, które 3 z 6 kandydujących samolotów zostaną trafione jako pierwsze . Jeśli twoim (nietypowym) wektorem kierunku promienia jest (-1, 1, -1), wówczas 3 płaszczyzny, które można trafić, to + x, -y i + z.

  2. Z 3 kandydujących samolotów znajdź wartość t dla przecięcia dla każdego. Zaakceptuj płaszczyznę, która otrzymuje największą wartość t, jako płaszczyznę, która została trafiona, i sprawdź, czy trafienie jest w polu . Schemat w tekście wyjaśnia to:

wprowadź opis zdjęcia tutaj

Moja realizacja:

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 za faktyczne wyjaśnienie (to też ze zdjęciem)
legends2k

4

To jest moje skrzyżowanie 3D ray / AABox, którego używałem:

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
}

Jakie są tneari tfar?
tekknolagi,

Przecięcie jest między [tnear, tfar].
Jeroen Baert,

3

Oto zoptymalizowana wersja powyższego, której używam do 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];
}

konwertowane to do stosowania jedności, i to szybciej niż wbudowanego bounds.IntersectRay gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

Jak mogę zinterpretować zwróconą wartość? Czy jest to coś w rodzaju odległości euklidesowej między początkiem a punktem przecięcia?
Ferdinand Mütsch

Jaką wartością jest odległość do pudełka?
jjxtra

1

Jedną z rzeczy, które możesz chcieć zbadać, jest rasteryzacja przedniej i tylnej ściany obwiedni w dwóch osobnych buforach. Renderuj wartości x, y, z jako rgb (działa najlepiej dla ramki granicznej z jednym rogiem na (0,0,0) i przeciwnie na (1,1,1).

Oczywiście ma to ograniczone zastosowanie, ale uważam, że świetnie nadaje się do renderowania prostych woluminów.

Aby uzyskać więcej szczegółów i kod:

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


1

Oto kod Line vs. AABB, którego używałem:

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

Wygląda to podobnie do kodu opublikowanego przez zacharmarz.
Kod ten otrzymałem z książki Christer Ericson w książce „Wykrywanie kolizji w czasie rzeczywistym” w części „5.3.3 Przecinające się promienie lub segmenty przeciw pudełku”

// 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;

to jest 2d, tak?
SirYakalot,

To tylko 2D, tak. Ponadto kod nie jest tak dobrze przemyślany, jak kod zacharmarz, który dba o zmniejszenie liczby podziałów i testów.
sam hocevar,

0

Dziwi mnie, że Tavian nie wspomniał o bez rozgałęzionej płycie

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;
}

Pełne wyjaśnienie: https://tavianator.com/fast-branchless-raybounding-box-intersections/


0

Dodałem do odpowiedzi @zacharmarz, aby obsłużyć, gdy początek promienia znajduje się w AABB. W takim przypadku tmin będzie ujemne i za promieniem, więc tmax jest pierwszym przecięciem promienia i 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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.