Getting correct pre-skinned normals/tangents in shader

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:
Shader code

float diffNorm = 1-(dot(i.localNorm, i.preskinNorm));
outEmission = float4(saturate(diffNorm),0,0,0);

theres a bunch of discrepancies in a weird pattern:

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:
C# Script

void Start () {
        smr = GetComponent<SkinnedMeshRenderer>();
        Mesh m = smr.sharedMesh;
        List<Vector3> norms = new List<Vector3>();
        List<Vector4> tangs = new List<Vector4>();
        Quaternion rot = Quaternion.Inverse(smr.rootBone.rotation);
        for (int i = 0; i<m.vertexCount; i++)
        {
            norms.Add(rot * (m.normals));
            tangs.Add(rot * (m.tangents));
        }
        m.SetUVs(2, norms);
        m.SetUVs(3, tangs);
        smr.sharedMesh = m;
    }

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?

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.

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.

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.
3394904--267003--diff.PNG

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?

1 Like

Nope, that initial screenshot was made with the shader code i posted above initially, no multiplication at all, just an inverted dot.

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.

1 Like

Damn, yeah, my bad. Now i see. Thanks again!