Search Unity

Skinned Mesh root bone changes vertex normals in shader

Discussion in 'Shaders' started by ginconic, Oct 16, 2017.

  1. ginconic

    ginconic

    Joined:
    May 19, 2015
    Posts:
    89
    I'm currently re-writing my tessellation shaders and I'm adding skinned mesh support, but I've encountered a weird issue.

    When the skinned mesh has a 'root bone' transform assigned, it changes the normals across the entire mesh. What I find strange though is that this affects lighting and normal maps as well. Though I could fix this in the shader, I would like to know why this is the case to begin with.

    Here are some images to illustrate what I mean using the debug shader that shows the mesh normals.
    The mesh to the left is a non-skinned mesh renderer with the same mesh. On the right we have the cowboy model as a skinned mesh.

    Here the skinned mesh has the pelvis bone as a 'root bone' (it's how the artist made the prefab):
    cftd-cowboy1.jpg


    Here the cowboy has no 'root bone' transform at all (and it looks and performs like the mesh renderer one):
    cftd-cowboy2.jpg

    And here the cowboy has the cube as a 'root bone' and the cube is rotated in a random direction:
    cftd-cowboy3.jpg

    Wouldn't this be confusing for artists, as when they assign a root bone, their normal maps would suddenly be possibly wrong, even though they have exported them right from their modelling app?
     
  2. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    The normals aren't changing when the root bone changes, how the lighting is calculated is what's changing. How are you calculating your normals in shader? You shouldn't be getting a bright color like that if you're using purely normal direction.

    This is why if you have your mesh sliced into parts that need to match up seamlessly, you need to make those parts share the same root bone. Skinned meshes can be deformed all manner of way by the bones that influence them, so they need a defined "center" point to base lighting calculations from.
     
  3. ginconic

    ginconic

    Joined:
    May 19, 2015
    Posts:
    89

    This is literally Unity's normal debug viz shader:

    Code (CSharp):
    1. Shader "Debug/Normals" {
    2. SubShader {
    3.     Pass {
    4.         CGPROGRAM
    5.         #pragma vertex vert
    6.         #pragma fragment frag
    7.         #include "UnityCG.cginc"
    8.  
    9.         // vertex input: position, normal
    10.         struct appdata {
    11.             float4 vertex : POSITION;
    12.             float3 normal : NORMAL;
    13.         };
    14.  
    15.         struct v2f {
    16.             float4 pos : SV_POSITION;
    17.             fixed4 color : COLOR;
    18.         };
    19.        
    20.         v2f vert (appdata v) {
    21.             v2f o;
    22.             o.pos = UnityObjectToClipPos(v.vertex );
    23.             o.color.xyz = v.normal * 0.5 + 0.5;
    24.             o.color.w = 1.0;
    25.             return o;
    26.         }
    27.        
    28.         fixed4 frag (v2f i) : SV_Target { return i.color; }
    29.         ENDCG
    30.     }
    31. }
    32. }

    The normals need to change when some animated bone is rotated, otherwise when a given character rises an arm, the lighting would be as if the arm is in the bind pose. But I think your answer does indeed answer the question: the root bone seems to also ensure identical lighting / aligned normals across for all different mesh parts.
    So then say, if a character has a glove prop, which is a separate model completely, would the 'root bone' be the same as the character's?
     
  4. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Not really, only if you need the lighting to be be perfectly consistent across a surface. For an accessory like that, it's better to let the lighting be consistent to the center of that accessory instead of the whole character.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    What matters is the calculated world normal, not the object normal. Just dumping out "v.normal" doesn't really give you anything useful without knowledge of what the object to world transform is doing.

    Try:
    o.color.xyz = UnityObjectToWorldNormal(v.normal) * 0.5 + 0.5;

    I suspect you'll find all versions of the mesh look exactly the same now (and probably all different than they did before). This is actually the great advantage of tangent space normal maps. Tangent space normal maps do not actually care what the surface normal direction is, as long as there is a transform to convert it from tangent space to world space correctly. The object space surface normal direction and tangent.

    Basically, this shouldn't be something you need to be concerned about.
     
    earrgames likes this.
  6. ginconic

    ginconic

    Joined:
    May 19, 2015
    Posts:
    89

    Ah, but is is exactly what's happening: the root bone transform completely overrides the skinned mesh transform, but on a shader level there is no way to know about this. I suppose this is some kind of common knowledge, but for some reason I expected different. Like a global matrix passed down or something.

    While I have't tested, I believe this could lead to the flowing problems: if the root bone transform has a negative scale somewhere, normals on the mesh will be flipped; or, if the transom of the root bone is different form the original mesh transform, tangent space normal maps would produce different results. I suppose a negative scaled bone is a no-no anyway, or an artist would notice the normal maps look different and fiddle with the channels until they look right, but what if the root bone changes during gameplay? Or is that not expected behavior?

    In my shader, I need the normals to calculate the control points in the hull shader. Normals are cached and stored, but the way I do it, I just take the nomals from the shardmesh. If the original transforms match up, all is fine, if not then well, it's all messed up. So I can pass down a matrix at level load, but then I was wonder if I should constantly monitor for changes in the root bone. In any case, I'm now getting plenty of other problems when working with skinned meshes, so that's really the least bit of concern I'm having :)
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    When a skeletal mesh is transformed by animation, so too are the normals. The normals from the shared mesh will only ever match the skeletal mesh when it is in its default pose, and as you noticed, without a root bone. I've noticed skeletal meshes imported with a root bone tend to be rotated 90 degrees, likely due to coordinate system differences between the modeling tool and Unity and how Unity is importing those meshes and bones.
     
  8. GGeff

    GGeff

    Joined:
    Nov 11, 2014
    Posts:
    40
    What's the solution for this? I'm having the same sort of issues with normals not correctly being transformed to world space via UnityObjectToWorldNormal(v.normal) in the vertex shader when a root bone is assigned.
     
    kyuskoj likes this.