найефективніші алгоритми зіткнення AABB vs Ray


53

Чи існує відомий "найефективніший" алгоритм для виявлення зіткнень AABB проти Рея?

Нещодавно я натрапив на алгоритм зіткнення AABB проти Sphere Arvo, і мені цікаво, чи існує аналогічний алгоритм для цього.

Необхідно, що умова цього алгоритму полягає в тому, що мені потрібно мати можливість запитувати результат для відстані від початку променя до точки зіткнення. сказавши це, якщо є інший, більш швидкий алгоритм, який не повертає відстань, то, окрім публікації, яка це робить, також розміщення цього алгоритму було б дуже корисно.

Будь ласка, вкажіть, що таке аргумент повернення функції та як ви використовуєте її для повернення відстані або випадку "без зіткнення". Наприклад, чи має він вихідний параметр відстані, а також значення повернення bool? чи просто повертає поплавок з відстані, порівняно зі значенням -1, без зіткнення?

(Для тих, хто не знає: AABB = Axis Aligis Bounding Box)


Я можу помилятися, але, думаю, ви все одно отримаєте помилкові позитиви за допомогою цього алгоритму. Ви маєте рацію, що якщо всі кути перебувають на одній стороні при перевірці осі 3, то зіткнення не буде. Але здається, ви все ще можете мати умову, коли всі 3 осі мають точки з обох сторін і все ще не мають зіткнення. Я звичайно перевіряю, чи перетинаються відстані входу / виходу на всіх трьох плитах, щоб точно знати. Це з сайту Геометричні інструменти.
Стів Н

Чому обов'язково має бути умова для запиту на відстань? Якщо є ще більш швидкий алгоритм для випадку, коли вам не потрібна відстань, чи не хочете ви про це також знати?
sam hocevar

ну ні, не дуже. Мені потрібно знати, на якій відстані відбувається зіткнення.
SirYakalot

насправді я гадаю, що ти маєш рацію, я відредагую це питання.
SirYakalot

4
Як я публікував у вашій іншій темі, тут є хороший ресурс для таких типів алгоритмів: realtimerendering.com/intersections.html
Тетрад

Відповіді:


22

Ендрю Ву, який разом з Джоном Аманатідесом розробив алгоритм ранжироутворення (DDA), який всюди використовується в промінчиках, написав "Fast Ray-Box Intersection" (альтернативне джерело тут ), яке було опубліковано в Graphics Gems, 1990, с. 395-396. Замість того, щоб будуватись спеціально для інтеграції через сітку (наприклад, об'єм вокселів), як DDA (див. Відповідь zacharmarz), цей алгоритм спеціально підходить для світів, які не поділені рівномірно, наприклад, вашого типового багатогранника, що знаходиться в більшості 3D ігри.

Цей підхід забезпечує підтримку 3D, а також необов'язково відкидання фонових зображень. Алгоритм виходить з тих же принципів інтеграції, які використовуються в DDA, тому це дуже швидко. Більш детально можна ознайомитися в оригінальному томі Graphics Gems (1990).

Багато інших підходів спеціально для Ray-AABB можна знайти на сайті realtimerendering.com .

EDIT: альтернативний, Позаофісне підхід - який би бажано на обох GPU і CPU - можна знайти тут .


ах! ти побив мене до цього, я щойно натрапив на це сьогодні вранці. Чудова знахідка!
SirYakalot

Задоволення, сер. Я б також запропонував порівняти будь-які алгоритми, які ви знайдете на цій основі. (Є більше офіційних списків, як це в інших місцях, але зараз їх не знайти.)
Інженер

Стаття тут
bobobobo

1
Добре прокоментувала реалізацію алгоритму В можна знайти тут .
Інженер

4
Дві надані вами посилання створюють помилки "Не знайдено" та "Заборонено" відповідно
liggiorgio

46

Що я використовував раніше у своєму 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;

Якщо це повертає істину, воно перетинається, якщо воно повертає помилкове, воно не перетинається.

Якщо ви використовуєте один і той же промінь багато разів, ви можете попередньо обчислити dirfrac(лише поділ у цілому тесті перетину). І тоді це дійсно швидко. І у вас також є довжина променя до перетину (зберігається в t).


чи можна було б надати ключ, що означають ваші імена змінних?
SirYakalot

1
Я спробував додати трохи пояснень у коментарі. Отже: "r" - це промінь, "r.dir" - це його векторний напрямок, "r.org" - це походження, з якого ви знімаєте промінь, "dirfrac" - це просто оптимізація, оскільки ви можете використовувати його завжди для одного і того ж променя (не потрібно робити поділ), і це означає 1 / r.dir. Тоді "фунт" - кут AABB з усіма 3 координатами мінімальними, а "rb" - протилежним - кутом з максимальними координатами. Вихідний параметр "t" - це довжина вектора від початку походження до перетину.
zacharmarz

як виглядає визначення функції? Чи можна дізнатися відстань, на якій зіткнення відбулося на промені?
SirYakalot

1
так що означає ваш алгоритм, коли він повертає перехрестя, але це перетин має від'ємне число? tmin іноді повертається як від’ємне число.
SirYakalot

1
ах, це коли походження знаходиться в коробці
SirYakalot

14

Тут ніхто не описав алгоритм, але алгоритм Graphics Gems просто:

  1. Скориставшись вектором напрямку вашого променя, визначте, хто з 6-ти площин-кандидатів першим вдарив би . Якщо ваш (ненормалізований) вектор напрямку променів дорівнює (-1, 1, -1), то 3 площини, які можна вразити, становлять + x, -y та + z.

  2. З трьох площин-кандидатів знайдіть значення t для перетину для кожної. Прийміть площину, яка набирає найбільшу величину 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 за те, що насправді пояснив це (що теж із зображенням :)
legends2k

4

Це моє перехрестя 3D-променів / 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];
}

перетворюють це для єдності, і це було швидше , ніж bounds.IntersectRay вбудованої команди gist.github.com/unitycoder/8d1c2905f2e9be693c78db7d9d03a102
mgear

Як я можу інтерпретувати повернене значення? Це щось на зразок евклідової відстані між початком та точкою перетину?
Фердинанд Мютч

Яке значення має відстань до коробки?
jjxtra

1

Одне, що ви, можливо, захочете дослідити, - це розсіювання передньої та задньої панелей обмежувального поля в двох окремих буферах. Надайте значення x, y, z як rgb (це найкраще працює для обмежувального поля з одним кутом при (0,0,0) та протилежним у (1,1,1).

Очевидно, що це обмежене використання, але я вважаю, що це чудово для візуалізації простих томів.

Детальніше та код:

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


1

Ось рядок і код 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.
Я отримав цей код із книги "Виявлення зіткнення в реальному часі" Крістера Еріксона в розділі "5.3.3 Пересічний промінь або відрізок проти коробки"

// 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, який піклується про зменшення кількості підрозділів і тестів.
sam hocevar

0

Я здивований, побачивши, що ніхто не згадав про метод безрубанних плит Тавіаном

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.