在OpenGL中生成Superellipsoid的表面

时间:2015-02-03 14:57:24

标签: c++ opengl math geometry

我正在尝试在OpenGL中生成Superellipsoid的表面。 由于该表面可以使用参数方程表示:

enter image description here

我在这个表面上生成一定数量的点,改变参数u v并使用看起来像这样的函数将它们放入数组中:

float du = 1.0/(float)(u_max-1);
float dv = 1.0/(float)(v_max-1);
int u, v;

vertices.reserve(u_max*v_max);
normals.reserve(u_max*v_max);
texcoords.reserve(u_max*v_max);

for(u = 0; u < u_max; u++)
for(v = 0; v < v_max; v++)
{
    texcoords.emplace(texcoords.end(),vec2(u*du,v*dv));

    float cos_v_dv=cos(2*M_PI*v*dv);
    float sin_v_dv=sin(2*M_PI*v*dv);
    float sin_u_du=sin(M_PI*u*du);
    float cos_u_du=cos(M_PI*u*du);

    // Parametric equation of the surface
    float x=A*sgn(cos_v_dv)*sgn(sin_u_du)*pow(std::abs(cos_v_dv),n)*pow(std::abs(sin_u_du),m);
    float y=B*sgn(sin_v_dv)*sgn(sin_u_du)*pow(std::abs(sin_v_dv),n)*pow(std::abs(sin_u_du),m);
    float z=C*sgn(cos_u_du)*pow(std::abs(cos_u_du),m);
    vertices.emplace(vertices.end(),x,y,z);

    // Derivative with respect to u
    float dx_du=A*sgn(cos_v_dv)*cos_u_du*pow(std::abs(cos_v_dv),n)*pow(std::abs(sin_u_du),m-1);
    float dy_du=B*sgn(sin_v_dv)*cos_u_du*pow(std::abs(sin_v_dv),n)*pow(std::abs(sin_u_du),m-1);
    float dz_du=-C*sin_u_du*pow(std::abs(cos_u_du),m-1);

    // Derivative with respect to v
    float dx_dv=-A*sgn(sin_u_du)*pow(std::abs(sin_u_du),m)*sin_v_dv*pow(std::abs(cos_v_dv),n-1);
    float dy_dv=B*sgn(sin_u_du)*pow(std::abs(sin_u_du),m)*cos_v_dv*pow(std::abs(sin_v_dv),n-1);
    // derivative of z with respect to v is 0

    //Crossing the tangent vectors to get the normal
    vec3 normal(-dz_du*dy_dv,dx_dv*dz_du,dx_du*dy_dv-dx_dv*dy_du);
    normal.normalize();
    normals.push_back(normal);
}

然后我为连接相邻点的Quads生成索引,如:

indices.reserve(u_max * v_max * 4);
for(u = 0; u < u_max-1; u++) 
    for(v = 0; v < v_max-1; v++) 
    {
        indicies.push_back(u* v_max + v);
        indicies.push_back(u* v_max + (v+1));
        indicies.push_back((u+1)* v_max + (v+1));
        indicies.push_back((u+1)* v_max + v);
    }

但结果非常糟糕......: enter image description here

大多数情况下,曲面细分在某些位置非常差,而在其他位置则太多。 还有一些奇怪的黑点可能是由不正常的原因引起的。首先我认为我生成法线的方法是有缺陷的。我曾经通过交叉两个切线向量来计算法线,我通过区分顶点来得到法线如上面的代码所示,例如:

然后我决定通过交叉切线来计算法线,我通过减去两个相邻顶点的坐标得到切线。 结果与那里的黑点完全相同,在某种意义上,闪电看起来并不那么好。

显然有一种非常好的方法可以做到这一点,因为维基文章显示了这个表面的非常好的图像。我可能正在做一些非常愚蠢的事情。

所以我想我的问题是:有没有一种很好的方法来生成这个对象的表面?我怎样才能避免现在遇到的问题?

2 个答案:

答案 0 :(得分:2)

我认为您的问题很可能是由于您的采样以及由于参数曲面本身的奇异性造成的。

您可以在参数空间(您的u和v)中定期对曲面进行采样,从而在几何空间(您显示的图像)中进行非均匀采样。

极点的奇点可能会给你你提到的阴影文物。在参数空间和几何空间中,您对法线的计算都会受到限制,导致您的法线出现“极端”错误。

