algoritmi di collisione AABB vs Ray più efficienti


53

Esiste un algoritmo "più efficiente" noto per il rilevamento delle collisioni AABB vs Ray?

Di recente mi sono imbattuto nell'algoritmo di collisione AABB vs Sphere di Arvo e mi chiedo se esiste un algoritmo altrettanto degno di nota per questo.

Una condizione che deve avere per questo algoritmo è che devo avere la possibilità di interrogare il risultato per la distanza dall'origine del raggio al punto di collisione. detto questo, se esiste un altro algoritmo più veloce che non restituisce la distanza, quindi oltre a pubblicarne uno che lo fa, anche pubblicare quell'algoritmo sarebbe davvero molto utile.

Indica anche qual è l'argomento return della funzione e come lo usi per restituire la distanza o un caso di "nessuna collisione". Ad esempio, ha un parametro out per la distanza e un valore di ritorno bool? o restituisce semplicemente un galleggiante con la distanza, rispetto a un valore di -1 per nessuna collisione?

(Per chi non lo sapesse: AABB = Axis Aligned Bounding Box)


Potrei sbagliarmi, ma penso che otterrai ancora falsi positivi con questo algoritmo. Hai ragione che se tutti gli angoli sono sullo stesso lato quando si controlla il 3 asse, non c'è collisione. Ma sembra che tu possa ancora avere la condizione in cui tutti e 3 gli assi hanno punti su entrambi i lati e non hanno ancora alcuna collisione. In genere controllo per vedere se le distanze di entrata / uscita si sovrappongono su tutte e tre le lastre per accertarmene. Viene dal sito degli strumenti geometrici.
Steve H,

Perché è necessario disporre della condizione per la query sulla distanza? Se esiste un algoritmo ancora più veloce per il caso in cui non hai bisogno della distanza, non vuoi saperlo anche tu?
Sam Hocevar,

bene, no, non proprio. Devo sapere a quale distanza avviene la collisione.
SirYakalot,

in realtà suppongo che tu abbia ragione, modificherò la domanda.
SirYakalot,

4
Come ho postato nell'altra tua discussione, c'è una buona risorsa per questi tipi di algoritmi qui: realtimerendering.com/intersections.html
Tetrad

Risposte:


22

Andrew Woo, che insieme a John Amanatides ha sviluppato l'algoritmo raymarching (DDA) usato in modo ubiquitario nei raytracer, ha scritto "Fast Ray-Box Intersection" (fonte alternativa qui ) che è stato pubblicato in Graphics Gems, 1990, pp. 395-396. Piuttosto che essere costruito appositamente per l'integrazione attraverso una griglia (ad es. Un volume voxel) come DDA (vedi la risposta di zacharmarz), questo algoritmo è specificamente adatto a mondi che non sono suddivisi uniformemente, come il tipico mondo poliedri presente nella maggior parte del 3D Giochi.

L'approccio fornisce supporto per il 3D e facoltativamente l'abbattimento del backface. L'algoritmo deriva dagli stessi principi di integrazione utilizzati nei DDA, quindi è molto rapido. Maggiori dettagli sono disponibili nel volume originale di Graphics Gems (1990).

Molti altri approcci specifici per Ray-AABB sono disponibili su realtimerendering.com .

EDIT: un approccio alternativo e senza rami - che sarebbe auspicabile su GPU e CPU - può essere trovato qui .


ah! mi hai battuto, l'ho appena incontrato stamattina. Grande scoperta!
SirYakalot,

Piacere, signore. Suggerirei anche di confrontare tutti gli algoritmi che trovi su questo tipo di base. (Esistono altri elenchi ufficiali come questo altrove, ma non riesco a trovarli in questo momento.)
Ingegnere

L'articolo è qui
bobobobo,

1
Un'implementazione ben commentata dell'algoritmo di Woo può essere trovata qui .
Ingegnere il

4
I due link forniti generano rispettivamente gli errori "Non trovato" e "Proibito" ...
liggiorgio,

46

Quello che ho usato prima nel mio 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;

Se questo restituisce vero, si interseca, se restituisce falso, non si interseca.

