WebGL - 在平面上显示球体

时间:2015-07-31 11:32:34

标签: opengl-es glsl webgl shader vertex-shader

我想在一个球体上显示一个像是在飞机上的图像。 这个操作的一个例子是Mercatore投影,地球地图"展开"来自这个星球。 为了更好地解释自己,在球体上有一个平方纹理 - 不是在整个球体上,而是在它的一部分上 - 我想在平面上显示在球体上看到这个纹理的结果。 我发现了这个,已经: How do I 'wrap' a plane over a sphere with three.js?

但是我想用着色器来做,因为它可能是最有效的,但可能也是最困难的。我在找到合适的配方时遇到了问题。它有任何数学框架吗?

1 个答案:

答案 0 :(得分:1)

您应该指定您真正想要的投影。弯曲表面有许多种方法(不仅仅适用于球体)。你的问题是这种变换的反转,所以首先是直接投影(平面 - >球面)。我使用这两个(两者都用于特定目的):

projections

  1. 球体上方区域中间的距离与平面上的距离相匹配

    这用于修正曲面上的纹理,例如眼镜上的装饰品dekors ......

  2. 球体上与视轴的垂直距离与平面上的距离相匹配

    所以,如果从视轴看,你会在球体和平面上看到相同的图像,只需设置坐标系,Z轴是观察方向,x,y轴对应于您的2D平面轴。然后只计算z坐标以匹配球体表面

  3. 我想你想要第一个选项

    因此计算中间点(x0,y0)作为边界框的中心或均匀间隔的点平均点。通过ang 以弧度为单位计算每个点和坐标(从中间点)的atan2 !!!

    然后计算dx,dy并将2D坐标计算为(x,y)=(x0+dx,y0+dy)

    这里的结果示例(我将其用于任何类型的曲率):

    example

    <强> [注释]

    还有另一种基于光线投射的方法,可能还有更多......

    [edit1] C ++示例

    为您打造小型C ++课程:

    //---------------------------------------------------------------------------
    #include <Math.h>
    class sphere_projection
        {
    public:
        float x0,y0,z0,r0;  // 3D sphere
        float u0,v0;        // mid point of 2D image
        float m;            // scale 2D image
        int   mode;         // which projection type
        sphere_projection()
            {
            x0=0.0; y0=0.0; z0=0.0; r0=1.0;
            u0=0.0; v0=0.0; m=1.0;
            mode=1;
            }
        void uv2xyz(float &x,float &y,float &z,float u,float v)
            {
            if (mode==1)
                {
                float a,b;
                // 2D position scaled around midpoint and converted from arclength to angle
                u=(u-u0)*m/r0;
                v=(v-v0)*m/r0;
                // correct on radius distrotion in both axises
                a=u/cos(v);
                b=v/cos(u);
                // compute the 3D cartesian point on surface
                z=z0+(r0*cos(b)*cos(a));
                x=x0+(r0*cos(b)*sin(a));
                y=y0+(r0*sin(b));
                }
            if (mode==2)
                {
                // 2D position scaled around midpoint
                x=(u-u0)*m;
                y=(v-v0)*m;
                // compute the 3D cartesian point on surface
                x=x0+x;
                y=y0+y;
                z=z0+sqrt(r0*r0-x*x-y*y);
                }
            }
        void uv2xy (float &x,float &y,         float u,float v)
            {
            if (mode==1)
                {
                float a,b,z;
                // 2D position scaled around midpoint and converted from arclength to angle
                a=(u-u0)*m/r0;
                b=(v-v0)*m/r0;
                // correct on radius distrotion in both axises and convert back to 2D position
                x=u0+(a*r0/(m*cos(b)));
                y=v0+(b*r0/(m*cos(a)));
                }
            if (mode==2)
                {
                float z;
                // 2D position scaled around midpoint + Z axis
                x=(u-u0)*m;
                y=(v-v0)*m;
                z=sqrt(r0*r0-x*x-y*y);
                // compute arclengths and convert back to 2D position
                x=u0+(r0*atan2(x,z)/m);
                y=v0+(r0*atan2(y,z)/m);
                }
            }
        };
    //---------------------------------------------------------------------------
    

    这是如何使用它(在OpenGL中渲染):

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glTranslatef(0.0,+2.5,-20.0);
    
    static float ang=0.0; ang+=2.5;
    float x,y,z,u,v,d=0.2;
    sphere_projection sp;
    sp.x0=0.0;
    sp.y0=0.0;
    sp.z0=0.0;
    sp.r0=1.5;
    sp.u0=0.0;
    sp.v0=0.0;
    sp.m =0.5;
    
    
    for (sp.mode=1;sp.mode<=2;sp.mode++)
        {
        // original 2D grid
        glMatrixMode(GL_MODELVIEW);
        glTranslatef(-5.0,0.0,0.0);
        glColor3f(1.0f, 1.0f, 1.0f);
        for (u=d-1.0;u<=1.0;u+=d)
         for (v=d-1.0;v<=1.0;v+=d)
            {
            glBegin(GL_LINE_LOOP);
            glVertex3f(u-d,v-d,0.0);
            glVertex3f(u-d,v  ,0.0);
            glVertex3f(u  ,v  ,0.0);
            glVertex3f(u  ,v-d,0.0);
            glEnd();
            }
        // sphere mapped corrected
        glMatrixMode(GL_MODELVIEW);
        glTranslatef(+5.0,0.0,0.0);
        glPushMatrix();
        glRotatef(ang,0.0,1.0,0.0);
        glColor3f(1.0f, 0.0f, 0.0f);
        for (u=d-1.0;u<=1.0;u+=d)
         for (v=d-1.0;v<=1.0;v+=d)
            {
            glBegin(GL_LINE_LOOP);
            sp.uv2xyz(x,y,z,u-d,v-d); glVertex3f(x,y,z);
            sp.uv2xyz(x,y,z,u-d,v  ); glVertex3f(x,y,z);
            sp.uv2xyz(x,y,z,u  ,v  ); glVertex3f(x,y,z);
            sp.uv2xyz(x,y,z,u  ,v-d); glVertex3f(x,y,z);
            glEnd();
            }
        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();
    
        // sphere mapped corrected
        glMatrixMode(GL_MODELVIEW);
        glTranslatef(+5.0,0.0,0.0);
        glColor3f(0.0f, 0.0f, 1.0f);
        for (u=d-1.0;u<=1.0;u+=d)
         for (v=d-1.0;v<=1.0;v+=d)
            {
            glBegin(GL_LINE_LOOP);
            sp.uv2xy(x,y,u-d,v-d); glVertex3f(x,y,0.0);
            sp.uv2xy(x,y,u-d,v  ); glVertex3f(x,y,0.0);
            sp.uv2xy(x,y,u  ,v  ); glVertex3f(x,y,0.0);
            sp.uv2xy(x,y,u  ,v-d); glVertex3f(x,y,0.0);
            glEnd();
            }
    
        glTranslatef(-5.0,-5.0,0.0);
        }
    
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glFlush();
    SwapBuffers(hdc);
    

    结果如下:

    example

    • sp.uv2xy将2D(u,v)图像坐标转换为投影校正后的2D(x,y)坐标(图像)
    • sp.uv2xyz将2D(u,v)图像坐标转换为投影校正3D(x,y,x)坐标(x,y轴与屏幕x,y轴对应的球面)
    • sp.mode {1,2}选择您想要使用的投影类型
    • sp.u0,v0,m选择投影图像中点和比例
    • sp.x0,y0,z0,r0定义您要投射的球体

    [edit2] Sphere EquirectangularProjection

    此一个2D u,v坐标不需要进行校正,直接转换为球面角a=long,b=lat,因此范围u,v中的<0,+1>

    a=x*2.0*M_PI; b=(y-0.5)*M_PI;
    

    然后3D坐标只是球形变换:

    x=x0+(r0*cos(b)*cos(a));
    y=y0+(r0*cos(b)*sin(a));
    z=z0+(r0*sin(b));
    

    Sphere_EquirectangularProjection

    如果你想要反向变换谷球坐标系