维基百科上的图像可能是使用光线跟踪和表面的完全隐式形式创建的。您可以尝试做同样的事情以获得更好的曲面法线:不是在参数值(u,v)上区分显式参数方程,而是在位置(x,y,z)区分隐式方程。 / p>

另一方面,您也可以简单地尝试明确给定的法线 http://www.gamedev.net/page/resources/_/technical/opengl/superquadric-ellipsoids-and-toroids-opengl-lig-r1172

答案 1 :(得分:2)

绘制球体时会遇到类似的问题,球体的任何参数化都会至少有一个单一的位置,通过北极的纬度和经度参数进行参数化。

此方法的一个方法是将曲面分成两个或多个沿曲线相交的贴片。有几种方法可以让你有两个半球,沿着赤道相遇。相当不错的方法是采用单位立方体并沿半径向量投影到单位球体。一旦我们得到了球体的一个点,我们就可以得到极坐标并使用它们来找到超椭圆体上的点。

如果u,v是曲面的两个参数,则0 <= u&lt; = 1,0&lt; = v&lt; = 1立方体的一个面将由x = 2u-1,y = 2v-1给出,z = 1。我们可以通过除以长度l = sqrt(x ^ 2 + y ^ 2 + z ^ 2),x1 = x / l,y1 = y / l,z1 = z / l来找到单位球面上的点。现在找到极坐标th = atan2(y1,x1),phi = asin(z1)并使用它们来找到超椭圆上的坐标。

我已将其作为JavaScript中的小提琴实现。 http://jsfiddle.net/SalixAlba/n1hjm35n/使用OpenGL实现起来相当简单。

Super ellipsoid using cubic mesh

// Auxiliary cos function  
function auxC(w,m) {
    var c = Math.cos(w);
    return sign(c) * Math.pow(Math.abs(c),m);
}

// Auxiliary sin function  
function auxS(w,m) {
    var s = Math.sin(w);
    return sign(s) * Math.pow(Math.abs(s),m);
}

// Given a point on a sphere find the corresponding point on the super-ellipsoid
function SEvec(x,y,z) {
    var th = Math.atan2(y,x);
    var phi = Math.asin(z);
    var xx = A * auxC(phi, 2/ t) * auxC(th, 2/r);
    var yy = B * auxC(phi, 2/ t) * auxS(th, 2/r);
    var zz = C * auxS(phi, 2/ t);
    console.log(x,y,z,xx,yy,zz);
    return new THREE.Vector3(xx*100,yy*100,zz*100);
}

// Generate points for the first face
function sf1(u,v) {
    var x = 2*u-1;
    var y=  2*v-1;
    var z= 1;
    var l = Math.sqrt(x*x+y*y+z*z);
    return SEvec( x/l,y/l,z/l);
}

// second face
function sf2(u,v) {
    var x = 2*u-1;
    var y=  2*v-1;
    var z= -1;
    var l = Math.sqrt(x*x+y*y+z*z);
    return SEvec( x/l,y/l,z/l);
}
function sf3(u,v) {
    var x = 2*u-1;
    var z=  2*v-1;
    var y= 1;
    var l = Math.sqrt(x*x+y*y+z*z);
    return SEvec( x/l,y/l,z/l);
}
function sf4(u,v) {
    var x = 2*u-1;
    var z=  2*v-1;
    var y= -1;
    var l = Math.sqrt(x*x+y*y+z*z);
    return SEvec( x/l,y/l,z/l);
}
function sf5(u,v) {
    var z = 2*u-1;
    var y=  2*v-1;
    var x= 1;
    var l = Math.sqrt(x*x+y*y+z*z);
    return SEvec( x/l,y/l,z/l);
}
function sf6(u,v) {
    var z = 2*u-1;
    var y=  2*v-1;
    var x= -1;
    var l = Math.sqrt(x*x+y*y+z*z);
    return SEvec( x/l,y/l,z/l);
}

我没有计算法线,但你的代码应该有效。当你在北极或南极时,唯一的问题就发生了。在这里你可以使用向量(0,0,1),(0,0,-1),或者你可以为步数选择奇数值,因此极点永远不会包含在网格中。

使用立方体作为基础可以很容易地沿着边缘匹配相应的补丁。