Search code examples
unity-game-enginemathshadergismap-projections

Shader that transforms a mercator projection to equirectangular?


I am trying to make a shader in Unity taking a mercator projection texture as a source and converting it to an equirectangular projection texture.

Input example:

enter image description here

enter image description here

Output example:

enter image description here

enter image description here

This example does the opposite with an equirectangular as source.

If you look at the source of the above example:

 // mercator
 float latClamped = clamp(lat, -1.4835298641951802, 1.4835298641951802);
 float yMerc = log(tan(PI / 4.0 + latClamped / 2.0)) / PI2;
 float xMerc = xEqui / 2.0;
 vec4 mercatorPos = vec4(xMerc, yMerc, 0.0, 1.0);

Can anyone help to reverse this so I'm able to go from a mercator map as a source to equirectangular (or even better, azimuthal).

Looking for a way to do 2D texture deformations going from x/y to longitude(x)/latitude(y) and back.

I appreciate your input.


Solution

  • If you want to output the equirectangular projection, you need to convert from equirectangular coordinates to mercator coordinates and then sample the mercator projection at those coordinates.

    This is what it would look like in a fragment shader from uvs:

    //uv to equirectangular
    float lat = (uv.x) * 2 * PI;    // from 0 to 2PI
    float lon = (uv.y - .5f) * PI;  // from -PI to PI
    
    // equirectangular to mercator
    float x = lat;
    float y = log(tan(PI / 4. + lon / 2.));
    
    // bring x,y into [0,1] range
    x = x / (2*PI);
    y = (y+PI) / (2*PI);
    
    // sample mercator projection
    fixed4 col = tex2D(_MainTex, float2(x,y));
    

    The same thing applies to the azimuthal projection: You can go from azimuthal coordinates -> equirectangular -> mercator and sample the image. Or you can find a formula to go directly from azimuthal -> mercator. The wiki pages have a bunch of formulas to go back and forth between projections.

    Here is a full shader to play around with. Input is a mercator projection and outputs a equirectangular or azimuthal projection (choose from the dropdown menu) enter image description here

    Shader "Unlit/NewUnlitShader 1"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
            [Enum(Equirectangular,0,Azimuthal,1)]
            _Azimuthal("Projection", float) = 0
    
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag           
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f
                {
                    float2 uv : TEXCOORD0;                
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
                float _Azimuthal;
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    #define PI 3.141592653589793238462f
    #define PI2 6.283185307179586476924f
    
                float2 uvToEquirectangular(float2 uv) {
                    float lat = (uv.x) * PI2;   // from 0 to 2PI
                    float lon = (uv.y - .5f) * PI;  // from -PI to PI
                    return float2(lat, lon);
                }
    
                float2 uvAsAzimuthalToEquirectangular(float2 uv) {                  
                    float2 coord = (uv - .5) * 4; 
    
                    float radius = length(coord);
                    float angle = atan2(coord.y, coord.x) + PI;
    
                    //formula from https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection
                    float lat = angle;
                    float lon = 2 * acos(radius / 2.) - PI / 2;
                    return float2(lat, lon);
                }           
    
                fixed4 frag(v2f i) : SV_Target
                {
                    // get equirectangular coordinates
                    float2 coord = _Azimuthal ? uvAsAzimuthalToEquirectangular(i.uv) : uvToEquirectangular(i.uv);
    
                    // equirectangular to mercator
                    float x = coord.x;
                    float y = log(tan(PI / 4. + coord.y / 2.));
                    // brin x,y into [0,1] range
                    x = x / PI2;
                    y = (y + PI) / PI2;                 
    
                    fixed4 col = tex2D(_MainTex, float2(x,y));
    
                    // just to make it look nicer
                    col = _Azimuthal && length(i.uv*2-1) > 1 ? 1 : col;
    
                    return col;
                }
                ENDCG
            }
        }
    }