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"
}
我正在使用 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,我还没有解决
但是
我能够通过使用
解决方案
我将一些着色器代码从 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"
}