Search Unity

Getting correct pre-skinned normals/tangents in shader

Discussion in 'Shaders' started by customphase, Feb 12, 2018.

  1. customphase

    customphase

    Joined:
    Aug 19, 2012
    Posts:
    246
    Im trying to reconstruct a bone rotation transform in the shader, and for that i need pre-skinned normals and tangents. I do that as a pre-process step in a c# script assigning them onto free uv channels. However, when i debug the difference between my pre-skinned normals set from script and local normals from the NORMAL semantic like this:
    Code (CSharp):
    1. float diffNorm = 1-(dot(i.localNorm, i.preskinNorm));
    2. outEmission = float4(saturate(diffNorm),0,0,0);
    theres a bunch of discrepancies in a weird pattern:
    normsProb.PNG

    The skeleton pose here is absolutely unmodified, so in theory it should just be all black.

    And heres how i set the normals from script:
    Code (CSharp):
    1. void Start () {
    2.         smr = GetComponent<SkinnedMeshRenderer>();
    3.         Mesh m = smr.sharedMesh;
    4.         List<Vector3> norms = new List<Vector3>();
    5.         List<Vector4> tangs = new List<Vector4>();
    6.         Quaternion rot = Quaternion.Inverse(smr.rootBone.rotation);
    7.         for (int i = 0; i<m.vertexCount; i++)
    8.         {
    9.             norms.Add(rot * (m.normals));
    10.             tangs.Add(rot * (m.tangents));
    11.         }
    12.         m.SetUVs(2, norms);
    13.         m.SetUVs(3, tangs);
    14.         smr.sharedMesh = m;
    15.     }

    What am i doing wrong? Should i be rotating normals/tangents in the script by all the bones affecting the current vertex? How do i do that?
     
  2. customphase

    customphase

    Joined:
    Aug 19, 2012
    Posts:
    246
    Nevermind, the script was correct. The problem was with the fact that in shader i needed to normalize the normals per-pixel, in addition to per-vertex. Although i dont understand how 2 exactly same normals interpolated over exactly the same triangle using the same technique, without normalization, can result in such vast difference between their values in pixel shader.
     
    Last edited: Feb 17, 2018
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    They should be identical, assuming they’re being handled identically too. That certainly looks like what I would expect from comparing an in fragment shader normalized normal vector to it's in vertex shader normalized twin.
     
  4. customphase

    customphase

    Joined:
    Aug 19, 2012
    Posts:
    246
    They ARE identical, i think. This is what their difference looks like after proper normalization. Its multiplied by 1.000.000 to start being visible, and even then it looks like its just a precision problem at this magnitude.
    diff.PNG
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Copying the skinned mesh's initial normals into extra UVs and comparing them to the skinned mesh normals post skinning (even in a bind pose that matches the mesh's un-deformed state) I would expect to be every so slightly different, just because a lot of things are happening to those mesh normals before the shader gets to them and floating point math is a bitch fun. Multiplying the values by a million and getting a little noise in this case I would say is totally expected. That mesh normal, even in the bind pose, is still being multiplied by multiple bone matrices, ones that you might expect are identity matrices (no rotation, no position offset, no scaling), but probably aren't exactly perfectly so. Again, floating point numbers ... and a bone that should have zero rotation might be 0.00001 degrees off from perfectly zero'd rotation.


    But that's a different problem than the original one. I assume the original shader wasn't multiplying by a million too?
     
    Last edited: Feb 17, 2018
    customphase likes this.
  6. customphase

    customphase

    Joined:
    Aug 19, 2012
    Posts:
    246
    Nope, that initial screenshot was made with the shader code i posted above initially, no multiplication at all, just an inverted dot.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    So the issue is the dot product of two identical non unit length vectors isn't 1.0. When a normal is interpolated, it's just lerping between the value on the three vertices. The example I give is a lerp between (1,0,0) and (0,1,0) is (0.5, 0.5, 0). The dot product of (0.5, 0.5, 0.0) and (0.5, 0.5, 0.0) isn't 1, it's 0.5! So you are seeing the dot product of two effectively identical values. Even if you do that dot product with the local normal against itself it would have the same effect unless you've normalized the value.

    Edit: the noise you see in your normalized version will probably also show up if you do dot(normalize(localNorm), normalize(localNorm)) because you're effectively visualizing the limits of floating point precision.
     
    Last edited: Feb 17, 2018
    customphase likes this.
  8. customphase

    customphase

    Joined:
    Aug 19, 2012
    Posts:
    246
    Damn, yeah, my bad. Now i see. Thanks again!