デュアルコンターリング-特徴点の検索、法線オフ


9

このチュートリアルに従って、デュアルコンタリングを実装してい ますhttp://www.sandboxie.com/misc/isosurf/isosurfaces.html

私のデータソースはグリッド16x16x16です。私はこのグリッドを下から上、左から右、近くから遠くまで行き来しています。

グリッドのインデックスごとに、キューブ構造を作成します。

public Cube(int x, int y, int z, Func<int, int, int, IsoData> d, float isoLevel) {
            this.pos = new Vector3(x,y,z);
            //only create vertices need for edges
            Vector3[] v = new Vector3[4];
            v[0] = new Vector3 (x + 1, y + 1, z);
            v[1] = new Vector3 (x + 1, y, z + 1);
            v[2] = new Vector3 (x + 1, y + 1, z + 1);
            v[3] = new Vector3 (x, y + 1, z + 1);
            //create edges from vertices
            this.edges = new Edge[3];
            edges[0] = new Edge (v[1], v[2], d, isoLevel);
            edges[1] = new Edge (v[2], v[3], d, isoLevel);
            edges[2] = new Edge (v[0], v[2], d, isoLevel);
        }

グリッドをトラバースする方法により、4つの頂点と3つのエッジのみを確認する必要があります。この図では、頂点2、5、6、7が私の頂点0、1、2、3に対応し、エッジ5、6、10が私のエッジ0、1、2に対応しています。 グリッドキューブ

エッジは次のようになります。

    public Edge(Vector3 p0, Vector3 p1, Func<int, int, int, IsoData> d, float isoLevel) {
        //get density values for edge vertices, save in vector , d = density function, data.z = isolevel 
        this.data = new Vector3(d ((int)p0.x, (int)p0.y, (int)p0.z).Value, d ((int)p1.x, (int)p1.y, (int)p1.z).Value, isoLevel);
        //get intersection point
        this.mid = LerpByDensity(p0,p1,data);
        //calculate normals by gradient of surface
        Vector3 n0 = new Vector3(d((int)(p0.x+1),   (int)p0.y,      (int)p0.z       ).Value - data.x,
                                 d((int)p0.x,       (int)(p0.y+1),  (int)p0.z       ).Value - data.x,
                                 d((int)p0.x,       (int)p0.y,      (int)(p0.z+1)   ).Value - data.x);

        Vector3 n1 = new Vector3(d((int)(p1.x+1),   (int)p1.y,      (int)p1.z       ).Value - data.y,
                                 d((int)p1.x,       (int)(p1.y+1),  (int)p1.z       ).Value - data.y,
                                 d((int)p1.x,       (int)p1.y,      (int)(p1.z+1)   ).Value - data.y);
        //calculate normal by averaging normal of edge vertices
        this.normal = LerpByDensity(n0,n1,data);
    }

次に、すべてのエッジの符号の変化をチェックします。エッジが存在する場合は、周囲のキューブを見つけて、それらのキューブの特徴点を取得します。

これで機能ポイントを立方体の中心に設定すると機能し、ブロック状のマインクラフトの外観になります。しかし、それは私が望んでいることではありません。

特徴点を見つけるために、私はこの投稿のようにそれをしたかった:https : //gamedev.stackexchange.com/a/83757/49583

基本的に、頂点はセルの中心から開始します。次に、頂点から各平面に取られたすべてのベクトルを平均し、その結果に沿って頂点を移動し、この手順を固定回数繰り返します。結果に沿って約70%移動すると、最小限の反復で安定します。

だから私は飛行機のクラスを得ました:

private class Plane {

        public Vector3 normal;
        public float distance;

        public Plane(Vector3 point, Vector3 normal) {
            this.normal = Vector3.Normalize(normal);
            this.distance = -Vector3.Dot(normal,point);
        }

        public float Distance(Vector3 point) {
            return Vector3.Dot(this.normal, point) + this.distance;
        }

        public Vector3 ShortestDistanceVector(Vector3 point) {
            return this.normal * Distance(point);
        }
 }

そして、特徴点を取得する関数。ここでは、各エッジに1つずつ、3つの平面を作成し、中心までの距離を平均化します。

 public Vector3 FeaturePoint {
            get {
                Vector3 c = Center;
 //                 return c; //minecraft style

                Plane p0 = new Plane(edges[0].mid,edges[0].normal);
                Plane p1 = new Plane(edges[1].mid,edges[1].normal);
                Plane p2 = new Plane(edges[2].mid,edges[2].normal);

                int iterations = 5;
                for(int i = 0; i < iterations; i++) {
                    Vector3 v0 = p0.ShortestDistanceVector(c);
                    Vector3 v1 = p1.ShortestDistanceVector(c);
                    Vector3 v2 = p2.ShortestDistanceVector(c);
                    Vector3 avg = (v0+v1+v2)/3;
                    c += avg * 0.7f;
                }

                return c;
            }
        }

しかし、機能していません。頂点はあちこちにあります。エラーはどこにありますか?エッジ頂点の法線を平均化することで、実際にエッジ法線を計算できますか?データソースとして整数グリッドしかないので、エッジの中点で密度を取得できません...

