Các thuật toán va chạm AABB vs Ray hiệu quả nhất


53

Có thuật toán 'hiệu quả nhất' được biết đến để phát hiện va chạm AABB so với Ray không?

Gần đây tôi đã tình cờ phát hiện ra thuật toán va chạm AABB vs Sphere của Arvo và tôi tự hỏi liệu có một thuật toán đáng chú ý tương tự cho việc này không.

Một điều kiện phải có cho thuật toán này là tôi cần có tùy chọn truy vấn kết quả cho khoảng cách từ điểm gốc của tia tới điểm va chạm. đã nói điều này, nếu có một thuật toán khác, nhanh hơn mà không trả về khoảng cách, thì ngoài việc đăng một thuật toán đó, còn đăng thuật toán đó thực sự rất hữu ích.

Ngoài ra, vui lòng cho biết đối số trả về của hàm là gì và cách bạn sử dụng nó để trả về khoảng cách hoặc trường hợp 'không va chạm'. Ví dụ, nó có một tham số out cho khoảng cách cũng như giá trị trả về bool không? hoặc chỉ đơn giản là trả lại một số float với khoảng cách, so với giá trị -1 để không va chạm?

(Đối với những người không biết: AABB = Hộp giới hạn căn chỉnh trục)


Tôi có thể sai nhưng tôi nghĩ bạn vẫn sẽ nhận được kết quả dương tính giả với thuật toán này. Bạn đúng rằng nếu tất cả các góc nằm cùng một phía khi kiểm tra trục 3 thì không có va chạm. Nhưng có vẻ như bạn vẫn có thể có điều kiện cả 3 trục đều có điểm ở cả hai bên và vẫn không có va chạm. Tôi thường kiểm tra xem liệu khoảng cách vào / ra trùng nhau trên cả ba tấm để biết chắc chắn. Đó là từ trang web công cụ hình học.
Steve H

Tại sao phải có điều kiện cho truy vấn khoảng cách? Nếu có một thuật toán thậm chí còn nhanh hơn cho trường hợp khi bạn không cần khoảng cách, bạn cũng không muốn biết về nó?
sam hocevar

tốt, không, không thực sự. Tôi cần biết khoảng cách nào xảy ra vụ va chạm.
SirYakalot

thực ra tôi cho rằng bạn đúng, tôi sẽ chỉnh sửa câu hỏi.
SirYakalot

4
Như tôi đã đăng trong chủ đề khác của bạn, có một nguồn tài nguyên tốt cho các loại thuật toán này tại đây: realtimerendering.com/intersections.html
Tetrad

Câu trả lời:


22

Andrew Woo, người cùng với John Amanatides đã phát triển thuật toán raymar (DDA) được sử dụng phổ biến trong raytracers, đã viết "Giao lộ hộp tia nhanh" (nguồn thay thế ở đây ) được xuất bản trên Graphics Gems, 1990, tr. 395-394. Thay vì được xây dựng đặc biệt để tích hợp thông qua lưới (ví dụ: thể tích voxel) như DDA (xem câu trả lời của zacharmarz), thuật toán này đặc biệt phù hợp với các thế giới không được chia đều, như thế giới đa diện điển hình của bạn được tìm thấy trong hầu hết 3D Trò chơi.

Cách tiếp cận này cung cấp hỗ trợ cho 3D và tùy chọn loại bỏ backface. Thuật toán được bắt nguồn từ các nguyên tắc tích hợp tương tự được sử dụng trong các DDA, vì vậy nó rất nhanh chóng. Chi tiết hơn có thể được tìm thấy trong tập Đồ họa Đá quý gốc (1990).

Nhiều cách tiếp cận khác dành riêng cho Ray-AABB được tìm thấy tại realtimerendering.com .

EDIT: Một cách tiếp cận thay thế, không phân nhánh - có thể được mong muốn trên cả GPU & CPU - có thể được tìm thấy ở đây .


Ah! bạn đánh tôi với nó, tôi vừa đi qua nó sáng nay. Tuyệt vời tìm thấy!
SirYakalot

Hân hạnh, thưa ngài. Tôi cũng muốn đề nghị so sánh bất kỳ thuật toán bạn tìm thấy trên này loại cơ sở. (Có nhiều danh sách chính thức như thế này ở nơi khác, nhưng không thể tìm thấy bất kỳ ngay bây giờ.)
Kỹ sư

Bài viết ở đây
bobobobo

1
Một triển khai nhận xét tốt về thuật toán của Woo có thể được tìm thấy ở đây .
Kỹ sư

4
Hai liên kết bạn cung cấp tạo ra các lỗi "Không tìm thấy" và "
Bị

46

Những gì tôi đã sử dụng trước đó trong raytracer của tôi:

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

Nếu điều này trả về true, nó giao nhau, nếu nó trả về false, thì nó không giao nhau.

