Search Unity

Ending The Confusion About Mirrored Normal Mapping

Discussion in 'General Graphics' started by SomeGuy22, Jun 7, 2016.

  1. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    It's time to finally squash this issue I've been having for years. I'll admit, I'm a bit confused about what's actually going on, and it's hard to track down the cause of all this, but I'll do my best to explain. Also, I've seen quite a few other posts about this so it's probably best to at least gather info so others can avoid these complications.

    The Problem:

    Mirrored objects end up with incorrect normal mapping on the mirrored side. Example:



    Note that the right side is correctly "bumped" yet the left is essentially an inverted version of the normal map.

    Here's another example, probably more noticeable:



    It should be noted that I'm using Blender 2.75 for all of my modeling.

    What's happening?

    After extensive searching I've found a few possibilities as to what this could be.
    1. Tangents [w] are inverted on the mirrored side
    2. Normals are somehow inverted on the mirrored side
    3. The very nature of normal maps in Tangent space doesn't allow for a mirrored UV map

    I thought it was certainly the first item listed, due to my findings on this post. However, I recently attempted that solution for the models above, and they remain the way seen above. (There is a visible change, but it appears to only flip the problem to the other side)

    Is there a way to fix this?

    I'd like to use the original Blender file (for workflow purposes) as the main Asset for my game. That means I'd prefer not to have to export it as any other format. I'd also like to keep the mirroring on my objects and my UVs the way they are. Any of you other developers would certainly know that keeping the mirror has a tremendous advantage when changes need to be made to the model. And diffuse maps can be made much faster. Certainly modern 3D programs wouldn't stop us from having a mirrored normal map right?

    Here's a few links that add even more confusion to the issue.

    In this post, Daniel claims that Unity calculates the tangents correctly for mirrored meshes. Later on, he says that it could be an issue with other file formats or software. Could Blender be not exporting the "homogeneous tangents" needed to display the normal map properly? Or is Unity grabbing the wrong information from the native Blender file?

    This post has doj, who says Unity doesn't handle mirrored tangents correctly.

    This user recommends triangulation. I've tried this, and it didn't help the issue above.

    Help is appreciated! Thanks!

    EDIT: I'm using Blender's Mirror modifier. If you're unfamiliar with this mesh modifier, it essentially duplicates geometry on a specific axis and updates in realtime. In my files, the modifier has not been "applied," meaning I can't directly edit the mirrored side. Blender is intended to do this work instead.
     
    Last edited: Jun 8, 2016
  2. AkiraWong89

    AkiraWong89

    Joined:
    Oct 30, 2015
    Posts:
    662
    When an object is mirrored. Every information is inverted specially normal is obvious.
    Simply freeze transformation / Reset X-form after mirroring and flip faces should be OK.
     
  3. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I'm not too sure about this, it's my understanding that Blender's Mirror Modifier handles normals correctly. See this post. The user claims the issue is present from manual mirroring, but with the built-in modifier results are correct.

    I've also verified this myself, take a look at the object's normals with it's mirror shown in edit mode:



    I think I should also specify that I'm using Blender's mirror modifier. "After mirroring" is not something that happens on the user end because it's specifically designed to update the mirrored side in realtime. This is different from Maya and other software in the fact that the software handles all the mirroring automatically. So long as the mirror modifier is present, you'll only ever have to deal with half the mesh. As stated above, I'd to keep it this way for workflow purposes. OP has been edited to clarify.
     
  4. AkiraWong89

    AkiraWong89

    Joined:
    Oct 30, 2015
    Posts:
    662
  5. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    It isn't just the seams though--if you look at the second picture in my first post, you can see the whole mirrored side is inverted. Sure, I could blend the seams so there's no visible mark at the mirror point, but that won't solve the fact that the entire normal map (on that side) is showing up inverted. While it may not be noticeable with hand-painted maps, baked maps end up with entire sections of the same direction.

    At the end of the tutorial above, the user says it does not fix the "inverted lighting bug" on mirrored geometry. That sounds closer to what's happening here. Here's a forum on Kerbal Space Program that just confused me even more about this stuff.
     
  6. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    So it's a problem with the normal map and not the geometry?
    I guess the normal aren't inverted so much as one component is inverted, you should try to verify that.
     
  7. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    With a custom shader, I've been able to verify that the tangents are in deed "out of whack." Results:



    And here's the normals as they appear in Unity:



    For anyone interested, here's the shader I made to Debug this. Simply comment out the o.color with the other one to check normals.

    Code (CSharp):
    1. Shader "Custom/TangentDebug" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    6.         _Metallic ("Metallic", Range(0,1)) = 0.0
    7.     }
    8.     SubShader {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 200
    11.        
    12.         Pass {
    13.         CGPROGRAM
    14.         #pragma vertex vert
    15.           #pragma fragment frag
    16.           #include "UnityCG.cginc"
    17.        
    18.    
    19.       struct v2f {
    20.           float4 pos : SV_POSITION;
    21.           fixed4 color : COLOR;
    22.       };
    23.      
    24.       v2f vert (appdata_tan v)
    25.       {
    26.           v2f o;
    27.           o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    28.           float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
    29.          
    30.           //o.color.xyz = v.normal * 0.5 + 0.5;
    31.           o.color.xyz = binormal * 0.8;
    32.           o.color.w = 1.0;
    33.           return o;
    34.       }
    35.  
    36.       fixed4 frag (v2f i) : SV_Target { return i.color; }
    37.       ENDCG
    38.       }
    39.     }
    40.     FallBack "Diffuse"
    41.    
    42. }
    43.  

    Now the issue at hand... Why is Unity handling the tangents this way? The tangents are set to "calculate" in the import settings. (Normals are imported) Can anyone explain what's going on?!
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Tangents look correct actually.
     
  9. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    That looks correct?! The big black and red divide down the middle is what looks off to me. Could you elaborate?
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Tangents and binormals are in the direction of the texture UV "flow", so if a model is mirrored I would expect the tangents or binormals to have a hard break.

    The red and black make sense since red is a vector pointed toward x (1,0,0), and black is likely negative x (-1,0,0).
     
  11. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    The binormal shouldn't affect the normal anyway, right? If binormal is wack it affect UV I think.
     
  12. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    If you are showing the normal relative to surface, it should be a straight blue (0,0,1), I don't see you translating the normal to worldspace so why are the model colored?
     
  13. bart_the_13th

    bart_the_13th

    Joined:
    Jan 16, 2012
    Posts:
    498
    keeping my eyes on this, I have this problem too...
    I always ended up in having to uv map the mirrored side too...
     
  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Tangents and binormals are 100% related to normals, specifically normal maps, that's the only reason they exist. They have no effect on the UVs themselves; tangents and binormals are based on the UVs, but the UVs don't care about them or even if they exist.
    Don't conflate surface normals and normal maps. Normal maps are in "tangent space", and would appear blue if there was no detail in them, but surface normals are initially in model space which is going to be pointed in all sorts of directions unless you have a flat plane.

    A tangent space normal map stores a direction relative to the surface tangents, that is to say the red is the right or left amount, green is the up or down amount, and blue is the forward or back amount, but those directions are relative to the orientation of how the texture appears. Most people understand surface normals as the direction away from the surface, but tangents and binormals are usually more difficult for people to understand.

    image.png
    If you were to use this crappy image as the texture on your model the arrows point (roughly) in the direction of the tangent (X) and binormal (Y).
     
  15. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    I need to sleep to make stupid error like this ...
     
  16. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Thanks for your explanation, bgolus. I think I understand how tangents, normals, and the UV0 map are related now. This article also helped. If I'm understanding correctly, it makes sense that since that the "red" left and right are inverted, since the "U" value of the UV coordinates is mirrored.

    As the article I linked mentions,

    Now the ultimate problem is... Is there any way around this? I'm referring back to this post for this information.

    In Summary
    1. Normals are correct on mirrored geometry
    2. UV's are mirrored (as they should be)
    3. Tangents on the mirrored side are incorrect; they are pointing inwards to the model
    4. The Bitangents on the mirrored side that Unity calculates are correct, due to this:
      1. float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
    5. Inverted the Tangents on the mirrored side (fixing them) will cause an incorrect bitangent, as their W component will need to be inverted (unless they are calculated as homogenous tangents)
    Help on the execution for this fix would be appreciated. Perhaps Blender has a way of declaring how a mesh's tangents are handled?
     
  17. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I've found a temporary solution.

    Setting the tangents to None in the model importer seems to do the trick. The results look a bit different then when they were on "Calculate," but it looks fine for my purposes. Here's a few theories for what's happening (correct me if I'm wrong)

    1. Unity's Standard shader has an Object Space normal map fallback OR
    2. The Shader calculates it's own tangents when none is provided in the model OR
    3. "None" actually means that Blender somehow passes correct data to Unity
    I'm thinking number 1 or 2 is the case because I don't remember this working on the legacy shaders. Either way, I'm not about to complain or question anything. If anyone has a more correct explanation as to why this is producing this result, I'm all ears. And if number 1 is indeed the case, this should really be listed in the docs or made clear for those that want a good fallback for mirrored geometry.
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Tangents set to none should disable normal maps entirely, or be using completely bogus data (maybe just always a single object space direction?). The standard shader absolutely does not have an object space fallback, and does not calculate its own tangents, and unless there's a bug "None" shouldn't read anything from blender at all.

    edit: confirmed setting it to "none" results in constant tangent of (1,0,0,1). Also, the issue happens with a model from 3ds Max as well so it's not confined to blender.

    edit 2: I take that back, it was a shader bug causing it for the 3ds Max model.
     
    Last edited: Jun 9, 2016
  19. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Here's a shader I use to test this kind of stuff. Lets you switch between several different modes. Displays mesh normals, tangents, and bitangents (aka binormals) in world space, as well as the normal mapped normals in world space. Enabling derivatives calculates the tangent and binormal directly in the shader rather than using the tangent from the mesh. They'll be "chunkier" than mesh tangents, but can be used to validate the correctness of the tangents and binormals.

    Code (CSharp):
    1. Shader "Tangent Visualizer" {
    2. Properties {
    3.     _MainTex ("Base (RGB)", 2D) = "white" {}
    4.     [NoScaleOffset] _BumpMap ("Normal Map", 2D) = "bump" {}
    5.     [KeywordEnum(Lit, Mesh Normals, Mesh Tangents, Mesh Bitangents, Tangent Normals, World Normals)] _Display ("Debug Display", Float) = 0
    6.     [Toggle(_DERIVATIVES_ON)] _Derivatives ("Debug Use Derivatives", Float) = 0
    7.     [Enum(Zero to One, 0, Normalized, 1, Inverted, 2, Absolute, 3)] _Normalization ("Vector Display", Float) = 0
    8. }
    9.  
    10. SubShader {
    11.     Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
    12.     LOD 100
    13.    
    14.     Pass {  
    15.         CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             #pragma shader_feature _ _DISPLAY_MESH_NORMALS _DISPLAY_MESH_TANGENTS _DISPLAY_MESH_BITANGENTS _DISPLAY_TANGENT_NORMALS _DISPLAY_WORLD_NORMALS
    19.             #pragma shader_feature _ _DERIVATIVES_ON
    20.            
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct v2f {
    24.                 float4 vertex : SV_POSITION;
    25.                 half2 texcoord : TEXCOORD0;
    26.                 #ifdef _DERIVATIVES_ON
    27.                 half3 normal : TEXCOORD1;
    28.                 float3 pos : TEXCOORD2;
    29.                 #else
    30.                 half3x3 tspace : TEXCOORD1;
    31.                 #endif
    32.             };
    33.  
    34.             sampler2D _MainTex;
    35.             float4 _MainTex_ST;
    36.             sampler2D _BumpMap;
    37.  
    38.             fixed4 _LightColor0;
    39.             float _Normalization;
    40.  
    41.             float3x3 cotangent_frame( float3 normal, float3 position, float2 uv )
    42.             {
    43.                 // get edge vectors of the pixel triangle
    44.                 float3 dp1 = ddx( position );
    45.                 float3 dp2 = ddy( position ) * _ProjectionParams.x;
    46.                 float2 duv1 = ddx( uv );
    47.                 float2 duv2 = ddy( uv ) * _ProjectionParams.x;
    48.              
    49.                 // solve the linear system
    50.                 float3 dp2perp = cross( dp2, normal );
    51.                 float3 dp1perp = cross( normal, dp1 );
    52.                 float3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    53.                 float3 B = dp2perp * duv1.y + dp1perp * duv2.y;
    54.              
    55.                 // construct a scale-invariant frame
    56.                 // float invmax = rsqrt( max( dot(T,T), dot(B,B) ) );
    57.                 // return transpose(float3x3( T * invmax, B * invmax, normal ));
    58.                 return transpose(float3x3( normalize(T), normalize(B), normal ));
    59.             }
    60.  
    61.             fixed4 rescaleForVis(half3 vec)
    62.             {
    63.                 vec = normalize(vec);
    64.                 if (_Normalization == 3.0)
    65.                     return fixed4(abs(vec), 1.0);
    66.                 if (_Normalization == 2.0)
    67.                     return fixed4(-vec, 1.0);
    68.                 if (_Normalization == 1.0)
    69.                     return fixed4(vec, 1.0);
    70.                 // if (_Normalization == 0.0)
    71.                     return fixed4(vec * 0.5 + 0.5, 1.0);
    72.             }
    73.                        
    74.             v2f vert (appdata_full v)
    75.             {
    76.                 v2f o;
    77.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    78.                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    79.  
    80.         #ifdef _DERIVATIVES_ON
    81.                 o.normal = UnityObjectToWorldNormal(v.normal);
    82.                 o.pos = mul(_Object2World, v.vertex);
    83.         #else
    84.                 half3 wNormal = UnityObjectToWorldNormal(v.normal);
    85.                 half3 wTangent = UnityObjectToWorldDir(v.tangent.xyz);
    86.                 // compute bitangent from cross product of normal and tangent
    87.                 half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
    88.                 half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
    89.                 // output the tangent space matrix
    90.                 o.tspace = half3x3(wTangent.x, wBitangent.x, wNormal.x,
    91.                                    wTangent.y, wBitangent.y, wNormal.y,
    92.                                    wTangent.z, wBitangent.z, wNormal.z);
    93.         #endif
    94.                 return o;
    95.             }
    96.            
    97.             fixed4 frag (v2f i) : SV_Target
    98.             {
    99.                 half3x3 tbn;
    100.             #ifdef _DERIVATIVES_ON
    101.                 tbn = cotangent_frame(i.normal, i.pos, i.texcoord);
    102.             #else
    103.                 tbn = i.tspace;
    104.             #endif
    105.  
    106.             #ifdef _DISPLAY_MESH_NORMALS
    107.                 return rescaleForVis(half3(tbn[0].z, tbn[1].z, tbn[2].z));
    108.             #endif
    109.  
    110.             #ifdef _DISPLAY_MESH_TANGENTS
    111.                 return rescaleForVis(half3(tbn[0].x, tbn[1].x, tbn[2].x));
    112.             #endif
    113.  
    114.             #ifdef _DISPLAY_MESH_BITANGENTS
    115.                 return rescaleForVis(half3(tbn[0].y, tbn[1].y, tbn[2].y));
    116.             #endif
    117.  
    118.                 half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.texcoord));
    119.  
    120.             #ifdef _DISPLAY_TANGENT_NORMALS
    121.                 return rescaleForVis(tnormal);
    122.             #endif
    123.  
    124.                 half3 worldNormal = normalize(mul(tbn, tnormal));
    125.  
    126.             #ifdef _DISPLAY_WORLD_NORMALS
    127.                 return rescaleForVis(worldNormal);
    128.             #endif
    129.  
    130.                 fixed4 col = tex2D(_MainTex, i.texcoord);
    131.                 half ndotl = dot(worldNormal, _WorldSpaceLightPos0.xyz);
    132.                 col.rgb *= saturate(ndotl) * _LightColor0.rgb + ShadeSH9 (float4(worldNormal,1.0));
    133.  
    134.                 return col;
    135.             }
    136.         ENDCG
    137.     }
    138. }
    139.  
    140. }
    141.  
     
  20. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    The shader was throwing an error "undeclared identifier" for unity_WorldTransformParams. Any ideas?

    So the whole "constant tangent" is a bug? Or is it a bug that the 3ds Max model was doing that? Also, thanks for your time on this!
     
  21. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    What version of Unity are you using? That's been part of the built in variables for quite a while now (checked back to 5.2.3).

    The constant tangent seems like a work around to prevent catastrophic failure in the shaders, but I wouldn't say it's a bug. The bug I saw with my 3ds max model was caused by me not doing "bitangent.xyz * tangent.w" in the shader because I'd commented it out to test something else.

    However that also told me that for some reason your model isn't properly having the tangent.w set on import. If you want you can try changing line #87 from:

    half tangentSign = v.tangent.w * unity_WorldTransformParams.w;

    to

    half tangentSign = v.tangent.w;

    That should remove the error you're seeing for the shader (which might be caused by using an older version of Unity?), and that value should only really be needed if you have an negative nonuniformly scaled mesh.
     
  22. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Whoops! Should have mentioned I'm using 5.1.3.

    Side note: I read some time back that compatibility was being reduced for builds in newer versions. I haven't been keeping up to date with the roadmap, but I'm reading now that OpenGL 2.0 is planned to be dropped. If I update to 5.3, will I lose any build compatibility? (Any OS's not supported, anything with OpenGL, etc?)

    Back on topic, I've applied the shader and pretty much gotten the same result as the shader I posted above. Bitangents look identical to before, both with and without derivatives. It's eating too much development time to worry about it for now. Even though the "constant tangent" isn't how the shader is supposed to work, any sort of bump mapping without artifacts is good enough for me. Thanks! *I would like your opinion on the side note however :^)
     
  23. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Shader model 2.0 and OpenGL 2.0 support on Windows is being dropped, which is basically only computers older than 10 years. That's happening with Unity 5.4 though.

    5.3 drops support for precompiled shaders, so if you're using shaders from there asset store there's a small chance they might stop working.

    I find it odd the mirrored normals still don't work with my debug shader when derivatives are turned on.

    http://blogs.unity3d.com/2015/08/27/plans-for-graphics-features-deprecation/
     
  24. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    Isn't this simply a limitation because the binormal is calculated?

    The tangent and binormal point along the mapping directions, which complete the tangent space base vectors. This allows you to do tangent space normal mapping, because these tangent space base vectors match the intended directions stored in the tangent space normal map. So far so good.

    The binormal is calculated though with fixed handedness determined by the platform. If you mirror the texture mapping over one axis however, the handedness of the tangent space vectors should also be mirrored.

    Based on the sign of the determinant of a matrix of the tangent space base vectors (sorry, I know, complicated) you would know whether the mapping is inverted or not. The problem is that the binormal is not stored and is calculated with the assumption that the mapping is not inverted. (And orthogonal.)

    calculated_binormal.png

    So, in conclusion, the mapping that can be used in combination with normal maps and calculated binormals is limited. The mapping should not be inverted and as orthogonal as possible. (Not skewed.)

    If 't be true thee wanteth to use normal maps,
    thee shouldn't skew thy mapping by too much.
     
    Last edited: Jun 14, 2016
    SomeGuy22 likes this.
  25. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Thanks for that link. Looks like they're still supporting DX9, just no GPUs that use Shader Model 2. And as you said, 5.4 isn't even out yet. And I don't have any precompiled shaders, so I guess I'll be updating!

    I've been making games for 5+ years and never have I been told not to make mirrored meshes for games. You'd think that this sort of information would be made more known, at least somewhere in the docs.
     
  26. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Inverting, mirroring, etc. should not be an issue. The tangent has the w component specifically to handle inversion and mirroring. Skewing is fine too, but only if the normal map was generated with skewed UVs. If you're using a "generic" normal map (like a brick wall, or floor tiles) it'll be weird, but if you're generating normal maps from a high resolution mesh onto a low poly in game asset this is fine. Skewed "generic" normal maps will also generally still look decent as long as they're not too badly skewed. The problem with skewing on generic normal maps is edges that are like | will end up like / but still be lit like |.
     
  27. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    I've used other engines where this was not the case, but it seems Unity has things sorted. I always assumed the w component was just adjusted to match the handiness of the target platform, but it seems it's actually also used to specifically solve the binormal mirroring issue.
     
  28. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    Ok I'm sorry to resurrect a post that's old about a year, but the problem still and looks like no one explain how to solve it.
    I work with 3DS Max, but I guess it's the same also for blender (maybe?)

    In my case I've mirrored a hand,so while Right Hand was fine, the left hand still have normals inverted and flip normals in 3DS didn't fix the problem.

    - It is caused by the pivot axis which become flipped when you mirror the mesh.
    to fix it, create a new mesh (a cube for example) convert it in editable poly, and then attach to it the mirrored mesh (left hand in my case)

    at this point the hand will gain a correct positioned pivot point.
    In sub-object delete the extra element (the cube in my case) and here you go, problem fixed.

    To avoid the issue to recreate the entire rig, you can use Skin Warp modifier
    Here is the tutorial on how it's work
     
  29. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    Skeletal meshes generally should be merged into a single mesh anyway before rigging, just for performance reasons, unless you're planning on being able to dynamically swap parts.

    But yes, 3ds Max can do some weird things with transforms that will cause problems for Unity (and Unreal, and pretty much every game engine I've used that takes FBX files). Most of the time using Reset Transform/Reset Xform will fix the problem, but the "append to cube" method is a time honored technique for un-F***ing transforms in Max, I've been doing that for nearly 20 years. :-/
     
    theANMATOR2b likes this.
  30. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
    Yeah - 18 years here.
    Just to be clear - Resetting xform upon creation of the mirrored hand is the correct process and avoids the attach to cube and skin wrap 'fix'. 5 seconds now saves the artist 5-15 minutes later.

    Also special note - reset xform before combining/merging the mesh just to be safe. Depending on which mesh is selected - determines which transform the combined mesh inherits. Don't mirror a mesh then attach the body to the new mirrored mesh. ;)
     
  31. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,788
    what is this "append to cube" thingy?
     
  32. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
     
  33. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    The pivot you see in max is not the actual pivot, but the actual pivot is the one that is used when exporting. Reset XForm resets the visual pivot, but not the actual pivot.

    That's how in max you can have instances with different pivots. The actual pivot is the same because of instancing, but the visual pivot can be different for each instance.

    "append to cube" is a fairly common technique because of this. (And "merge into new file" if it really gets crazy.)
     
  34. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,788
    ah i see, so that what it called. i've been doing that thing for ages :D
     
  35. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    Please have mercy, I know that my english Sucks :rolleyes::oops:


    1) Clone the mesh afflicted by "normals bug" (CTRL+V)
    2) Apply Reset XForm
    3) Apply Unify Normals modifier
    4) Collapse all
    5) Apply Skin Wrap modifier
    6) Select the original mesh with the Skin modifier applied correctly and Convert to Skin
    7) Export as FBX file
    8) Test it in unity and Enjoy
     
    Last edited: Jun 24, 2017
    theANMATOR2b likes this.
  36. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
    Hey Max - good video.

    One small suggestion that might help other artists in the future avoid this entire process.

    1. Turn off double sided materials in the Max viewport. You can't tell if the normals are flipped if you see both sides of the mesh in the viewport by default. When this is turned off (it is called something else in Max viewport settings) when a mistake like attaching the mesh incorrectly with the negative scaled value mesh selected - the mistake will be instantly noticeable in the Max viewport - avoids having to fix it later.
     
  37. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    the fact is that the normal in 3ds Max are fine, you 'll get it flipped in Unity 'cause the pivot is messed up (we guess)
    So in 3DS you still see your mesh fine. Usually this happen when you import mesh from other sources, for example when mesh was OBJ, or created in blender. BTW in the most of the case happen when you mirror it and then attack a mesh with another, and when booth didn't have the same "standard" pivot. That's why the solution still XForm, the Unity modifier with "Unify normals" is just an assurance... 'cause... never know ;)
     
  38. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
    Agree - just pointing out I was talking about backface cull - which is on by default whenever Max starts. I turn this off - because with backface cull off it mimics how the model will look in engine exactly.
    This can be turned off so imported meshes and new Max files do not have it turned on by default.
    With backface cull on - imported meshes can look correct even though the normals are inverted. A trained eye like yours can spot these issues, but an untrained eye will overlook it because the normals look 'normal'. By turning off backface cull on the mesh properties - this error is obvious and can't be ignored.

    However you are correct - this setting does not 'fix' the negative scale value which causes inverted normals upon import into Unity. I always xform meshes anyway mirrored, arrays, imported, scaled, xfrom all meshes before rigging and exporting - to keep from having to 'fix' it later. ;)
     
  39. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    It's a little different. In 3DS and Maya the oldschool method would be to duplicate the mesh and flip it and merge it back with the original. (Or the easier method which is to duplicate an instance and scale it so you have realtime updates on your mesh). However, Blender has a mirror modifier, which internally does all this work automatically. On top of that, it also correctly mirrors normals and displays the mesh as if it was one whole piece when imported into Unity.

    Because of this, my workflow has always been to preserve the mirror modifier as long as possible, and keep it when importing to Unity. This allows me to easily make changes later on down the line. This means I never "apply" the modifier, I always ever work with half of the mesh. (This also makes texturing easier). From what I understand, the issue is that tangents get screwed up because Unity can't properly interpret the information from the mirror modifier. Correct me if I'm wrong, but I think it has to do with the mirrored UV maps. Still not sure what's going on here and would really like some clarification again...

    I don't think pivot points was ever the original issue, all of my mirrored meshes were always one mesh in Unity. Once again, I'd really like to solve the original problem stated in the opening thread, but I'm unclear of which part exactly is causing the issue. Is it how Blender treats mirror modifiers? Is it how Unity interprets the mirrored information on import? Is it just a quirk of mirrored UV maps?

    EDIT: Reading back through the thread, it seems like the issue may come down to Binormal calculation. However, it's been mentioned that Unity uses the tangent "w" component to correct binormal calculation on mirrored meshes. Is this true? If so, why was my mesh broken?
     
    Last edited: Jul 6, 2017
  40. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    I don't blame Unity, I blame the fbx parsing tools Autodesk supplies. I've experienced this same issue with mirrored geometry in custom engines and even importing FBX files from Max into Maya. On previous projects I've worked on we had to export to ASCII FBX so we could manually parse and sometimes hand modify the files since we could not get the official fbx tools to give us the information we needed.
     
    theANMATOR2b likes this.
  41. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Then perhaps the issue would be solved if the mesh was exported in a different format. And for further clarification, when you import a Blender file in Unity it converts the .blend to ASCII FBX and uses that internally, which is what I've been doing. I'll run some tests and see if different formats change anything tomorrow.
     
  42. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,339
    The old school way of dealing with mesh assets was writing you own asset format and asset exporter for the modeling tools you were using as the older public or well understood formats (like obj or stl) are too limited in scope only offering vertex positions, vertex normals, and a single UV channel, or less.

    I know some companies also payed huge sums of cash to Autodesk or Alias|Wavefront to get access to the proprietary file formats for Max and Maya to try to remove the export step and parse the files directly, but that was rare.

    However it basically meant most studios were limited to a single 3d modeling program, or had to use various third party tools or convoluted work flows to transfer data between programs. I worked on one game where all static meshes had to be exported from 3dsmax, all skeletal meshes had to be exported from Maya, and all animation were exported from Filmbox (pre .fbx) for a little while before they gave up and exported everything from Maya. A few big studios spent the money to support multiple tools, but since those asset exporters were often project and program version specific, it was expensive.

    One of the less obvious benefits of the Unreal Engine was they supplied exporters for multiple 3d modeling programs and did all of the work to keep them up to date and working.

    When Sony released Collada in 2004 it was a huge hit and almost everyone switched to it. Finally a fully featured, open format with someone else supporting it. Then Autodesk bought Maya and Motionbuilder (what had been Filmbox prior to being bought by Alias) and made almost everything support the .fbx format (which had already started gaining a lot of traction), and they released tools and an API for getting data out of them ... and now everyone just uses .fbx and Collada effectively died in 2008.

    So why not use a different format? Because few are as full featured, ubiquitously supported, easily accessible (even if via proprietary tools), and interoperable as fbx. Even if it means occasionally having to deal with some weirdness with scaling, it's still less pain than most of the alternatives.
     
    theANMATOR2b, neoshaman and SomeGuy22 like this.
  43. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    We still use Collada quite often to keep it alive. Very accessible for human reading/understanding.
     
    bgolus likes this.
  44. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Bgolus, you deserve a thumbs up for still sticking around! Thanks for the info, I normally stick to .fbx and let Unity automatically convert .blends. I ran some tests using FBX ASCII, FBX Binary, Collada, and .obj--they all produced similar results. Still similar to the issues I was having before using the baked xNormal maps:



    However, when I tested a tileable normal map, it almost looked correct. But I realized that detailed tileable textures are hard to spot which side is up and which is down. And there was still a noticeable seam. When I moved the lighting around it became more obvious that one side was actually inverted, so the problem still stands.

    As a result, I don't think this is an issue with any specific importer. Maybe a Unity employee could shed some light on what's going on? This isn't a crucial issue, but it's been bothering me for a while and I'm curious about what could be going wrong. Having normal maps working correctly on mirrored meshes would be a huge advantage for my projects.
     
  45. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Alright, so after like 2 years of standing awestruck as to why this is happening, I believe I've figured out the problem. I think there are probably others who might run across this, so I'll try to explain as best I can. Actually, I don't quite understand it myself, and I'd really like to know how these specific steps cause the issue, but for now I know generally what's wrong.

    It was the maps.

    I checked and double-checked the normals, UVs, and from my last experiments the tangents. Everything was supposedly working correctly, and even multiple formats had the "issue" as well. And it's mentioned briefly around the forums that Unity should handle mirrored normal maps correctly as long as the tangents are correctly calculated. So if the model, Unity's importer, and Blender were all working correctly, that narrows down the issue to the material/shader itself.

    Sure enough, the root of the issue was the actual normal maps I was using. Take a look at this generated result:



    I had a testing map similar to this. So to test the problem, I inverted some channels, played around with the settings, etc. Nothing seemed to work at first. Until... I found the "Generate Map from Greyscale" option in the Texture importer. When I hit that the map changed to something like this:



    And then suddenly, there was no seam on the mirrored side. Obviously, the results were still atrocious (due to the greyscale generation) but the "inverted map" issue was clearly gone.

    So to test this I generated a new map with a different program, which yielded a map similar the latter image. And sure enough, the results were now what I expected them to be.

    Why I didn't find it sooner...

    It seems like something as simple as using a different program I could've found by now. And I probably would have, if I expected that maps themselves were causing the issue. I just don't understand how the map data could cause inconsistencies in the mirrored side even though both sides use the same UVs.

    At any rate, the problem wasn't exactly the program, rather the generation method. The program that was causing me issues was xNormal. It has a feature that can generate maps from images, so I would use that. However, it would ask for multiple light sources and multiple images, so me (being the lazy developer that I am) just filled all the slots with the same image. That must've been what was causing the maps to come out weird. Some of the channels were probably expecting a different light direction on the other images.

    When I switched programs, I used the heightmap generation algorithm instead of the light direction one. The heightmap generation is closer to what I was used to using with CrazyBump, which probably explains why I never noticed any issues with my older CrazyBump maps.

    Now it's not like I didn't know the results were different. I could clearly see that the xNormal maps were darker and didn't have the same colors as the other maps I used. But, as explained, I didn't think this would be an issue. After all, if "Channel Y" points to X direction, then surely on an inverted UV map, it would point the opposite. I assumed that nothing the map could do would influence the mirrored side.

    In conclusion

    I would like to understand why the map data can influence one mirrored side differently, but for now I have a working solution. For anyone else that runs across this, you'll need to make sure your baked maps are outputting the correct colors. For me, that involved using heightmap generation instead of light-based generation.

    Thanks to everyone who helped with this issue! I hope we all learned something new at least...
     
    Schazzwozzer and theANMATOR2b like this.