1
\$\begingroup\$

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 downloading via c# async/await
  • 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

sphere with map tiles attached

** EDIT **

The shader is starting to come along

better shader

\$\endgroup\$
5
  • \$\begingroup\$ 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. \$\endgroup\$
    – DMGregory
    Dec 2 '21 at 1:39
  • \$\begingroup\$ @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. \$\endgroup\$
    – Jacksonkr
    Dec 2 '21 at 17:26
  • \$\begingroup\$ 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. \$\endgroup\$
    – DMGregory
    Dec 2 '21 at 18:09
  • \$\begingroup\$ 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. \$\endgroup\$
    – Jacksonkr
    Dec 2 '21 at 20:33
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$
    – DMGregory
    Dec 2 '21 at 21:10

You must log in to answer this question.

Browse other questions tagged .