Nếu bạn sử dụng cùng một tia nhiều lần, bạn có thể tính toán trước dirfrac(chỉ phân chia trong toàn bộ kiểm tra giao cắt). Và sau đó nó thực sự nhanh chóng. Và bạn cũng có độ dài của tia cho đến khi giao nhau (được lưu trữ trong t).


nó có thể cung cấp một khóa cho ý nghĩa tên biến của bạn không?
SirYakalot

1
Tôi đã cố gắng để thêm một số lời giải thích trong ý kiến. Vì vậy: "r" là tia, "r.dir" là vectơ chỉ hướng đơn vị của nó, "r.org" là nguồn gốc, từ đó bạn bắn tia, "dirfrac" chỉ là tối ưu hóa, bởi vì bạn có thể sử dụng nó luôn cho cùng một tia (bạn không phải thực hiện phép chia) và nó có nghĩa là 1 / r.dir. Khi đó "lb" là góc của AABB với cả 3 tọa độ tối thiểu và "rb" là đối diện - góc có tọa độ tối đa. Thông số đầu ra "t" là chiều dài của vectơ từ gốc đến giao.
zacharmarz

định nghĩa hàm trông như thế nào? Có thể tìm ra khoảng cách mà vụ va chạm xảy ra trên tia?
SirYakalot

1
Vậy thuật toán của bạn có ý nghĩa gì khi nó trả về một giao lộ nhưng giao điểm đó có số âm? tmin đôi khi được trả về như một số âm.
SirYakalot

1
à, đó là khi nguồn gốc nằm trong hộp
SirYakalot

14

Không ai mô tả thuật toán ở đây, nhưng thuật toán Gems đồ họa chỉ đơn giản là:

  1. Sử dụng vectơ chỉ đường của tia của bạn, xác định 3 trong số 6 mặt phẳng ứng cử viên sẽ bị bắn trúng trước . Nếu vectơ hướng tia (không chuẩn hóa) của bạn là (-1, 1, -1), thì 3 mặt phẳng có thể bị bắn là + x, -y và + z.

  2. Trong số 3 mặt phẳng ứng cử viên, hãy tìm giá trị t cho giao điểm của mỗi mặt phẳng. Chấp nhận mặt phẳng có giá trị t lớn nhất là mặt phẳng bị bắn và kiểm tra xem cú đánh có nằm trong hộp không . Sơ đồ trong văn bản làm rõ điều này:

nhập mô tả hình ảnh ở đây

Thực hiện của tôi:

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 để thực sự giải thích nó (điều đó cũng đúng với một bức ảnh :)
legends2k

4

Đây là giao lộ tia 3D / AABox của tôi mà tôi đã sử dụng:

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
}

Là gì tneartfar?
tekknolagi

Giao điểm là giữa [tgần, tfar].
Jeroen Baert

3

Đây là phiên bản tối ưu hóa ở trên mà tôi sử dụng cho 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];
}

chuyển đổi này để sử dụng thống nhất, và nó đã nhanh hơn BUILTIN bounds.IntersectRay gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

Làm thế nào tôi có thể giải thích giá trị trả về? Có phải nó giống như khoảng cách Euclide giữa điểm gốc và điểm giao nhau?
Ferdinand Mütsch

Giá trị nào là khoảng cách đến hộp?
jjxtra

1

Một điều bạn có thể muốn điều tra là rasterizing mặt trước và mặt sau của hộp giới hạn của bạn trong hai bộ đệm riêng biệt. Kết xuất các giá trị x, y, z dưới dạng rgb (cách này hoạt động tốt nhất cho hộp giới hạn có một góc tại (0,0,0) và ngược lại tại (1,1,1).

Rõ ràng, điều này đã hạn chế sử dụng nhưng tôi thấy nó tuyệt vời để hiển thị khối lượng đơn giản.

Để biết thêm chi tiết và mã:

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


1

Đây là mã Line vs AABB tôi đã sử dụng:

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

Điều này có vẻ tương tự như mã được đăng bởi zacharmarz.
Tôi đã nhận được mã này từ cuốn sách 'Phát hiện va chạm trong thời gian thực' của Christer Ericson trong phần '5.3.3 Giao thoa tia hoặc phân đoạn chống lại hộp'

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

đây là 2ngày
SirYakalot

Đây chỉ là 2D, vâng. Ngoài ra, mã không được nghĩ nhiều như zacharmarz, trong đó đảm nhiệm việc giảm số lượng các phép chia và kiểm tra.
sam hocevar

0

Tôi ngạc nhiên khi thấy rằng không ai đã đề cập đến phương pháp phiến không phân nhánh của 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;
}

Giải thích đầy đủ: https://tavianator.com/fast-branchless-raybounding-box-intersections/


0

Tôi đã thêm vào câu trả lời @zacharmarz để xử lý khi nguồn gốc tia nằm trong AABB. Trong trường hợp này tmin sẽ âm và phía sau tia nên tmax là giao điểm đầu tiên giữa tia và 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;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.