En verimli AABB vs Ray çarpışma algoritmaları


53

Ray çarpışma algılaması vs AABB için bilinen en etkili bir algoritma var mı?

Geçenlerde Arvo'nun AABB'e karşı Sphere çarpışma algoritmasına rastladım ve bunun için de benzer bir kayda değer algoritma olup olmadığını merak ediyorum.

Biri bu algoritma için şart olmalıdır, bunun sebebi, yağmurun kaynağından çarpışma noktasına kadar olan mesafenin sonucunu sorgulama seçeneğine sahip olmam gerektiğidir. Bunu söylemek gerekirse, mesafe döndürmeyen başka, daha hızlı bir algoritma varsa, o zaman bir tane yayınlamaya ek olarak, o algoritmayı yayınlamak da gerçekten yararlı olacaktır.

Lütfen ayrıca fonksiyonun geri dönüş argümanının ne olduğunu ve geri dönüş mesafesini veya 'çarpışmasız' durumunu nasıl kullandığınızı da belirtin. Örneğin, bir bool dönüş değerinin yanı sıra mesafe için bir çıkış parametresi var mı? ya da sadece herhangi bir çarpışma için -1 değerine karşılık mesafe ile bir şamandıra döndürür mü?

(Bilmeyenler için: AABB = Eksen Hizalı Sınırlama Kutusu)


Yanlış olabilirim ama bence bu algoritma ile yanlış pozitifler elde edersiniz. 3 ekseni kontrol ederken tüm köşeler aynı tarafta ise, çarpışma olmadığını doğru söylüyorsunuz. Fakat yine de, 3 eksenin tamamının her iki tarafta da noktaları olması ve hala çarpışması olmaması koşuluna sahip olabilirsiniz. Genelde giriş / çıkış mesafelerinin kesin olarak bilmek için her üç slabın üst üste gelip gelmediğini kontrol ediyorum. Geometrik araçlar sitesinden.
Steve H,

Neden mesafe sorgusu için şart olması şart? Mesafeye ihtiyaç duymadığınızda daha hızlı bir algoritma varsa, onun hakkında da bir şey bilmek istemez misiniz?
sam hocevar

hayır, hayır, gerçekten değil. Çarpışmanın ne kadar uzakta olduğunu bilmem gerekiyor.
SirYakalot

Aslında haklı olduğunu varsayalım, soruyu düzenleyeceğim.
SirYakalot

Yanıtlar:


22

