อัลกอริธึมการชน AABB ที่มีประสิทธิภาพมากที่สุด


53

มีอัลกอริทึมที่รู้จักกันดีที่สุดสำหรับการตรวจจับการชนกันของ AABB กับเรย์หรือไม่?

เมื่อเร็ว ๆ นี้ฉันได้พบกับอัลกอริทึม AABB vs Sphere collision ของ Arvo และฉันสงสัยว่ามีอัลกอริธึมที่น่าสังเกตเช่นนี้หรือไม่

สิ่งหนึ่งที่ต้องมีเงื่อนไขสำหรับอัลกอริทึมนี้คือฉันต้องมีตัวเลือกในการค้นหาผลลัพธ์สำหรับระยะทางจากจุดกำเนิดของรังสีไปยังจุดที่เกิดการชน ต้องบอกว่าถ้ามีอีกอัลกอริทึมที่เร็วกว่าซึ่งไม่ได้ส่งกลับระยะทางแล้วนอกจากการโพสต์อันที่ทำแล้วการโพสต์อัลกอริธึมก็จะมีประโยชน์มากเช่นกัน

โปรดระบุว่าอาร์กิวเมนต์ส่งคืนของฟังก์ชันคืออะไรและคุณใช้เพื่อคืนค่าระยะทางหรือกรณี 'ไม่ชนกัน' ได้อย่างไร ตัวอย่างเช่นมันมีพารามิเตอร์ out สำหรับระยะทางรวมถึงค่าส่งคืน bool หรือไม่ หรือเพียงแค่คืนลอยตามระยะทางเทียบกับค่า -1 สำหรับไม่มีการชนกันหรือไม่

(สำหรับผู้ที่ไม่ทราบ: AABB = กล่องจัดแนวขอบเขตของแนวแกน)


ฉันอาจจะผิด แต่ฉันคิดว่าคุณจะยังคงได้รับผลบวกปลอมด้วยอัลกอริทึมนี้ คุณคิดถูกว่าถ้ามุมทั้งหมดอยู่ด้านเดียวกันเมื่อตรวจสอบ 3 แกนจะไม่มีการชนกัน แต่ดูเหมือนว่าคุณยังสามารถมีเงื่อนไขที่ทั้ง 3 แกนมีคะแนนทั้งสองด้านและยังไม่มีการชนกัน ฉันมักตรวจสอบเพื่อดูว่าระยะทางเข้า / ออกทับซ้อนกันบนทั้งสามแผ่นเพื่อทราบอย่างแน่นอน มันมาจากไซต์เครื่องมือเรขาคณิต
Steve H

ทำไมต้องมีเงื่อนไขในการสอบถามระยะทาง หากมีอัลกอริทึมที่เร็วยิ่งขึ้นสำหรับกรณีเมื่อคุณไม่ต้องการระยะทางคุณไม่ต้องการรู้เกี่ยวกับมันเช่นกัน?
sam hocevar

ดีไม่ไม่จริง ฉันจำเป็นต้องรู้ว่าระยะทางใดที่การปะทะเกิดขึ้น
SirYakalot

ที่จริงฉันคิดว่าคุณพูดถูกฉันจะแก้ไขคำถาม
SirYakalot

4
เมื่อฉันโพสต์ในหัวข้ออื่นของคุณมีแหล่งข้อมูลที่ดีสำหรับอัลกอริธึมประเภทนี้ที่นี่: realtimerendering.com/intersections.html
Tetrad

คำตอบ:


22

Andrew Woo ผู้ซึ่งร่วมกับ John Amanatides พัฒนาอัลกอริทึม raymarching (DDA) ใช้แพร่หลายใน raytracers เขียน"Fast Ray-Box Intersection" (แหล่งที่มาทางเลือกที่นี่ ) ซึ่งตีพิมพ์ใน Graphics Gems, 1990, pp. 395-396 แทนที่จะถูกสร้างขึ้นโดยเฉพาะสำหรับการรวมเข้ากับกริด (เช่นปริมาตร voxel) ตามที่ DDA คือ (ดูคำตอบของ zacharmarz) อัลกอริทึมนี้เหมาะสมอย่างยิ่งกับโลกที่ไม่ได้แบ่งย่อยอย่างเท่าเทียมกันเช่นโลกโพลีเฮดราทั่วไปของคุณ เกม.

วิธีการนี้ให้การสนับสนุน 3D และเลือกทำ backface การคัดสรร อัลกอริทึมนั้นมาจากหลักการเดียวกันของการรวมที่ใช้ใน DDA ดังนั้นจึงรวดเร็วมาก รายละเอียดเพิ่มเติมสามารถพบได้ในไดรฟ์ข้อมูลกราฟิกกราฟิกต้นฉบับ (1990)

วิธีการอื่น ๆ อีกมากมายโดยเฉพาะสำหรับ Ray-AABB ที่จะพบได้ที่realtimerendering.com

แก้ไข: ทางเลือกวิธีการสาขา - ซึ่งจะเป็นที่น่าพอใจทั้ง GPU และ CPU - อาจจะพบได้ที่นี่


