最も効率的なAABB対レイ衝突アルゴリズム


53

AABB対レイ衝突検出用の既知の「最も効率的な」アルゴリズムはありますか?

私は最近、ArvoのAABB対Sphere衝突アルゴリズムに出くわしましたが、これに似た注目すべきアルゴリズムがあるのではないかと考えています。

このアルゴリズムの条件が必要なのは、光線の原点から衝突点までの距離の結果を照会するオプションが必要だということです。これを言ったが、距離を返さない別のより高速なアルゴリズムがある場合、そうするものを投稿することに加えて、そのアルゴリズムを投稿することも実際に非常に役立ちます。

また、関数の戻り引数が何であるか、そしてそれを使用して距離または「衝突なし」の場合を返す方法も述べてください。たとえば、距離の出力パラメータとブール値の戻り値はありますか?または、距離のあるフロートを返すか、衝突しない場合は-1の値を返しますか?

(知らない人のために:AABB = Axis Aligned Bounding Box)


私は間違っているかもしれませんが、このアルゴリズムではまだ誤検知が発生すると思います。3軸をチェックするときにすべてのコーナーが同じ側にある場合、衝突はありません。しかし、3つの軸すべてが両側に点を持ち、衝突がない状態を引き続き保持できるようです。一般的に、3つのスラブすべてで出入り距離が重なるかどうかを確認して、確実に確認します。Geometric toolsサイトからです。
スティーブH

なぜ距離クエリの条件が必要なのですか?距離が不要な場合にさらに高速なアルゴリズムがあれば、それについても知りたくないですか?
サムホセバル

まあ、いや、そうでもない。衝突が発生する距離を知る必要があります。
サーヤカロット

実際、あなたが正しいと思うので、質問を編集します。
サーヤカロット

4
あなたの他のスレッドに投稿したように、これらのタイプのアルゴリズムのための良いリソースがここにあります:realtimerendering.com/intersections.html
Tetrad

回答:


22

ジョン・アマナティデスとともにレイトレーサーで広く使用されているレイマーチング・アルゴリズム(DDA)を開発したアンドリュー・ウーは、グラフィックス・ジェムズ、1990、pp。395-396で公開された「高速レイボックス交差点」(代替ソースはこちら)を書きました。DDA(zacharmarzの回答を参照)のようにグリッド(ボクセルボリュームなど)を介して統合するために特別に構築されるのではなく、このアルゴリズムは、ほとんどの3Dで見られる典型的な多面体の世界のように、均等に細分されていない世界に特に適していますゲーム。

このアプローチは3Dのサポートを提供し、オプションで背面カリングを行います。このアルゴリズムは、DDAで使用されているのと同じ統合の原則から派生しているため、非常に高速です。詳細については、元のGraphics Gemsボリューム(1990)を参照してください。

ray-AABB専用のその他の多くのアプローチはrealtimerendering.comで見つけることができます

編集:GPUとCPUの両方で望ましい代替のブランチレスアプローチがここにあります


ああ!あなたは私にそれを打ち負かした、私は今朝それに出くわした。素晴らしい発見!
サーヤカロット

よろしくお願いします。また、この種のベースで見つけたアルゴリズムを比較することもお勧めします。(他にもこのような公式リストが他にもありますが、現時点では見つかりません。)
エンジニア


1
よくコメントされたWooのアルゴリズムの実装は、ここにあります
エンジニア

4
指定した2つのリンクは、それぞれ「Not found」および「Forbidden」エラーを生成します
...-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;

これがtrueを返す場合は交差し、falseを返す場合は交差しません。

同じレイを何度も使用する場合は、事前計算することができますdirfrac(交差テスト全体の除算のみ)。そして、それは本当に速いです。また、交差点までの光線の長さもあります(に格納されますt)。


変数名の意味のキーを提供することは可能でしょうか?
サーヤカロット

1
コメントに説明を追加しようとしました。したがって、「r」は光線、「r.dir」はその単位方向ベクトル、「r.org」は原点であり、光線を発射します。「dirfrac」は同じ最適化に使用できるため、最適化にすぎません。 (除算を行う必要はありません)、それは1 / r.dirを意味します。次に、「lb」は3つすべての座標が最小のAABBのコーナーであり、「rb」は正反対-最大の座標を持つコーナーです。出力パラメーター「t」は、原点から交差点までのベクトルの長さです。
-zacharmarz

関数定義はどのように見えますか?レイで衝突が発生した距離を知ることは可能ですか?
サーヤカロット

1
交差点を返すが、その交差点に負の数がある場合、アルゴリズムは何を意味しますか?tminは、負の数として返されることがあります。
サーヤカロット

1
ああ、それは原点が箱の中にあるときです
サーヤカロット

14

ここでアルゴリズムを説明した人はいませんが、Graphics Gemsアルゴリズムは単純です:

  1. 光線の方向ベクトルを使用して、6つの候補平面のうち3つが最初にヒットするかどうかを判断します。(正規化されていない)光線方向ベクトルが(-1、1、-1)の場合、ヒットする可能性のある3つの平面は+ x、-y、および+ zです。

  2. 3つの候補平面のうち、それぞれの交差点の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 14年

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
}

何であるtneartfar
tekknolagi

交差点は[tnear、tfar]の間です。
ジェローンバート

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

これを統一使用のために変換し、組み込みの境界よりも高速でした。IntersectRaygist.github.com
unitycoder

返された値をどのように解釈できますか?原点と交差点間のユークリッド距離のようなものですか?
フェルディナンド・ミュッチュ

ボックスまでの距離はどのくらいですか?
jjxtra

1

調査したいことの1つは、境界ボックスの前面と背面を2つの別々のバッファーでラスタライズすることです。x、y、z値をrgbとしてレンダリングします(これは、1つのコーナーが(0,0,0)にあり、反対側が(1,1,1)にある境界ボックスに最適です。

明らかに、これは使用が制限されていますが、単純なボリュームのレンダリングに最適であることがわかりました。

詳細とコード:

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


1

これが私が使ってきたLine対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ボックスに対する光線またはセグメントの交差」セクションの下にあるChrister Ericsonの本「Real-Time Collision Detection」から入手しました。

// 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です、はい?
サーヤカロット

これは2Dのみです、はい。また、コードはzacharmarzのようには考えられていません。zacharmarzは、分割とテストの数を減らすことに注意を払っています。
サムホセバー

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

光線の起点がAABB内にある場合に処理するために、@ zacharmarzの回答に追加しました。この場合、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.