1
votes

I want to project several equirectangular textures on a sphere (each texture contains only on earth continent kept at its original equirectangular shape, the rest is transparent). I want to scale the sphere so that the continents keep their surface/size and stay at their position without scaling with the sphere. All of this done in a Shader. Projector approaches are excluded as the continent/texture can map behind the frustum depending on it's original size or the sphere size.

I am now struggling with this since some days not being able to find the solution.

My approach is so far to resize the texture UV proportionally to the scaling of the sphere (so that it seems to keep its surface and position) but it does not work properly as it scales in the cartesian plane whereas it should be a scaling in equirectangular or radial coordinate system. And I don't know the maths to do that...

Maybe someone knows the maths to scale in equirectangular or radial coordinate systems ? Maybe I am just complicating things and there is something to do with world coords. I was also thinking to project equirectangular to gnomonic, scale, then re-project to equirectangular but I am pretty sure this is overkill and too approximate.

See the illustration. texture scaling on a sphere

Shader "Unlit/Equirectangular2SphereSimple"
{
    Properties
    {
        [NoScaleOffset] _FirstTex ("Equirectangular (First)", 2D) = "white" {}
        _Scale ("Scale", Range(0,10)) = 1.0
    }

    SubShader
    {
        Pass
        {
            Tags { "DisableBatching"="True" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            #include "UnityCG.cginc"

            sampler2D _FirstTex;
            half _Scale;

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 texcoord : TEXCOORD0;
            };

            inline float2 ToRadialCoords(float3 coords)
            {
                float3 normalizedCoords = normalize(coords);
                float latitude = acos(normalizedCoords.y);
                float longitude = atan2(normalizedCoords.z, normalizedCoords.x);
                float2 sphereCoords = float2(longitude, latitude) * (1/UNITY_PI);

                return float2(0.5F - sphereCoords.x * 0.5F, 1.0F - sphereCoords.y);
            }

            v2f vert(appdata_full v)
            {
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);
                //NOTE: we need to inverse x normal
                o.texcoord = v.vertex * half3(-1,_Scale,1); //scaling does not work if not at the pole

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 equirectangularUV = ToRadialCoords(i.texcoord);
                
                fixed4 first = tex2D(_FirstTex, equirectangularUV);

                return first.a != 0 ? first : fixed4(1,1,1,1);
            }
            ENDCG
        }
    }
    FallBack "VertexLit"
}

1

1 Answers

1
votes

Haven't tried it, but I think robust working solution will be:

  1. Move uv computation to vertex shader. Sphere model pos in unit space (xyz) -> longitude / latitude -> equirectangular coords.
  2. When drawing each continent choose direction of its center and pass it as uniform. Let's call it CenterDir.
  3. When drawing continent look at current sphere direction (Cur) and if it is in the same hemisphere as CenterDir - rotate Cur more from CenterDir or to it weighted by angle between Cur and CenterDir.
  4. Use skewed directions from 3 in uv computation from 1.

enter image description here

Also there is a good basis for prototyping this feature and checking other options in shadertoy.