ah! คุณเอาชนะฉันไปฉันเพิ่งเจอมันเมื่อเช้านี้ เยี่ยมมาก!
SirYakalot

ยินดีครับ ฉันยังขอแนะนำให้เปรียบเทียบขั้นตอนวิธีการใด ๆ ที่คุณพบในนี้การจัดเรียงของพื้นฐาน (มีรายชื่ออย่างเป็นทางการเช่นนี้ที่อื่น แต่ไม่สามารถหาได้ในขณะนี้)
วิศวกร

บทความอยู่ที่นี่
bobobobo

1
การดำเนินดีความเห็นของอัลกอริทึมวูอาจจะพบได้ที่นี่
วิศวกร

4
ทั้งสองเชื่อมโยงคุณให้สร้าง "ไม่พบ" และข้อผิดพลาด "พระราชวังต้องห้าม" ตามลำดับ ...
liggiorgio

46

สิ่งที่ฉันใช้ก่อนหน้านี้ในโปรแกรมทดสอบของฉัน:

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

ถ้านี่กลับเป็นจริงมันกำลังตัดกันถ้ามันคืนค่าเท็จมันจะไม่ตัดกัน

หากคุณใช้รังสีเดียวกันหลาย ๆ ครั้งคุณสามารถคำนวณล่วงหน้าdirfrac(เฉพาะส่วนในการทดสอบการตัดกันทั้งหมด) แล้วมันก็เร็วมาก และคุณก็มีความยาวของเรย์จนถึงทางแยก (เก็บไว้ในt)


เป็นไปได้ไหมที่จะระบุคีย์สำหรับชื่อตัวแปรของคุณ
SirYakalot

1
ฉันพยายามเพิ่มคำอธิบายในความคิดเห็น ดังนั้น: "r" คือ ray "r.dir" เป็นเวกเตอร์ทิศทางหน่วย "r.org" เป็นจุดกำเนิดซึ่งคุณถ่ายภาพรังสี "dirfrac" เป็นเพียงการปรับให้เหมาะสมเพราะคุณสามารถใช้มันสำหรับรังสีเดียวกันเสมอ (คุณไม่ต้องทำการแบ่ง) และมันหมายถึง 1 / r.dir จากนั้น "lb" คือมุมของ AABB โดยมีค่าพิกัดทั้ง 3 ค่าน้อยที่สุดและ "rb" เป็นมุมตรงข้ามที่มีพิกัดสูงสุด ผลลัพธ์ parametr "t" คือความยาวของเวกเตอร์จากจุดเริ่มต้นถึงจุดตัด
zacharmarz

นิยามฟังก์ชั่นมีลักษณะอย่างไร เป็นไปได้หรือไม่ที่จะทราบระยะที่การชนกันของรังสี?
SirYakalot

1
ดังนั้นอัลกอริทึมของคุณหมายความว่าอย่างไรเมื่อมันส่งกลับจุดตัด แต่จุดตัดนั้นมีจำนวนลบ tmin บางครั้งถูกส่งคืนเป็นจำนวนลบ
SirYakalot

1
อามันคือเมื่อต้นกำเนิดอยู่ในกล่อง
SirYakalot

14

ไม่มีใครอธิบายอัลกอริทึมที่นี่ แต่อัลกอริทึมกราฟิกกราฟิกเป็นเพียง:

  1. โดยใช้เวกเตอร์ทิศทาง ray ของคุณตรวจสอบที่ 3 จาก 6 เครื่องบินผู้สมัครจะได้รับการตีครั้งแรก หากเวกเตอร์ทิศทางรังสีของคุณ (ผิดปกติ) คือ (-1, 1, -1) ดังนั้นระนาบ 3 อันที่สามารถตีได้คือ + x, -y, และ + z

  2. จาก 3 ระนาบของผู้สมัครให้หาค่า 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 สำหรับการอธิบายอย่างแท้จริง (ด้วยภาพ :)
ตำนาน 2

4

นี่คือจุดตัดสามมิติเรย์ / 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
}

สิ่งที่เป็นtnearและtfar?
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];
}

แปลงนี้เพื่อใช้ความสามัคคีและมันก็เร็วกว่า builtin bounds.IntersectRay gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

ฉันจะตีความค่าที่ส่งคืนได้อย่างไร มันเหมือนกับระยะทางแบบยุคลิดระหว่างจุดกำเนิดและจุดตัดหรือไม่?
เฟอร์ดินานด์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 ภายใต้หัวข้อ '5.3.3 Intersecting Ray หรือ 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;

นี่คือ 2d ใช่ไหม
SirYakalot

นี่เป็น 2D เท่านั้นใช่ นอกจากนี้โค้ดไม่ได้คิดเช่นเดียวกับ zacharmarz's ซึ่งดูแลการลดจำนวนหน่วยงานและการทดสอบ
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

ฉันได้เพิ่มคำตอบ @zacharmarz เพื่อจัดการเมื่อแหล่งกำเนิดรังสีอยู่ภายใน AABB ในกรณีนี้ 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.