Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

World projected normal map on rotated mesh

Discussion in 'Shaders' started by Elyaradine, May 13, 2019.

  1. Elyaradine

    Elyaradine

    Joined:
    Aug 13, 2015
    Posts:
    27
    I've got a surface shader with a normal map that's world projected from above.

    Code (csharp):
    1. Shader "Custom/Cliff"
    2. {
    3.     Properties
    4.     {
    5.         _WorldTiling ("World tiling (XY) Cliff Tiling (ZW)", Vector) = (0.1, 0.1, 1, 1)
    6.         _Color ("Terrain colour", Color) = (1,1,1,1)
    7.         _BumpTex ("Normal map", 2D) = "bump" {}
    8.     }
    9.     SubShader
    10.     {
    11.         Tags
    12.         {
    13.             "RenderType"="Opaque"
    14.         }
    15.  
    16.         CGPROGRAM
    17.         #pragma surface surf Standard fullforwardshadows
    18.         #pragma target 3.0
    19.  
    20.         sampler2D _MainTex, _BumpTex;
    21.         float4 _WorldTiling;
    22.         float _Glossiness, _Metallic;
    23.  
    24.         struct Input
    25.         {
    26.             float3 worldPos;
    27.         };
    28.  
    29.         fixed4 _Color, _CliffCol, _CliffTopCol;
    30.         fixed _BumpStrength;
    31.  
    32.         // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    33.         // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    34.         // #pragma instancing_options assumeuniformscaling
    35.         UNITY_INSTANCING_BUFFER_START(Props)
    36.             // put more per-instance properties here
    37.         UNITY_INSTANCING_BUFFER_END(Props)
    38.  
    39.         void surf (Input IN, inout SurfaceOutputStandard o)
    40.         {
    41.             float2 uv = IN.worldPos.xz * _WorldTiling.xy;
    42.  
    43.             o.Normal = UnpackNormal(tex2D(_BumpTex, uv));
    44.             o.Albedo = _Color.rgb;
    45.             o.Metallic = _Metallic;
    46.             o.Smoothness = _Glossiness;
    47.         }
    48.         ENDCG
    49.     }
    50.     FallBack "Diffuse"
    51. }
    The idea is that the meshes that receive this are cliff pieces that get rotated around depending on what their adjacent pieces are (so that there are corner pieces, diagonal pieces, etc.). (I don't think what the meshes are matters; the point is really just that the pieces get rotated around the y-axis.)

    When I use four Unity quads, when they're all rotated the same way, the result is what I expect. When I rotate one of the quads, there's a visible seam.

    mapseam.gif

    (I'm just using Unity quads as a most basic example of what goes wrong. I can't just not rotate the mesh pieces, because in the real-world case it's not as simple as using quads.)

    I'd normally suspect that it's got something to do with my incorrectly looking up my normal map (like some error with my tangent space calculations), but as this is a surface shader, my understanding is that Unity's generating all of that "for me", so that I don't know what's going wrong.

    I'm using Unity 2018.3.12f1. (Curiously, when I roll back to old builds of the game -- though I don't remember what version of Unity that was -- this doesn't seem to show up...)

    Does anyone have any ideas?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    This will happen in any shader where you calculate new UVs in the shader to use with normal maps.

    Most normals maps are tangent space normal maps. Tangent space is the orientation of the texture's UVs and the surface normal, where the UV's x and y are right and up, and the surface normal is back. Most real time engines pre-calculate the tangent space from the mesh's UVs and store the data on the vertices so the tangent space to world space transform matrix can be constructed in the vertex shader. Unity is no exception here.

    Surface Shaders assume the normal vector you set on the o.Normal is in the mesh's tangent space, and will always apply the tangent to world transform. You're generating the normal map's UVs in the shader, so the tangent space of your UVs, and thus the normal vector you're setting on o.Normal, do not match the tangent space the shader is assuming you're using.

    The solution is you need to manually calculate an appropriate tangent to world transform matrix. Easy, right!?

    Don't worry, I wrote a short *cough* article on the topic here:
    https://medium.com/@bgolus/normal-mapping-for-a-triplanar-shader-10bf39dca05a

    For Surface Shaders you also then have to convert the world normal back to the mesh's original tangent space. I talk briefly about this at the end of the article, but here's a direct link to an example shader showing how to do that:
    https://github.com/bgolus/Normal-Ma...der/blob/master/TriplanarSurfaceShader.shader
     
    Elyaradine and Sh-Shahrabi like this.
  3. Elyaradine

    Elyaradine

    Joined:
    Aug 13, 2015
    Posts:
    27
    Ben, you're my hero.
     
  4. thomasnoerby

    thomasnoerby

    Joined:
    May 24, 2023
    Posts:
    1
    Ben, you are still someones hero!
     
  5. BenMora

    BenMora

    Joined:
    Jul 6, 2021
    Posts:
    7
    Ben, at my work we refer to you as "other Ben."