John Amanatides ile birlikte, ışın izleyicilerde her yerde bulunan ışın izleme algoritmasını (DDA) geliştiren Andrew Woo, Graphics Gems, 1990, s. 395-396'da yayınlanan "Fast Ray-Box Intersection" ( burada alternatif kaynak ) yazdı. DDA'nın (bakınız zacharmarz 'ın cevabı gibi) bir ızgara (örneğin bir voksel hacmi) aracılığıyla entegrasyon için özel olarak inşa edilmek yerine, bu algoritma, çoğu 3D'de bulunan tipik polyhedra dünyanız gibi, eşit şekilde bölünmeyen dünyalar için özellikle uygundur. oyunlar.

Bu yaklaşım 3D için destek sağlar ve isteğe bağlı olarak arka yüzey temizleme işlemini yapar. Algoritma, DDA'larda kullanılan aynı entegrasyon prensiplerinden türetilmiştir, bu yüzden çok hızlıdır. Orijinal Graphics Gems ciltinde (1990) daha fazla ayrıntı bulunabilir.

Özellikle Ray-AABB için realtimerendering.com adresinde bulunan pek çok başka yaklaşım .

EDIT: Her iki GPU’da ve CPU’da istenebilecek alternatif bir branşsız yaklaşım burada bulunabilir .


Ah! Beni yenersin, bu sabah rastladım. Harika bul!
SirYakalot

Zevk efendim. Ayrıca, bu temelde bulduğunuz algoritmaları karşılaştırmanızı da öneririm . (Başka bir yerde bunun gibi daha resmi listeler var, ancak şu anda hiçbir şey bulamıyorum.)
Mühendis

Makale burada
bobobobo

1
Woo'nun algoritmasının iyi yorumlanmış bir uygulaması burada bulunabilir .
Mühendis

4
Sağladığınız iki bağlantı sırasıyla "Bulunamadı" ve "Yasak" hataları oluşturuyor ...
liggiorgio

46

Raytracer'ımda daha önce ne kullanıyordum:

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

Bu doğru olursa, kesişir, yanlışsa, kesişen değildir.

Aynı ışını birçok kez kullanıyorsanız, önceden hesaplayabilirsiniz dirfrac(yalnızca tüm kesişim testindeki bölme). Ve sonra gerçekten hızlı. Ve ayrıca kesişme noktasına kadar (içinde saklanmış t) ışın uzunluğu vardır .


Değişken isimlerinizin ne anlama geldiğine dair bir anahtar vermek mümkün müdür?
SirYakalot

1
Yorumlara bir açıklama eklemeye çalıştım. Yani: "r", ışındır, "r.dir", birim yön vektörüdür, "r.org" orijindir, oradan ışın çektirirsiniz, "dirfrac" sadece optimizasyondur, çünkü her zaman aynı ışın için kullanabilirsiniz (bölmek zorunda değilsiniz) ve bu 1 / r.dir anlamına gelir. Daha sonra "lb", AABB'nin köşesidir, tüm 3 koordinat minimaldir ve "rb" oposite - maksimum koordinatlara sahip köşelidir. Çıkış parametresi "t", kökenden kesişime kadar vektörün uzunluğu.
zacharmarz

işlev tanımı neye benziyor? Çarpmanın ışın üzerinde meydana geldiği mesafeyi bulmak mümkün müdür?
SirYakalot,

1
Öyleyse, algoritmanız bir kavşak döndürdüğü zaman ne anlama geliyor? tmin bazen negatif bir sayı olarak döndürülür.
SirYakalot

1
ah, kökeni kutunun içinde olduğunda
SirYakalot

14

Burada algoritmayı kimse tarif etmedi, fakat Grafik Taşlar algoritması basitçe:

  1. Ray'ınızın yön vektörünü kullanarak, 6 aday uçaktan hangisinin ilk önce vurulacağını belirleyin . Eğer (normalize edilmemiş) ışın yönü vektörünüz (-1, 1, -1) ise, vurulması muhtemel 3 uçak + x, -y ve + z'dir.

  2. 3 aday uçağın her birinin kesişme noktasındaki t değerini bulun. En büyük t değerini alan düzlemi, vurulmuş düzlem olarak kabul edin ve isabetin kutunun içinde olup olmadığını kontrol edin . Metindeki şema bunu netleştirir:

görüntü tanımını buraya girin

Benim uygulama:

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

Aslında bunu açıklamak için +1 (o da bir resimli :)
legends2k

4

Bu benim kullandığım 3B ışınım / AABox kavşağım:

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
}

Neler tnearve tfar?
tekknolagi

Kavşak [tnear, tfar] arasındadır.
Jeroen Baert

3

GPU için kullandığım yukarıdakilerin optimize edilmiş bir sürümü:

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

birlik kullanım için bu dönüştürülmüş ve yerleşik bounds.IntersectRay daha hızlıydı gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

Döndürülen değeri nasıl yorumlayabilirim? Menşe ile kesişme noktası arasındaki Öklid mesafesi gibi bir şey mi?
Ferdinand Mütsch

Kutuya olan mesafe ne kadardır?
jjxtra

1

Araştırmak isteyebileceğiniz bir şey, sınırlama kutunuzun ön ve arka yüzeylerini iki ayrı tamponda rasterleştirmektir. X, y, z değerlerini rgb olarak işleyin (bu, bir köşesinde (0,0,0) ve diğerinde (1,1,1) bulunan bir sınırlayıcı kutu için en iyisidir.

Açıkçası, bunun sınırlı bir kullanımı var ama basit birimler oluşturmak için harika buldum.

Daha fazla ayrıntı ve kod için:

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


1

İşte kullandığım AABB kodu vs Line:

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

Bu zacharmarz tarafından yayınlanan koda benziyor.
Bu kodu Christer Ericson tarafından '5.3.3 Işınla Işınla veya Kutuya Karşı Segmentle Işınlama' bölümündeki 'Gerçek Zamanlı Çarpışma Tespiti' kitabından aldım.

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

bu 2d, evet?
SirYakalot

Bu sadece 2D, evet. Ayrıca, kod, bölümlerin ve testlerin sayısının azaltılmasına özen gösteren zacharmarz'ınki kadar iyi düşünülmemiştir.
sam hocevar

0

Tavian'ın kimsenin branşsız döşeme yönteminden bahsetmediğini gördüğüme şaşırdım.

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

Tam açıklama: https://tavianator.com/fast-branchless-raybounding-box-intersections/


0

Işın kökeni AABB'nin içindeyken işlemek için @zacharmarz cevabına ekledim. Bu durumda tmin negatif ve ışın arkasında olacak, bu nedenle tmax, ışın ile AABB arasındaki ilk kesişimdir.

// 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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.