Unity 3d - Web 墨卡托图像到球体着色器

Unity 3d - Web Mercator image to Sphere Shader

我正在使用 Web 墨卡托图像来覆盖球体。我的着色器采用带有图像的平面并将其变成球体。唯一的问题是由此产生的球体最终会拉长国家(如美国)。

我发现我可以使用地球的等边图像来获得非拉伸国家的预期效果

问题

对于我的项目,我只有网络墨卡托图像,并且我一直在努力让我的着色器以正确的比例显示国家/地区。如何将墨卡托经纬度转换为等边经纬度以写入我的着色器?

注意

我需要的一切似乎都在 上,但无论出于何种原因,它就是没有点击。

一些代码

平面脚本

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 "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"
}

** 编辑 **

感谢@Ruzihm 对这个关于

的回答的着色器贡献

这个问题与一个更大的问题有关tile-based earth question,我还没有解决

但是

我能够通过使用

中的数学来弄清楚如何解决这个子问题

解决方案

我将一些着色器代码从 and merged it in with my current shader. I assigned a web mercator image 带到了一个也附加了这个着色器的平面上。默认的“投影”着色器参数是 0 所以一切都已经设置为将墨卡托图像转换为等距柱状和中提琴〜图像在球体上呈现为等距柱状。

MercatorBender.shader

Shader "Custom/MercatorBender" {
    Properties{
        _MainTex("Tex", 2D) = "" {}
        _SphereCenter("SphereCenter", Vector) = (0, 0, 0, 1)
        _SphereRadius("SphereRadius", Float) = 5

        [Enum(Equirectangular,0,Azimuthal,1)]
        _Azimuthal("Projection", float) = 0
    }

    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;
                float _Azimuthal;

                // float4 frag(v2f IN) : COLOR
                // {
                //     fixed4 col = tex2D(_MainTex, IN.uv);
                //     return col;
                // }

#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
        }
    }
    FallBack "VertexLit"
}