1
\$\begingroup\$

I'm trying to make a 3d model of the Earth (it doesn't have to be a fully-accurate geoid), but whenever I try with a map as a material's albedo to do so, it looks kind of weird and wonky. I assume that this is because my map is in the wrong projection - so my question is, what projection should it be in?

\$\endgroup\$
2
  • \$\begingroup\$ @Kromster I'd disagree for this reason. Spherical projections are generally non-linear/non-affine, which means the affine texture mapping provided by per-vertex UV coordinates tends to show creases or warbles. In cases like these, using a cube map or calculating your projected coordinates in the shader can help you achieve the needed non-linearity. \$\endgroup\$
    – DMGregory
    Nov 19, 2021 at 18:09
  • \$\begingroup\$ @DMGregory agreed. Although it depends on the exact model topology. \$\endgroup\$
    – Kromster
    Nov 19, 2021 at 18:23

1 Answer 1

4
\$\begingroup\$

The default sphere in Unity has its UV coordinates mapped like this:

Animated UV mapping

You can see that the "v" coordinate (green) acts like a latitude, increasing from 0 at the bottom of the sphere (black/red) to 1 at the top (green/yellow). The "u" coordinate (red) acts like a longitude, starting at 0 at the left (x-) side of the sphere and increasing counter-clockwise or "eastward" until it comes all the way back around at 1.

But at the end of the day the mesh is made of only so many triangles, so there are places where it can't perfectly follow the latitude/longitude mapping. At the top of the sphere you can see there are notches cut out, making a zig-zag: parts of the texture space get skipped over. That leads to some noticeable distortions at the poles:

Pole distortion

As I explain in this answer, this is a limitation of affine texture mapping in general. Lines are only able to change directions at triangle edges, so it can't represent a smooth curving of a non-linear mapping inside an individual triangle. And an equirectangular projection (latitude-longitude) gets highly non-linear at the poles!

But that's not a problem. We can compute our own non-linear map projection in the shader. For instance, if we have a texture that uses equirectangular mapping, when we can modify the basic Surface Shader template to do that like so:

  1. Add vertex:vert to the surface shader declaration to make a custom pre-processor for our vertices.

    #pragma surface surf Standard fullforwardshadows vertex:vert

  2. Add a float3 direction field to our Input data structure:

    struct Input
    {
        float2 uv_MainTex;
        float3 direction;
    };
    
  3. Populate this direction with the object space normal of this vertex

    void vert(inout appdata_full v, out Input o) {
        UNITY_INITIALIZE_OUTPUT(Input, o);
        o.direction = v.normal;
    }
    
  4. Compute our own per-pixel UV using a non-linear projection in the surface shader function:

    void surf (Input IN, inout SurfaceOutputStandard o)
    {
        const float PI = 3.14159265359;
        // Puff out the direction to compensate for interpolation.
        float3 direction = normalize(IN.direction);
    
        // Get a longitude wrapping eastward from x-, in the range 0-1.
        float longitude = 0.5 - atan2(direction.z, direction.x) / (2.0f * PI);
        // Get a latitude wrapping northward from y-, in the range 0-1.
        float latitude = 0.5 + asin(direction.y) / PI;
    
        // Combine these into our own sampling coordinate pair.
        float2 customUV = float2(longitude, latitude);
    
        // Use them to sample our texture(s), instead of the defaults.
        fixed4 c = tex2D (_MainTex, customUV) * _Color;
    
        // ...
    
    

And now we get a proper non-linear mapping, with no more distortion at the poles, or zig-zagging where lines cross triangle boundaries:

Pole distortion corrected

You can do this with a shader graph too - you'd just use math nodes to do the same operations as the code above.

You're not limited to equirectangular mapping of course. You can use any map projection you like, just by putting the matching projection function into your shader.

Another strategy that works well is to store your spherical texture as a cubemap, then use a cubemap sample in direction rather than a conventional 2D texture sample. This can help get more even texel coverage around the sphere, as equirectangular mapping devotes a bit too much space to tiny regions at the poles.

\$\endgroup\$
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .