Search code examples
opengl-esglslwebglshadervertex-shader

WebGL - display a sphere on a plane


I would like to show an image like it was on a sphere - but on a plane. An example of this operation, would be the Mercatore projection, the map of the earth "unrolled" from the planet. To better explain myself, having a squared texture on a sphere - not on the WHOLE sphere, but on a part of it only - I would like to show on a plane the result of seeing this texture on the sphere. I found this, already: How do I 'wrap' a plane over a sphere with three.js?

But I would like to do it with shaders, because it might the most efficient, but probably also the most difficult. I have problems finding the right formula for it. There exists any mathematical framework for it?


Solution

  • You should specify what projection you really want. There are many approaches for curved surfaces (not just for spheres). Your problem is the inverse of such transform so first the direct projection (plane -> sphere surface). I use these two (both are used for specific purposes):

    projections

    1. distances from middle of area on the sphere match the distances on the plane

      this is used to correct textures on curved surfaces for example Ornament dekors on glasses etc...

    2. perpendicular distances to the view axis on the sphere match the distances on the plane

      so if you are looking from the view axis you see the same image on the sphere and on the plane just set the coordinate system so Z axis is the viewing direction and x,y axises are corresponding to your 2D plane axises. Then just compute z-coordinate to match sphere surface

    I think you want the first option

    so compute middle point (x0,y0) as center of bounding box or for evenly spaced point average point. Compute ang for each point and coordinate (from middle point) via atan2 in radians !!!

    Then compute dx,dy and compute 2D coordinates as (x,y)=(x0+dx,y0+dy)

    Here example of the result (I use this for any kind of curvature):

    example

    [Notes]

    There are also another approaches based on ray casting,and possibly much more ...

    [edit1] C++ example

    Busted small C++ class for you:

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

    This is how to use this (render in 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);
    

    This is the result:

    example

    • sp.uv2xy converts 2D (u,v) image coordinate to projection corrected 2D (x,y) coordinate (image)
    • sp.uv2xyz converts 2D (u,v) image coordinate to projection corrected 3D (x,y,x) coordinate (sphere surface where x,y axises corresponds with screen x,y axises)
    • sp.mode {1,2} selects whitch type of projection you want to use
    • sp.u0,v0,m selects the projection image mid point and scale
    • sp.x0,y0,z0,r0 defines the sphere on which you are projecting

    [edit2] Sphere EquirectangularProjection

    There is no correction needed for this one 2D u,v coordinate is directly converted to spherical angles a=long,b=lat so for u,v in range <0,+1>:

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

    Then the 3D coordinate is just spherical transformation:

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

    Sphere_EquirectangularProjection

    if you want the reverse transform google spherical coordinate system