algoritma tabrakan AABB vs Ray paling efisien


53

Apakah ada algoritma 'paling efisien' yang dikenal untuk deteksi tabrakan AABB vs Ray?

Saya baru-baru ini sengaja menemukan algoritma tabrakan Avo AABB vs Sphere Arvo, dan saya bertanya-tanya apakah ada algoritma yang sama pentingnya untuk ini.

Satu harus memiliki syarat untuk algoritma ini adalah bahwa saya harus memiliki opsi untuk menanyakan hasil untuk jarak dari asal sinar ke titik tumbukan. Setelah mengatakan ini, jika ada yang lain, algoritma lebih cepat yang tidak mengembalikan jarak, maka selain memposting yang melakukannya, juga memposting algoritma itu akan sangat membantu.

Harap sebutkan juga apa argumen pengembalian fungsi, dan bagaimana Anda menggunakannya untuk mengembalikan jarak atau kasus 'tanpa tabrakan'. Misalnya, apakah ia memiliki parameter keluar untuk jarak serta nilai pengembalian bool? atau apakah itu hanya mengembalikan pelampung dengan jarak, vs nilai -1 tanpa tabrakan?

(Bagi yang tidak tahu: AABB = Axis Aligned Bounding Box)


Saya mungkin salah tetapi saya pikir Anda masih akan mendapatkan positif palsu dengan algoritma ini. Anda benar bahwa jika semua sudut berada di sisi yang sama ketika memeriksa sumbu 3, tidak ada tabrakan. Tetapi sepertinya Anda masih dapat memiliki kondisi di mana ketiga sumbu memiliki titik di kedua sisi dan masih tidak memiliki tabrakan. Saya biasanya memeriksa untuk melihat apakah jarak masuk / keluar tumpang tindih pada ketiga lempeng untuk mengetahui dengan pasti. Itu dari situs alat Geometrik.
Steve H

Mengapa harus memiliki syarat untuk kueri jarak? Jika ada algoritma yang lebih cepat untuk kasing saat Anda tidak memerlukan jarak, tidakkah Anda ingin mengetahuinya juga?
sam hocevar

baik, tidak, tidak juga. Saya perlu tahu pada jarak berapa tabrakan terjadi.
SirYakalot

sebenarnya saya kira Anda benar, saya akan mengedit pertanyaan.
SirYakalot

4
Seperti yang saya posting di utas Anda yang lain, ada sumber daya yang bagus untuk jenis-jenis algoritma ini di sini: realtimerendering.com/intersections.html
Tetrad

Jawaban:


22

Andrew Woo, yang bersama dengan John Amanatides mengembangkan algoritma raymarching (DDA) yang digunakan di mana-mana dalam raytracer, menulis "Fast Ray-Box Intersection" (sumber alternatif di sini ) yang diterbitkan dalam Graphics Gems, 1990, hlm. 395-396. Alih-alih dibangun khusus untuk integrasi melalui kisi (mis. Volume voxel) sebagaimana DDA (lihat jawaban zacharmarz), algoritme ini secara khusus cocok untuk dunia yang tidak terbagi secara merata, seperti dunia polyhedra khas Anda yang ditemukan di sebagian besar 3D pertandingan.

Pendekatan ini memberikan dukungan untuk 3D, dan secara opsional melakukan pengaburan backface. Algoritma ini berasal dari prinsip-prinsip integrasi yang sama yang digunakan dalam DDA, sehingga sangat cepat. Lebih detail dapat ditemukan dalam volume Permata Grafis asli (1990).

Banyak pendekatan lain khusus untuk Ray-AABB dapat ditemukan di realtimerendering.com .

EDIT: Alternatif, pendekatan tanpa cabang - yang diinginkan pada GPU & CPU - dapat ditemukan di sini .


ah! Anda mengalahkan saya untuk itu, saya baru saja menemukan pagi ini. Great ditemukan!
SirYakalot

Senang, Tuan. Saya juga menyarankan untuk membandingkan algoritma apa pun yang Anda temukan berdasarkan jenis ini . (Ada lebih banyak daftar resmi seperti ini di tempat lain, tetapi tidak dapat menemukannya sekarang.)
Engineer

Artikelnya ada di sini
bobobobo

1
Implementasi algoritma Woo yang berkomentar dengan baik dapat ditemukan di sini .
Insinyur

4
Dua tautan yang Anda berikan menghasilkan kesalahan "Tidak ditemukan" dan "Terlarang" masing-masing ...
liggiorgio

46

Apa yang saya gunakan sebelumnya di raytracer saya:

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

Jika ini mengembalikan true, itu berpotongan, jika itu mengembalikan false, itu tidak berpotongan.