Se si utilizza lo stesso raggio più volte, è possibile eseguire il pre-calcolo dirfrac(solo divisione nell'intero test di intersezione). E poi è davvero veloce. E hai anche la lunghezza del raggio fino all'intersezione (memorizzata in t).


sarebbe possibile fornire una chiave per il significato dei nomi delle variabili?
SirYakalot,

1
Ho provato ad aggiungere qualche spiegazione nei commenti. Quindi: "r" è raggio, "r.dir" è il vettore direzione unità, "r.org" è origine, da cui si scatta raggio, "dirfrac" è solo ottimizzazione, perché è possibile utilizzarlo sempre per lo stesso raggio (non devi fare divisione) e significa 1 / r.dir. Quindi "lb" è l'angolo di AABB con tutte e 3 le coordinate minime e "rb" è opposto - angolo con le coordinate massime. Il parametro di output "t" è la lunghezza del vettore dall'origine all'intersezione.
Zacharmarz,

come appare la definizione della funzione? È possibile scoprire la distanza in cui si è verificata la collisione sul raggio?
SirYakalot,

1
quindi cosa significa l'algoritmo quando restituisce un'intersezione ma quell'intersezione ha un numero negativo? tmin viene talvolta restituito come numero negativo.
SirYakalot,

1
ah, è quando l'origine è dentro la scatola
SirYakalot il

14

Nessuno ha descritto l'algoritmo qui, ma l' algoritmo Graphics Gems è semplicemente:

  1. Utilizzando il vettore di direzione del raggio, determinare quale dei 6 piani candidati sarebbe colpito per primo . Se il vettore di direzione del raggio (non normalizzato) è (-1, 1, -1), i 3 piani che è possibile colpire sono + x, -y e + z.

  2. Dei 3 piani candidati, trova il valore t per l'intersezione per ciascuno. Accetta l'aereo che ottiene il valore t più grande come l'aereo che è stato colpito e controlla che l'hit sia all'interno della scatola . Il diagramma nel testo lo chiarisce:

inserisci qui la descrizione dell'immagine

La mia implementazione:

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 per spiegarlo effettivamente (anche quello con un'immagine :)
legends2k

4

Questa è la mia intersezione 3D ray / AABox che ho usato:

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
}

Cosa sono tneare tfar?
Tekknolagi,

L'intersezione è tra [tnear, tfar].
Jeroen Baert,

3

Ecco una versione ottimizzata di quanto sopra che utilizzo per 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];
}

convertito per uso unitario, ed era più veloce dei limiti incorporati. IntertersRay gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

Come posso interpretare il valore restituito? È qualcosa di simile alla distanza euclidea tra origine e punto di intersezione?
Ferdinand Mütsch,

Qual è il valore della distanza dalla scatola?
jjxtra,

1

Una cosa che potresti voler indagare è rasterizzare la parte anteriore e posteriore del riquadro di delimitazione in due buffer separati. Renderizza i valori x, y, z come rgb (funziona meglio per un rettangolo di selezione con un angolo in (0,0,0) e l'opposto in (1,1,1).

Ovviamente, questo ha un uso limitato ma l'ho trovato ottimo per il rendering di volumi semplici.

Per maggiori dettagli e codice:

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


1

Ecco il codice Line vs AABB che ho usato:

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

Questo sembra simile al codice pubblicato da zacharmarz.
Ho ottenuto questo codice dal libro "Real-Time Collision Detection" di Christer Ericson nella sezione "5.3.3 Intersecting Ray o Segment Against Box"

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

questo è 2d, sì?
SirYakalot,

Questo è solo 2D, sì. Inoltre, il codice non è così ben pensato come quello di zacharmarz, che si occupa di ridurre il numero di divisioni e test.
Sam Hocevar,

0

Sono sorpreso di vedere che nessuno ha menzionato il metodo delle lastre senza rami di 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;
}

Spiegazione completa: https://tavianator.com/fast-branchless-raybounding-box-intersections/


0

Ho aggiunto alla risposta @zacharmarz da gestire quando l'origine del raggio è all'interno dell'AABB. In questo caso tmin sarà negativo e dietro il raggio, quindi tmax è la prima intersezione tra il raggio e 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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.