이중 윤곽선-형상 점 찾기, 법선 꺼짐


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

그리고 특징점을 얻는 함수. 각 모서리마다 하나씩 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

귀하의 의견에 감사드립니다. 구현을 Unity 구현으로 대체 했으며이 함수를 사용하여 private Vector3 shortestDistanceVector (Plane p, Vector3 point) {return p.GetDistanceToPoint (point) * p.normal; } 또한 임의의 정점 만 얻습니다. 내 법선이 완전히 벗어난 것 같습니다. 또한 두 번째 방법을 시도한 편집을 추가했습니다. 어쩌면 그것을보고 내가 잘못한 것을 말해 줄 수 있습니다.
ElDuderino

2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?-나는 틀릴 수도 있지만, 법선을 얻기 위해 보간하지 말라고 다른 곳에서 조언을 보았을 것입니다. 얼굴 당 계산하면 더 안전합니다. 실제로 법선 계산이 올바른지 확인하기 위해 최소 테스트 사례를 먼저 구성해야합니다. 그런 다음 계속하십시오.
엔지니어

그러나 나는 법선을 가진 후에 만 ​​얼굴을 얻습니다. 평면을 만들고 얼굴의 정점을 얻으려면 법선이 필요합니다. 그리고 현재 구조에서 말했듯이 가장자리 정점에서만 데이터를 인덱싱 할 수 있습니다. 아니면 당신은 무슨 얼굴을 이야기하고 있습니까?
ElDuderino

@ElDuderino 메쉬의면 (또는 삼각형)과 같은면이지만 데이터에서 어떻게 얻을 수 있는지 모르겠습니다. 가장자리 대신 삼각형을 생성 할 수 있으면 일반 계산이 정말 쉬워집니다.
EvilTak

답변:


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;

따라서 첫 번째 코드가 생성됩니다. 세 개의 비평면 평면에 점을 투영하면 그림과 같이 각 평면에서 점까지의 거리가 최소화되므로 점을 최소로 천천히 수렴합니다.

빨간색 점은 특징점을 나타내고 파란색 선은 법선을 나타내고 자주색 점은 평면에 투영 된 점을 나타냅니다. 0.7없이 사용하면 더 빨리 수렴해야하므로 0.7을 사용하지 않아도됩니다. 이 방법을 사용하는 경우 교차 평면이 아닌 경우 알고리즘이 작동하지 않을 수 있습니다.


이봐, 2 년 후에 답을 얻는 것이 좋습니다. :) 해결책을 찾지 못했기 때문에이 프로젝트를 중단했지만이 지식으로 다시 방문하여 어떻게 진행되었는지 알려 드리겠습니다. 그때까지 +1하십시오.
ElDuderino

대박! 도와 드리겠습니다. 그것이 당신을 위해 작동하는지 알려주세요.
팀 롤프
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.