編集:私はここでもhttp://www.mathsisfun.com/algebra/systems-linear-equations-matrices.html を見つけましたこれは、行列を使用して3つの平面の交差を計算できることです 少なくともそれが私が理解した方法です。このメソッドを作成しました

 public static Vector3 GetIntersection(Plane p0, Plane p1, Plane p2) {              
            Vector3 b = new Vector3(-p0.distance, -p1.distance, -p2.distance);

            Matrix4x4 A = new Matrix4x4 ();
            A.SetRow (0, new Vector4 (p0.normal.x, p0.normal.y, p0.normal.z, 0));
            A.SetRow (1, new Vector4 (p1.normal.x, p1.normal.y, p1.normal.z, 0));
            A.SetRow (2, new Vector4 (p2.normal.x, p2.normal.y, p2.normal.z, 0));
            A.SetRow (3, new Vector4 (0, 0, 0, 1));

            Matrix4x4 Ainv = Matrix4x4.Inverse(A);

            Vector3 result = Ainv * b;
            return result;
        }

このデータで

        Plane p0 = new Plane (new Vector3 (2, 0, 0), new Vector3 (1, 0, 0));
        Plane p1 = new Plane (new Vector3 (0, 2, 0), new Vector3 (0, 1, 0));
        Plane p2 = new Plane (new Vector3 (0, 0, 2), new Vector3 (0, 0, 1));

        Vector3 cq = Plane.GetIntersection (p0, p1, p2);

(2.0、2.0、2.0)で交差を計算するので、正しく動作すると思います。それでも、正しい頂点ではありません。僕の常態だと思う。


UnityにはすでにPlane構造が定義されています(ここを参照)。これには、指定したメソッドが既に定義されています(PlaneC#拡張メソッドを使用して構造に追加できる最短のベクトルメソッドを除く)。GetDistanceToPointメソッドの代わりにメソッドを使用できますDistance
EvilTak 2015年

コメントをいただきありがとうございます。私は実装をUnity実装に置き換え、この関数を使用しましたprivate Vector3 shortestDistanceVector(Plane p、Vector3 point){return p.GetDistanceToPoint(point)* p.normal; また、ランダムな頂点のみを取得します。私の法線は完全にオフになっていると思います。また、編集を追加しました。2番目の方法を試しました。おそらく、それを見て、私がそこで何をしたのか教えてください。
ElDuderino 2015年

2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?-私は間違っているかもしれませんが、法線を取得するために補間しないでくださいというアドバイスを他の場所で見たと思います-それらはうまく補間しないだけです。顔ごとに計算し、それはより安全です。実際には、最初に最小のテストケースを作成して、法線の計算が正しいことを確認する必要があります。次に、これを続行します。
エンジニア、

しかし、法線を取得した後でのみ面を取得します。平面を作成し、面から頂点の頂点を取得するには、法線が必要です。そして、現在の構造で述べたように、エッジ頂点でのみデータにインデックスを付けることができます。またはあなたの話している顔は何ですか?
ElDuderino 2015年

@ElDuderino面はメッシュの面(または三角形)に似ていますが、データからそれを取得する方法はわかりません。エッジの代わりに三角形を生成できれば、通常の計算は本当に簡単になります。
EvilTak 2015年

回答:


1

まず、法線が前方/後方/中央の差異から計算される場合、法線はまったく問題ありません。問題は、FeaturePoint関数で中心点を誤った方向に移動したため、最小値から遠ざかっていることです。

Vector3 c = Center;
Plane p0 = new Plane(edges[0].mid,edges[0].normal);
Plane p1 = new Plane(edges[1].mid,edges[1].normal);
Plane p2 = new Plane(edges[2].mid,edges[2].normal);

int iterations = 5;
for(int i = 0; i < iterations; i++) {
    Vector3 v0 = p0.GetDistanceToPoint(c) * edges[0].normal;
    Vector3 v1 = p1.GetDistanceToPoint(c) * edges[1].normal;
    Vector3 v2 = p2.GetDistanceToPoint(c) * edges[2].normal;
    Vector3 avg = (v0+v1+v2)/3;
    c -= avg * 0.7f; // Error was here!
}
return c;

これは、コードがポイントに収束せず、ボクセルボックスから飛び出すために発生しました。誰かがデュアル輪郭を説明できるのかどうかわかりませんかは、点が平面に投影される投影アプローチを使用することを目的としていました。

distance = Vector3.Dot(point - origin, normal);
projectedPoint = point - distance * normal;

しかし、それは同じ方法です。プロジェクションを元のコードに書き換えると、次のような結果になります。

    Vector3 v0 = c - p0.GetDistanceToPoint(c) * edges[0].normal;
    Vector3 v1 = c - p1.GetDistanceToPoint(c) * edges[1].normal;
    Vector3 v2 = c - p2.GetDistanceToPoint(c) * edges[2].normal;
    c = (v0+v1+v2)/3;

これは次のように書き換えることができます。

    Vector3 v0 = p0.GetDistanceToPoint(c) * edges[0].normal;
    Vector3 v1 = p1.GetDistanceToPoint(c) * edges[1].normal;
    Vector3 v2 = p2.GetDistanceToPoint(c) * edges[2].normal;
    c = c - (v0+v1+v2)/3;

したがって、最初のコードになります。3つの非平面に点を投影すると、図に示すように各面から点までの距離が最小になるため、点は徐々に最小に向かって収束します。

赤い点は特徴点を示し、青い線は法線を示し、紫色の点は平面に投影された点を示します。また、係数0.7を使用しないと収束が速くなるため、係数0.7を使用する必要はありません。この方法を使用する場合、交差しない平面がある場合、アルゴリズムが機能しない可能性があることに注意してください。


ねえ、2年後に答えを得るのは素晴らしいです:)解決策を見つけることができなかったので、このプロジェクトを停止しましたが、この知識を持って再検討し、それがどのように進んだかを知らせます。それまでは+1してください。
ElDuderino 2017年

驚くばかり!お役に立ててよかったです。それがあなたのために働くかどうか私に知らせてください。
Tim Rolff、2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.