双重轮廓-查找特征点,法线关闭


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定义了一个结构(请参阅此处),该结构具有您已经定义的方法(最短向量方法除外,您可以Plane使用C#扩展方法将其添加到结构中)。您可以使用GetDistanceToPoint方法代替您的Distance方法。
EvilTak 2015年

感谢您的评论,我用Unity实现替换了我的实现,并使用了此函数private Vector3 shortestDistanceVector(Plane p,Vector3 point){return p.GetDistanceToPoint(point)* p.normal; 我也只得到随机的顶点。我怀疑我的正常情况完全消失了。我还添加了一个编辑,尝试了第二种方法,也许您可​​以看看它,然后告诉我我在哪里做错了。
ElDuderino 2015年

2
Can I actually calculate the edge normal by averaging the normal of the edge vertices?-我可能会弄错了,但是我想我在其他地方也看到过建议,说不要为了得到法线而进行插值-它们只是插值不好。计算每张脸,这是更安全的。确实,您应该首先构造一个最小测试用例,以确保法线计算正确。然后继续前进。
工程师

但是只有在有了法线后才能得到这些面,我需要法线来创建这些平面并从这些平面中获取这些面的顶点。就像我目前的结构所说的那样,我只能在边缘顶点索引我的数据。还是您在说什么面孔?
ElDuderino

@ElDuderino面像网格的面(或三角形),但我不知道如何从数据中获取。如果您可以生成三角形而不是边缘,那么正常的计算将变得非常容易。
EvilTak

Answers:


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,因为如果没有系数,它将收敛更快。如果您使用此方法,请注意,如果平面不相交,则该算法可能无法正常工作。


嘿,在2年后得到解答的真棒:)我从未找到解决方案,所以我停止了这个项目,但是我将以这种知识重新审视它,并让您知道它的进行情况。然后进行+1直到。
ElDuderino

太棒了!很高兴可以为您服务。请让我知道这对你有没有用。
蒂姆·罗夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.