# Unity3d - Tile-based "Slippy" map rendered on a sphere (earth)

Scenario

I'm creating a spherical version of a slippy map using osm tiles and my luck has been limited thus far. I'm starting to think my strategy has lead me astray and that I need to start from scratch.

Current Progress

• Like the mentioned slippy maps I'm only loading tiles the user is interested in, using camera position & rotation to decipher X,Y of tiles needed
• Tiles are being positioned correctly on a sphere
• Tiles are partially rotated correctly (using basic normals)
• I'm currently working on sizing the tiles based on Y coordinate of image
• Will need to correctly position vertices for tiles as tiles close the poles need to be thinner and tiles near the equator need to be fatter
• Will need to bend tiles to match curvature of earth

Question

Is there a shader or some sort of math that already exists for Unity so that I can plug in the X,Y of an osm tile and have that texture appear correctly on the surface of a sphere ?

** some code **

    // tilesXY is the tile count per axis which depends on zoom level
// position for 3d tiles; adding .5 to keys so the polar caps look correct
float phi = (keyY + 0.5f) / tilesXY * Mathf.PI;
float theta = (keyX) / tilesXY * (Mathf.PI * 2);
float x = Mathf.Sin(phi) * Mathf.Cos(theta);
float y = Mathf.Sin(phi) * Mathf.Sin(theta);
float z = Mathf.Cos(phi);
this.transform.position = new Vector3(x, y, -z);

// wip rotation for tiles
Vector3 pu = new Vector3(x, y, -z); // plane up
Vector3 pr = Vector3.Cross(pu, Vector3.up); // plane right
Vector3 pf = Vector3.Cross(pr, pu); // plane forward
this.transform.rotation = Quaternion.LookRotation(pf, pu);

// wip scaling of tiles
float sfsy = Mathf.Sin(keyY / tilesXY * Mathf.PI);
float tsf = scaleFactor * 0.16f * (sfsy * 0.25f) + 0.016f;
this.transform.localScale = new Vector3(tsf, tsf, tsf);


    // code for using camera position + angle to figure out what tile(s) need to be loaded

Vector3 camAngle = MainCamera.instance.AngleToTarget();

UnityEngine.Camera cam = UnityEngine.Camera.main;
bool hemiFront = cam.transform.position.x > 0 ? true : false; // top bot vs front back hemispheres @jkr
bool hemiBot = cam.transform.position.y < 0 ? true : false;

// Debug.Log(\$"{hemiFront}, {hemiBot}");
float sinX = Mathf.Sin(camAngle.y / 360 * Mathf.PI);
int centerX = (int)(sinX * (tilesInfo["tilesXY"] / 2)); // center of where camera is looking, on the surface @jkr
if (hemiFront == false)
{
centerX *= -1;
centerX += tilesInfo["tilesXY"];
}

// if (hemiFront == false) 0 = 0;
float sinY = Mathf.Sin(camAngle.x / 360 * Mathf.PI);
int centerY = (int)(sinY * (tilesInfo["tilesXY"] / 2));
if (hemiBot == true)
{
centerY *= -1;
centerY += tilesInfo["tilesXY"];
}


** extra **

Screenshot of current progress

• the tight grouping of images is the north pole
• I'm pulling from a custom set of osm so anything green is water while blue is ice
• the underlying blue is a sphere I put in my scene to help me make sure the tiles are being positioned correctly

# ** EDIT **

The shader is starting to come along

• Myself, I think I'd load all of the map tiles into a texture array, then have an indirection map that the shader can look up into when it wants to find the index of the best matching tile for a given point. Then you render a simple sphere mesh for the whole world, or even a quad, and calculate the lookup coordinates in the shader, and sample/blend the relevant tiles from the array. Of course, this is easier said than done, but I think it will be easier to manage seamlessly joining tiles in image space like this than trying to generate each one as a separate piece of geometry. Dec 2, 2021 at 1:39
• @DMGregory I've been entertaining texture arrays recently so thanks for mentioning that. I need to keep in mind that at a theoretical zoom level of 23 there are 70 trillion tiles. An array size of 70 trillion hurts my brain let alone 70 trillion 2d texture instantiations. How can I used something like a texture array without sizing the array? I would need something like a texture hashmap, but I know of nothing like that. Dec 2, 2021 at 17:26
• That's where the indirection map comes in. Let's say at a typical view you'd need 16 tiles to cover the screen at the desired resolution. Let's say we're also keeping in cache the 16/4=4 tiles we'd need to cover the same area at the next lod down and the 16x4=64 tiles we'd need if the player zooms in. That's < 100 tiles we might be drawing from at a frame's notice. Size your array to have that many. When you need to add a new tile, evict the least recently used one. Then update the indirection map to tell the shader which array slot to look in for that part of the sphere. Dec 2, 2021 at 18:09
• If I understand you correctly, in this scenario I'd have a sort of buffer at size 100 and once I get to the 101th tile I'll evict tile 1 as it's most likely unused at that point? I was certainly planning on using a metric like this for caching the bitmap data but is this something a shader / textureArray can work with? I suppose my lack of understanding is around working with textureArrays and shaders. If I have a textureArray of 100 items that is for a zoom level with more than say 1M tiles how can I make sure the tiles are loaded into the correct spots? Maybe I'm missing your point. Dec 2, 2021 at 20:33
• Comments are not for extended discussion; this conversation has been moved to chat. Dec 2, 2021 at 21:10

Mapbox has made something similar to what I've made but I didn't find any unity opensource projects (just pieces) so I've had to create my own. Here are the highlights:

• be able to figure out latlon for anywhere on your sphere, you can do this with sin & cos but I took a shortcut with inversing camera.rotation per my situation
• map tiles are by-in-large "web mercator" so your longitude will always be 1:1 but the latitude needs to be converted from mercator to equirectangular with atan(sinh(latitudeRadians)) which I think is a Gubermannian Inverse function but don't quote me

below is how I mapped tiles to a sphere

    float x = r * Mathf.Cos(phi) * Mathf.Cos(theta);
float y = r * Mathf.Sin(phi);
float z = r * Mathf.Cos(phi) * Mathf.Sin(theta);


"theta" and "phi" are my latlon after I applied the GubermannianInv, which is how I get the xyz placement of tiles around a sphere.

It's been a long journey and I'm planning to add my code to slippymap so that other people can leverage a "free" google maps solution for unity.

video of project