Search Unity

Negative Normal Map ?

Discussion in 'Shaders' started by GlitchInTheMatrix, Feb 7, 2012.

  1. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    Hi lovers of Shaders and Materials.

    The last days playing with unity i found something that i don't know if is a Unity error, a Shader error or what's the problem with it.

    I have a character on screen with Diffuse and Normal Map, the shader is "Bumped Specular", the model looks great but in the game im making, im use a negative size (-X) to turn my player and play all the animations in a mirror form. Imagine like a fighting game, P1 vs P2, where the P2 is the same of P1 but with a negative scale (-X)

    This model that i use the negative Scale X show bad the normal maps.

    here a comparative pic of the positive and negative scale X.



    Any idea if this is a error of the shader, unity3D or something else?

    thanks
     
  2. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    You'd need to also invert the green channel of your normal map. Or multiply the y component of the normal in the shader by -1.

    I wouldn't use minus scales to do character mirroring, though... that seems a pretty hacky workaround.
     
  3. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    Thanks Farfarer,

    I see, is possible invert the green channel by code? or must create other shader and change it when i set my X to a negative value?
     
  4. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    In the shader you could add a float and set it to 1 or -1 in code.

    Then multiply the normal's y component by that float inside the shader.
     
  5. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    Can you show us the normal map you're using? It looks to me like more than the green channel needs inverting.

    This sort of thing is best to get figured out on your content creation side rather than in Unity, because Unity will convert your normal maps to its internal format before your code gets to see the data.
     
  6. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    There we go, here 1 of the normal map, i baked it using Maya.

     
  7. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    It could be the red channel needs inverting (if it's an X invert) - but I think it's definitely one of the two channels are simply flipped.
     
  8. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    I understand that the UDK works with flipped channel, that cause the normal map looks orange. there is any way to make normal map works correctly in positive and negative X scale?
     
  9. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    Ah, I misread why you were using a negative X scale. In that case, you should use a script to duplicate the mesh using the Mesh class. Then you can iterate through all the vertices and negate their tangent.w values. This will invert your the tangent space bases to compensate for your scaling. I believe this is more efficient than making a custom shader or a custom normal map for the mirrored actor.
     
  10. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    Uh... you might be able to use the unity_scale value in the shader to multiply the normal channel by either 1 or -1?
     
  11. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    The Scale X=1 is for the Player 1 the Scale X=-1 is for the Player 2 (Mirror of Player 1) so i use all the animations mirrored.

    My only problem is that the Player 2 (Scale X=-1) show bad the Normal Map, The Player 1 Show the normal map correctly.

    So for fix the player 2 maybe i have to do something like Daniel B.

    But honestly, i have not idea how to make it :(
     
  12. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    Mirroring the verts of the model would not work because the animations would play the same, just that the verts would be mirrored. Unless you did this in lateupdate each and every frame (which is woefully inefficient compared to simply mirroring the object in X).

    The shader has a value which contains the object's scale, you could use that to invert the channel of the normal map;
    Code (csharp):
    1.  
    2. // In your shader...
    3. o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv));
    4. o.Normal.x *= sign(unity_Scale.x); // Swap this line for the next line if it still appears wrong.
    5. //o.Normal.y *= sign(unity_Scale.x);
    6.  
     
  13. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    I suggested negating the W components of the tangents to mirror the tangent space. The vertex mirroring would still be done by a negative X scale, allowing the animations to continue working.

    Ratonmalo, have you looked at the documentation I linked?
     
    Last edited: Feb 7, 2012
  14. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    Thanks to both for the help!

    right now im testing the Farfarer way, if you Daniel Brauer don mind can you enplane me how to do this or post any example?

    I want to try both ways. Thanks.
     
  15. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    My bad, I misread your post. Yeah, that'd work too I think, and not require the if statement in the shader.


    Ratonmalo : Check out the mesh documentation. You copy out the tangent values to a Vector4 array, loop through that and multiply the w component of each entry by -1, then pass the array back into the mesh.
     
  16. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    Hi, checking the mesh documentation i found this :

    Code (csharp):
    1. var mesh : Mesh = GetComponent(MeshFilter).mesh;
    2. var vertices : Vector3[] = mesh.vertices;
    3. var normals : Vector3[] = mesh.normals;
    4.  
    5. for (var i = 0; i < vertices.Length; i++)
    6. vertices[i] += normals[i] * Mathf.Sin(Time.time);
    7.  
    8. mesh.vertices = vertices;
    That suppose to :

    1) get vertices
    2) modify them
    3) assign them back to the mesh.

    So i have to multiply the vertex by -1?
     
    Last edited: Feb 8, 2012
  17. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    No, multiply the tangent's w component by -1.

    Code (csharp):
    1.  
    2. var mesh : Mesh = GetComponent(MeshFilter).mesh;
    3. var tangents : Vector4[] = mesh.tangents;
    4. var i : int = tangents.Length;
    5. while ( i ) {
    6.   tangents[i].w = tangents[i].w * -1;
    7.   i--;
    8. }
    9. mesh.tangents = tangents;
    10.  
     
  18. GlitchInTheMatrix

    GlitchInTheMatrix

    Joined:
    Apr 12, 2010
    Posts:
    285
    Hi, i still testing it for a couple of days and i can/t get it working.

    here what im trying to do.

    Code (csharp):
    1. var mesh : Mesh = GetComponent(MeshFilter).mesh;
    2. var tangents : Vector4[] = mesh.tangents;
    3. var i : int = tangents.Length;
    4.  
    5. function Update () {
    6.  
    7. if (Input.GetKey("w"))
    8. {
    9.     while ( i ) {
    10.       tangents[i].w = tangents[i].w * -1;
    11.       i--;
    12.     }
    13.     mesh.tangents = tangents;
    14.     }
    15. }
     
  19. Maranasa

    Maranasa

    Joined:
    Mar 14, 2014
    Posts:
    5
    If Anyone is still looking.. her is the fixed C# version of the script. Just drop this into Any game object and it should work.

    using UnityEngine;
    using System.Collections;

    public class NormalInverter : MonoBehaviour
    {
    private MeshFilter filter;
    private Mesh mesh;
    private Vector4[] tangents;
    void Start ()
    {
    if (transform.lossyScale.x < 0 || transform.lossyScale.y < 0 || transform.lossyScale.z < 0)
    {
    filter = gameObject.GetComponent<MeshFilter>();
    mesh = filter.mesh;
    tangents = mesh.tangents;
    for (int i = 0; i < tangents.Length; i++)
    {
    tangents.w = tangents.w * -1;
    }
    mesh.tangents = tangents;
    }
    }
    }
     
    skullthug and the_motionblur like this.
  20. Alessandro-Previti

    Alessandro-Previti

    Joined:
    Nov 1, 2014
    Posts:
    30
    You, Sir, are my hero tonight.
    Five minutes of search saved me countless hours of work thanks to your contribution.