Search Unity

Mismatch normals in Game / Editor window

Discussion in 'Shaders' started by levelappstudios, Mar 13, 2019.

  1. levelappstudios

    levelappstudios

    Joined:
    Nov 7, 2017
    Posts:
    104
    I'm developing a shader which uses local normals. I found a strange behavior while coding, normals show fine in editor window but seems corrupted in game window. I tried a simple shader to debug it:

    Captura de pantalla 2019-03-13 a las 13.27.41.png Captura de pantalla 2019-03-13 a las 13.28.48.png

    This is the shader code:

    Code (CSharp):
    1. Shader "Custom/Test/Normals"
    2. {
    3.     Properties
    4.     {
    5.     }
    6.     SubShader
    7.     {
    8.         Tags {
    9.             "RenderType"="Opaque"          
    10.         }      
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag          
    17.          
    18.             #include "UnityCG.cginc"
    19.  
    20.             struct appdata
    21.             {
    22.                 float4 vertex : POSITION;
    23.                 float3 normal : NORMAL;
    24.             };
    25.  
    26.             struct v2f
    27.             {
    28.                 float3 normal : NORMAL;              
    29.                 float4 vertex : SV_POSITION;
    30.             };
    31.          
    32.             v2f vert (appdata v)
    33.             {
    34.                 v2f o;
    35.                 o.vertex = UnityObjectToClipPos(v.vertex);
    36.                 o.normal = v.normal;
    37.                 return o;
    38.             }
    39.          
    40.             fixed4 frag (v2f i) : SV_Target
    41.             {
    42.                 return float4(i.normal.x, i.normal.y, i.normal.z, 1.0);
    43.             }
    44.             ENDCG
    45.         }
    46.     }
    47. }
    48.  
    If o.normal gets normalized [o.normal = normalize(v.normal);] then the result is a bit better, but still wrong:

    Captura de pantalla 2019-03-13 a las 13.37.59.png


    If the car gets rotated then the door normals get totally wrong in game window, but show fine in editor window:

    Captura de pantalla 2019-03-13 a las 13.39.58.png Captura de pantalla 2019-03-13 a las 13.40.09.png

    Any clue on this? At first I thought it has something to do with car meshes, but they show OK on editor window so I'm very confused.

    Thanks in advance
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Lets talk about the game view first.

    The mesh's vertex normal is in the mesh's local space. That means it's not taking into account the orientation of that mesh within world space which is why when you rotate it some parts don't change at all. However some parts can be dynamically or staticly batched together into a single mesh that is pre-transformed into world space. For those meshes their normals are correctly oriented because the mesh's local space is already in world space. However one curiosity is that if the mesh is being scaled, either from the game object's transform or in the mesh's import settings, the batched mesh's normals may be scaled as well, which is why the normalize() function rescales things to be correct.

    However dynamic batching only works on objects that are relatively simple; less than 900 vertex attributes ((Position + UV + Normal + etc) * vertex count) or 300 vertices can be dynamically batched. Most likely some parts of your car modes are too complex to get dynamically batched, so you're seeing the original local space vertex normals still. So that's why some simpler parts, like the doors and hoods, look correct, but the car bodies are wrong and don't change when rotated.


    Now the scene view doesn't do any batching, so what you're seeing are the mesh's local space vertex normals all of the time.


    The fix is you need to be transforming the local mesh normals into world space in the shader.

    Code (csharp):
    1. // in the vertex shader function
    2. o.normal = UnityObjectToWorldNormal(v.normal);
    3.  
    4. // optionally, additionally in the fragment shader function
    5. // can be skipped if things don't look too wrong, but can have a noticeable impact on
    6. // lighting brightness on faces with significant normal differences between vertices.
    7. i.normal = normalize(i.normal);
     
  3. jmclaveria

    jmclaveria

    Joined:
    Oct 16, 2018
    Posts:
    19
    Many many thanks for your detailed answer, it helps me a lot to understand Unity under the hood.

    I will check the dynamic/static batching and the scale of the mesh.

    About the transformation code from local space to world space I tried the same thing this morning and it works as expected, but I need the normal value in local space in the fragment shader for my purposes.

    I will try tomorrow and post the results.

    Thanks.

    Edit: I’m the same person who wrote the first message, just using my personal account
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Why? Usually any effect you would do in the fragment shader would be either in world space or tangent space, rare to use local space outside of the vertex shader.

    However, if that's what you want, you can disable batching on the shader itself by adding "DisableBatching"="True" to the subshader tags.
    https://docs.unity3d.com/Manual/SL-SubShaderTags.html
     
  5. levelappstudios

    levelappstudios

    Joined:
    Nov 7, 2017
    Posts:
    104
    Disabling batching with "DisableBatching" = "True" is the key to show local normals without errors.

    Thank you very much bgolus!!

    Edit: Using Static Batching works too :) Seems like dynamic batching was giving the normals mismatch
     
    Last edited: Mar 14, 2019
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    If your objects aren’t mesh renderers set to be static gameobjects, static batching won’t have any effect. As those are car models, presumably you want them to move, which means they can’t be static.

    If they were static, they’d look exactly like the dynamically batched ones.
     
  7. jmclaveria

    jmclaveria

    Joined:
    Oct 16, 2018
    Posts:
    19
    Using StaticBatchingUtility.Combine on car root works fine, I just move and rotate the car root parent so batching will not be affected (checked with Statistics on Game Window)
     
    bgolus likes this.
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    I hadn't remembered the static batching utilities allowed you to move the root after the combine, that's useful. Realistically it's probably overkill to use static batching utility vs the more basic CombineMeshes() or merging the meshes externally before import.