将平面网格变形为球体

时间:2016-09-11 20:34:25

标签: algorithm math projection mercator

美好的一天,

目前我正在尝试将飞机弯曲成球体。 我已经准备好Mercator projectionlla to ecef一起尝试了。所以结果推迟但不像球体(半球)。 最成功的变体看起来像这样(更像是一个帐篷,而不是半球):

enter image description here

Code for this tent (pastebin)。我正在使用three.js进行渲染。

所以我要求一些建议。我做错了什么?

1 个答案:

答案 0 :(得分:3)

使用球面坐标系统。角度long,lat是您平面中的 2D 线性u,v坐标,输出为 3D x,y,z

  1. 将平面网格的顶点(点)转换为球体表面

    我怀疑您在(x,y,z)格式中获得了积分,因此您需要先计算u,v。设U,V是位于平面上的垂直单位基矢量。它们可以通过在平面网格上减去2点,标准化尺寸并利用叉积来确保垂直度来获得。所以:

    u = `dot_product((x,y,z),U)` = x*U.x + y*U.y + z*U.z
    v = `dot_product((x,y,z),V)` = x*V.x + y*V.y + z*V.z
    

    现在转换为球体角度:

    long=6.2831853*u/u_size_of_mesh
    lat =3.1415926*v/v_size_of_mesh
    

    最后在球体表面上计算新的(x,y,z)

    x = R*cos(lat)*cos(long)
    y = R*cos(lat)*sin(long)
    z = R*sin(lat)
    
  2. <强>目

    平面网格必须具有足够密集的点结构(足够的三角形/面),否则球体看起来不应该如此。另一个问题是平面网格确实没有边缘和球体表面。 Ti可能会在平面边缘连接的球体表面上产生似乎/间隙。如果你想避免这种情况,可以在平面网格的两侧边缘之间添加面以填充间隙或完全抛弃网格,并使用均匀的网格点重新采样平面。

    如果您想完全重新采样网格,那么您可以做的最好是首先创建常规球形网格,例如:

    Sphere triangulation by mesh subdivision

    然后通过逆过程计算平面上的相应点到#1 ,这样你就可以插入点的其他参数(如颜色,纹理坐标等)

  3. <强> [注释]

    如果你想为它设置动画,那么只需在原始平面点P0(x,y,z)和相应的球面点P1(x,y,z)之间使用动画参数t=<0.0,1.0>进行线性插值,如下所示:

    P = P0 + (P1-P0)*t
    

    如果t=0那么输出是平面网格物体,如果t=1那么它就是球体。介于两者之间的任何地方都是包装过程,所以将t增加到1,步长足够小(如0.01)并在某个计时器中渲染...

    [Edit1] U,V基础向量

    想法很简单,获得2个非平行向量并改变其中一个向量,使其垂直于第一个但仍在同一平面上。

    1. 采取任何网状面

      例如三角形 ABC

    2. 在平面上计算2个非零非平行向量

      这很简单,只需减去任意两对顶点,例如:

      U.x=B.x-A.x
      U.y=B.y-A.y
      V.x=C.x-A.x
      V.y=C.y-A.y
      

      并将它们按单位大小划分,然后按大小划分

      ul=sqrt((U.x*U.x)+(U.y*U.y))
      vl=sqrt((V.x*V.x)+(V.y*V.y))
      U.x/=ul
      U.y/=ul
      V.x/=vl
      V.y/=vl
      
    3. 让它们垂直

      因此,将一个向量保留为原样(例如U)并计算另一个向量,使其垂直。为此,您可以使用跨产品。两个单位向量的交叉乘积是垂直于两者的新单位向量。 2种可能性中的哪一种仅取决于操作数的顺序((U x V) = - (V x U)),例如:

      // W is perpendicular to U,V
      W.x=(U.y*V.z)-(U.z*V.y)
      W.y=(U.z*V.x)-(U.x*V.z)
      W.z=(U.x*V.y)-(U.y*V.x)
      // V is perpendicular to U,W
      V.x=(U.y*W.z)-(U.z*W.y)
      V.y=(U.z*W.x)-(U.x*W.z)
      V.z=(U.x*W.y)-(U.y*W.x)
      

      W只是一个临时矢量(在图像中称为V')btw它是表面的法线向量。

      basis vectors

    4. 尺寸和对齐

      现在由于我没有关于你的网格的更多信息,我不知道它的形状,大小等......理想的情况是网格是矩形的,U,V向量与边缘对齐。在这种情况下,您只需按每个方向的矩形大小标准化坐标(如上图左侧)。

      如果您的网格不是这样的,并且您通过此方法从面部计算U,V,则结果可能根本不会与边缘对齐(它们可以旋转任意角度)...

      non aligned

      如果无法避免这种情况(通过选择角面),那么形状的角点将沿着每条边具有不同的坐标限制。您需要将它们插入或映射到正确的球面间隔,在某种意义上是完整的(因为我不知道你到底在做什么,所以可以更具体。

      对于几乎是矩形的形状,有时可以将边缘用作U,V,即使它们彼此不完全垂直。

    5. [Edit2] C ++示例

      如果你在Z轴上有一些完全对齐的方形网格物体(如高度图)那么这就是我将如何进行网格转换:

      //---------------------------------------------------------------------------
      struct _pnt // points
          {
          double xyz[3];
          _pnt(){}; _pnt(_pnt& a){ *this=a; }; ~_pnt(){}; _pnt* operator = (const _pnt *a) { *this=*a; return this; }; /*_pnt* operator = (const _pnt &a) { ...copy... return this; };*/
          };
      struct _fac // faces (triangles)
          {
          int i0,i1,i2;
          double nor[3];
          _fac(){}; _fac(_fac& a){ *this=a; }; ~_fac(){}; _fac* operator = (const _fac *a) { *this=*a; return this; }; /*_fac* operator = (const _fac &a) { ...copy... return this; };*/
          };
      // dynamic mesh
      List<_pnt> pnt;
      List<_fac> fac;
      //---------------------------------------------------------------------------
      void mesh_normals() // compute normals
          {
          int i;
          _fac *f;
          double a[3],b[3];
          for (f=&fac[0],i=0;i<fac.num;i++,f++)
              {
              vector_sub(a,pnt[f->i1].xyz,pnt[f->i0].xyz);    // a = pnt1 - pnt0
              vector_sub(b,pnt[f->i2].xyz,pnt[f->i0].xyz);    // b = pnt2 - pnt0
              vector_mul(a,a,b);                              // a = a x b
              vector_one(f->nor,a);                           // nor = a / |a|
              }
          }
      //---------------------------------------------------------------------------
      void mesh_init()    // generate plane mesh (your square with some z noise)
          {
          int u,v,n=40;   // 40x40 points
          double d=2.0/double(n-1);
          _pnt p;
          _fac f;
          Randomize();
          RandSeed=13;
          // create point list
          pnt.allocate(n*n); pnt.num=0;               // preallocate list size to avoid realocation
          for (p.xyz[0]=-1.0,u=0;u<n;u++,p.xyz[0]+=d) // x=<-1.0,+1.0>
           for (p.xyz[1]=-1.0,v=0;v<n;v++,p.xyz[1]+=d)// y=<-1.0,+1.0>
              {
              p.xyz[2]=0.0+(0.05*Random());           // z = <0.0,0.05> noise
              pnt.add(p);
              }
          // create face list
          vector_ld(f.nor,0.0,0.0,1.0);
          for (u=1;u<n;u++)
           for (v=1;v<n;v++)
              {
              f.i0=(v-1)+((u-1)*n);
              f.i1=(v-1)+((u  )*n);
              f.i2=(v  )+((u-1)*n);
              fac.add(f);
              f.i0=(v  )+((u-1)*n);
              f.i1=(v-1)+((u  )*n);
              f.i2=(v  )+((u  )*n);
              fac.add(f);
              }
          mesh_normals();
          }
      //---------------------------------------------------------------------------
      void mesh_sphere()  // convert to sphere
          {
          int i;
          _pnt *p;
          double u,v,lon,lat,r,R=1.0;
          // I know my generated mesh is aligned so:
          double U[3]={ 1.0,0.0,0.0 };
          double V[3]={ 0.0,1.0,0.0 };
          for (p=&pnt[0],i=0;i<pnt.num;i++,p++)   // process all points
              {
              // get the u,v coordinates
              u=vector_mul(p->xyz,U);
              v=vector_mul(p->xyz,V);
              // I also know the limits are <-1,+1> so conversion to spherical angles:
              lon=M_PI*(u+1.0);               // <-1.0,+1.0> -> <0.0,6.28>
              lat=M_PI*v*0.5;                 // <-1.0,+1.0> -> <-1.57,+1.57>
              // compute spherical position (superponate z to r preserve noise)
              r=R+p->xyz[2];
              p->xyz[0]=r*cos(lat)*cos(lon);
              p->xyz[1]=r*cos(lat)*sin(lon);
              p->xyz[2]=r*sin(lat);
              }
          mesh_normals();
          }
      //---------------------------------------------------------------------------
      void mesh_draw()    // render
          {
          int i;
          _fac *f;
          glColor3f(0.2,0.2,0.2);
          glBegin(GL_TRIANGLES);
          for (f=&fac[0],i=0;i<fac.num;i++,f++)
              {
              glNormal3dv(f->nor);
              glVertex3dv(pnt[f->i0].xyz);
              glVertex3dv(pnt[f->i1].xyz);
              glVertex3dv(pnt[f->i2].xyz);
              }
          glEnd();
          }
      //---------------------------------------------------------------------------
      

      我使用了我的动态列表模板,所以:

      • List<double> xxx;double xxx[];
      • 相同
      • xxx.add(5);5添加到列表的末尾
      • xxx[7]访问数组元素(安全)
      • xxx.dat[7]访问数组元素(不安全但快速直接访问)
      • xxx.num是数组的实际使用大小
      • xxx.reset()清除数组并设置xxx.num = 0
      • xxx.allocate(100)100
      • 预分配空间

      使用方法如下:

      mesh_init();
      mesh_sphere();
      

      结果如下:

      overview

      左边是生成的带有噪声的平面网格,右边是转换后的结果。

      代码反映了上面的所有内容+将Z - 噪声添加到球体半径以保留特征。以标准方式从几何体重新计算法线。对于整个TBN矩阵,您需要来自拓扑的连接信息并从中重新计算(或利用球体几何并从中使用TBN。

      顺便说一句,如果你想要映射到球体而不是网格转换,你应该看一下相关的 QA