Jika Anda menggunakan sinar yang sama berkali-kali, Anda dapat melakukan precompute dirfrac(hanya pembagian di seluruh tes persimpangan). Dan kemudian sangat cepat. Dan Anda juga memiliki panjang sinar sampai persimpangan (disimpan di t).


apakah mungkin memberikan kunci untuk arti nama variabel Anda?
SirYakalot

1
Saya mencoba menambahkan beberapa penjelasan dalam komentar. Jadi: "r" is ray, "r.dir" adalah vektor arah unitnya, "r.org" adalah asal, dari mana Anda menembakkan sinar, "dirfrac" hanyalah optimasi, karena Anda dapat menggunakannya selalu untuk sinar yang sama (Anda tidak harus melakukan pembagian) dan itu berarti 1 / r.dir. Kemudian "lb" adalah sudut AABB dengan semua 3 koordinat minimal dan "rb" adalah sudut - berlawanan dengan koordinat maksimum. Parameter output "t" adalah panjang vektor dari titik asal ke persimpangan.
zacharmarz

seperti apa definisi fungsi itu? Apakah mungkin untuk mengetahui jarak tumbukan yang terjadi pada sinar?
SirYakalot

1
jadi apa arti algoritma Anda ketika mengembalikan sebuah persimpangan tetapi persimpangan itu memiliki angka negatif? tmin terkadang dikembalikan sebagai angka negatif.
SirYakalot

1
ah, saat asalnya ada di dalam kotak
SirYakalot

14

Tidak ada yang menggambarkan algoritme di sini, tetapi algoritme Graphics Gems sederhana:

  1. Dengan menggunakan vektor arah ray Anda, tentukan 3 dari 6 pesawat kandidat mana yang akan dipukul pertama . Jika vektor arah sinar (yang tidak dinormalkan) Anda adalah (-1, 1, -1), maka 3 bidang yang mungkin terkena adalah + x, -y, dan + z.

  2. Dari 3 pesawat kandidat, temukan nilai-t untuk persimpangan untuk masing-masing. Terima pesawat yang mendapat nilai t terbesar sebagai pesawat yang tertabrak, dan periksa bahwa klik itu ada di dalam kotak . Diagram dalam teks memperjelas ini:

masukkan deskripsi gambar di sini

Implementasi saya:

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 untuk benar-benar menjelaskannya (itu juga dengan gambar :)
legends2k

4

Ini adalah persimpangan 3D ray / AABox yang pernah saya gunakan:

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
}

Apa tneardan tfar?
tekknolagi

Persimpangan ini antara [tnear, tfar].
Jeroen Baert

3

Ini adalah versi yang dioptimalkan dari yang saya gunakan untuk 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];
}

dikonversi ini untuk penggunaan persatuan, dan itu lebih cepat daripada builtin bounds.IntersectRay gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

Bagaimana saya bisa menginterpretasikan nilai yang dikembalikan? Apakah ini seperti jarak Euclidean antara titik asal dan titik persimpangan?
Ferdinand Mütsch

Apa nilai jarak ke kotak?
jjxtra

1

Satu hal yang mungkin ingin Anda selidiki adalah meraster bagian depan dan belakang kotak pembatas Anda dalam dua buffer terpisah. Render nilai x, y, z sebagai rgb (ini bekerja paling baik untuk kotak pembatas dengan satu sudut di (0,0,0) dan sebaliknya di (1,1,1).

Jelas, ini memiliki penggunaan terbatas tetapi saya merasa hebat untuk merender volume sederhana.

Untuk lebih detail dan kode:

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


1

Inilah kode Line vs AABB yang telah saya gunakan:

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

Ini sepertinya mirip dengan kode yang diposting oleh zacharmarz.
Saya mendapatkan kode ini dari buku 'Deteksi Tabrakan Real-Time' oleh Christer Ericson di bawah bagian '5.3.3 Intersecting Ray atau 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;

ini 2d, ya?
SirYakalot

Ini hanya 2D, ya. Juga, kode tersebut tidak sebaik yang dipikirkan oleh zacharmarz, yang menangani pengurangan jumlah divisi dan tes.
sam hocevar

0

Saya terkejut melihat bahwa tidak ada yang menyebutkan metode slab tanpa cabang oleh 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;
}

Penjelasan lengkap: https://tavianator.com/fast-branchless-raybounding-box-intersections/


0

Saya telah menambahkan jawaban @zacharmarz untuk menangani ketika asal berkas ada di dalam AABB. Dalam hal ini tmin akan negatif dan di belakang sinar sehingga tmax adalah persimpangan pertama antara sinar dan 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;
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.