I'm using a Web Mercator image to cover a sphere. My shader takes a plane with an image and turns it into sphere. The only issue is that The resulting sphere ends up with countries stretched (like the united states).
I've figured out that I can use an equlateral image of earth to get the desired effect of non-stretched countries
Question
For my project I only have web mercator imagery and I've been struggling with the math for getting my shader to show countries at their correct scale. How can I transform mercator lat lon to equilateral lat lon for writing to my shader ?
NOTE
Everything I would need seems to be on this question about mercator projection to equirectangular but for whatever reason it's just not clicking.
Some Code
plane script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SquareBender : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Vector3Int tileIndex = new Vector3Int(0, 0, 0);
Mesh mesh = GetComponent<MeshFilter>().mesh;
this.SetUpTileLonLats(mesh, tileIndex);
GetComponent<Renderer>().material.SetFloat("SphereRadius", 50);
}
// tileIndex is column/row/zoom of current tile
// uv is relative postion within tile
// (0,0) for bottom left, (1,1) top right
Vector2 GetLonLatOfVertex(Vector3Int tileIndex, Vector2 uv)
{
float lon = uv.x * 360 - 180;
// float lat = uv.y * 180 - 90;
float lat = uv.y * 168 - 84;
float lonRad = lon / 180 * Mathf.PI;//uv.x * Mathf.PI * 2 - Mathf.PI;
float latRad = lat / 180 * Mathf.PI;//uv.y * Mathf.PI - Mathf.PI / 2;
float theta = lonRad;
float phi = Mathf.Log(Mathf.Tan(Mathf.PI/4 + latRad/2));
Debug.Log($"{uv.x} {uv.y} -- {lon} {lat} -- {lonRad} {latRad} -- {theta} {phi}");
// Use tileIndex and uv to calculate lon, lat (in RADIANS)
// Exactly how you could do this depends on your tiling API...
return new Vector2(theta, phi);
}
// Call after plane mesh is created, and any additional vertices/uvs are set
// tileIndex is column/row/zoom of current tile
void SetUpTileLonLats(Mesh mesh, Vector3Int tileIndex)
{
Vector2[] uvs = mesh.uv;
Vector2[] lonLats= new Vector2[uvs.Length];
for (int i = 0; i < lonLats.Length; i++)
{
lonLats[i] = GetLonLatOfVertex(tileIndex, uvs[i]);
}
mesh.uv2 = lonLats;
}
}
shader
Shader "Custom/SquareBender" {
Properties{
_MainTex("Tex", 2D) = "" {}
_SphereCenter("SphereCenter", Vector) = (0, 0, 0, 1)
_SphereRadius("SphereRadius", Float) = 50
}
SubShader{
Cull off // for doublesized texture @jkr todo: disable for prod
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float2 uv : TEXCOORD0;
float2 lonLat : TEXCOORD1;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 uv : TEXCOORD0;
};
float4 _SphereCenter;
float _SphereRadius;
v2f vert(appdata v)
{
v2f o;
float lon = v.lonLat.x;
float lat = v.lonLat.y;
_SphereRadius = 40;
fixed4 posOffsetWorld = fixed4(
_SphereRadius*cos(lat)*cos(lon),
_SphereRadius*sin(lat),
_SphereRadius*cos(lat)*sin(lon), 0);
float4 posObj = mul(unity_WorldToObject,
posOffsetWorld + _SphereCenter);
o.pos = UnityObjectToClipPos(posObj);
o.uv = v.uv;
o.norm = mul(unity_WorldToObject, posOffsetWorld);
return o;
}
sampler2D _MainTex;
float4 frag(v2f IN) : COLOR
{
fixed4 col = tex2D(_MainTex, IN.uv);
return col;
}
ENDCG
}
}
FallBack "VertexLit"
}
** EDIT **
Shout out to @Ruzihm for his shader contribution from this answer about wrapping map tiles around a sphere