4

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.

Rashed Hasan
  • 3,721
  • 11
  • 40
  • 82
bomanden
  • 314
  • 1
  • 2
  • 16

1 Answers1

3

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
        }
    }
}
Pluto
  • 3,911
  • 13
  • 21
  • 1
    thanks alot @Pluto !!, works perfectly. I only added the clamp back to avoid repeating. float yClamped = clamp(coord.y, -1.4835298641951802,1.4835298641951802); float y = log(tan(PI / 4. + yClamped / 2.)); – bomanden Jan 26 '20 at 17:39
  • @Pluto I've been trying to understand your code and your `//uv to equirectangular` section doesn't seem to do any transforming, it simply moves ranges from 0-1 to radians which leaves me wondering, how does this code stretch a mercator image to equirectangular? I've verified it actually does that I'm just struggling to understand where the "magic" math is. – Jacksonkr Jan 20 '22 at 16:28
  • my assumption is that the natural wrapping of an image around a sphere acts as a sort of mercator-to-equirectangular conversion (because an unwrapped sphere texture is 2x wide while being 1x tall and when wrapped the image appears scrunched together at the poles) – Jacksonkr Jan 20 '22 at 16:43