Search Unity

Question Which gets the inverse multiplication - normals or tangents?

Discussion in 'Shaders' started by SavedByZero, Nov 6, 2022.

  1. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    So, brand new to making shaders, bear with me...

    The Unity Shader Bible (which, IMO, has a lot of important information but is pretty poor at explaining it) on page 199 has normals being multiplied by the ObjectToWorld matrix with a standard transformation, and tangents being multiplied by an inverse WorldToObject matrix -- if I even have that right.

    However, internet examples all show that you're supposed to do the exact opposite of that.

    Which one is correct? And why? Further, I'm having trouble understanding why you need this inverse trickery instead of just transforming both with ObjectToWorld matrix, especially as the results in the first "lit" example shader look the same when I do either one.The book sure doesn't explain it, just tosses it in as something you do and breezes forward.
    Thanks...
    (I'm thinking I have a bright future ahead as a programmer of....unlit shaders)
     
    Last edited: Nov 6, 2022
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Normals are transformed by the transposed inverse object to world matrix.
    Tangents are transformed by the object to world matrix.

    The Unity Shader Bible is wrong if they’re doing the opposite.

    If you’re only using uniformly scaled transforms (scaling all the axes the same) you can just use the object to world for both, because the transpose of a uniform matrix is identical to the inverse of that matrix, so the transposed inverse of a matrix is equal to the original matrix.

    As for why, I find this visual example to be the easiest way to understand which one is correct. Lets say you have a diamond shape, like this:
    upload_2022-11-6_20-24-38.png
    Each flat side's normal is perpendicular to that side.

    But what happens if we scale this non ununiformly, and use the same transform matrix on the normals? You get this:
    upload_2022-11-6_20-28-15.png
    Those normals are no longer perpendicular to the sides! They're getting "squished" with the shape, which isn't what you want. Those sides are now facing more "up" and "down", which is the direction you want the normals to face.

    But if you use a transposed inverse matrix for the normals, you get these normals:
    upload_2022-11-6_20-34-9.png
    And those are the normals you want.

    As for tangents, those vectors are parallel to each side, so you do want them to "squish" with the shape.


    As for the mathematical reason ... you might need to go take some college math classes to understand that. I certainly don't.
     
    lilacsky824 likes this.
  3. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    Thanks. That book could use more explanations like the one you gave, although my spatial perception must be bad because the lines in the "correct" transformation look more crooked compared to where the sides are (with what look like 100 degree angles on one side and 80 degree angles on the other, roughly) and the lines on the incorrect one look like they're more properly perpendicular? I don't get it.

    Weirdly, I get an error when I rewrite it to be this way:
    o.tangent_world = UnityObjectToWorldNormal(v.tangent);
    Because tangent was written as a float4 in the appdata and v2f, and normal is a float3, which this function seems to be expecting. EDIT: I seem to have fixed it by changing it to v.tangent.xyz and changing tangent_world to a float3, but whatever the intentions were here with the example code they wrote, it seems like this was more than just a typo.

    .
    I took Linear Algebra long ago, let's say wasn't the greatest student in the class. All I took away from it was the ability to multiply basic matrices that have the row/column match-up, and a very general idea of what they do. If I concentrate hard and take the time to understand why they work mathematically, I can kind of get it for a little while, but if I go away from it for a long time they just become things I remember to plug in because they get the results I want.
     
    Last edited: Nov 7, 2022
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    It kind of becomes an illusion. It's easier to see that it's correct if you focus on one side at a time.
    EE6856EE-A0E2-4E40-A8F8-43A0EDB827B7.jpeg
     
    SavedByZero likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    v.tangent
    is a
    float4
    because the xyz is the vector, and the w is the orientation of the bitangent (aka binormal), which is calculated with a cross product of the normal and tangent.

    You can either calculate the bitangent vector in the vertex shader and pass that to fragment shader, or pass that w value, which is just a 1.0 or -1.0 and calculate the bitangent in the fragment shader. Neither option is the “correct”, though the normal maps have to be generated knowing which way the bitangent is being calculated as they produce slightly different results.
     
    SavedByZero likes this.
  6. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    Yeah, the final line in the vertex phase in that book code is:
    Code (CSharp):
    1.  o.binormal_world = normalize(cross(o.normal_world, o.tangent_world) * v.tangent.w);
    Which sounds like it describes what you mention as option 1.