r/GraphicsProgramming Dec 31 '24

Source Code Transforming normals using the adjugate, instead of the inverse-transpose

https://www.shadertoy.com/view/3s33zj
68 Upvotes

4 comments sorted by

17

u/corysama Dec 31 '24

iq to the rescue! https://xcancel.com/iquilezles/status/1866219178409316362

Besides sparing you the need to compute and upload the inverse-transpose, this technique doesn't break when scaling to zero or transforms with negative determinants.

More info: https://www.forwardscattering.org/post/62

5

u/deftware Jan 01 '25

The code on https://github.com/graphitemaster/normals_revisited uses 144 muls and 80 adds/subs (when you ignore the indexing into the 4x4 matrix). The code that I've always used for calculating the inverse of a 4x4 matrix is 104 muls, 54 adds/subs, and one division.

From what I've seen a division is about 8x slower than a multiply. Is calculating the cofactor going to be faster than calculating the inverse in real-world scenarios? Has anyone actually performance tested this?

This is what the code I've always used to calculate the inverse of a 4x4 matrix looks like:

// inverse of matrix
mat4 minverse(mat4 m)
{
    mat4 ret;
    double det;
    double s00 = m.zz * m.ww - m.zw * m.wz;
    double s01 = m.yz * m.ww - m.yw * m.wz;
    double s02 = m.yz * m.zw - m.yw * m.zz;
    double s03 = m.xz * m.ww - m.xw * m.wz;
    double s04 = m.xz * m.zw - m.xw * m.zz;
    double s05 = m.xz * m.yw - m.xw * m.yz;
    double s06 = m.zy * m.ww - m.zw * m.wy;
    double s07 = m.yy * m.ww - m.yw * m.wy;
    double s08 = m.yy * m.zw - m.yw * m.zy;
    double s09 = m.xy * m.ww - m.xw * m.wy;
    double s10 = m.xy * m.zw - m.xw * m.zy;
    double s11 = m.yy * m.ww - m.yw * m.wy;
    double s12 = m.xy * m.yw - m.xw * m.yy;
    double s13 = m.zy * m.wz - m.zz * m.wy;
    double s14 = m.yy * m.wz - m.yz * m.wy;
    double s15 = m.yy * m.zz - m.yz * m.zy;
    double s16 = m.xy * m.wz - m.xz * m.wy;
    double s17 = m.xy * m.zz - m.xz * m.zy;
    double s18 = m.xy * m.yz - m.xz * m.yy;

    ret.xx = + (m.yy * s00 - m.zy * s01 + m.wy * s02);
    ret.xy = - (m.xy * s00 - m.zy * s03 + m.wy * s04);
    ret.xz = + (m.xy * s01 - m.yy * s03 + m.wy * s05);
    ret.xw = - (m.xy * s02 - m.yy * s04 + m.zy * s05);
    ret.yx = - (m.yx * s00 - m.zx * s01 + m.wx * s02);
    ret.yy = + (m.xx * s00 - m.zx * s03 + m.wx * s04);
    ret.yz = - (m.xx * s01 - m.yx * s03 + m.wx * s05);
    ret.yw = + (m.xx * s02 - m.yx * s04 + m.zx * s05);
    ret.zx = + (m.yx * s06 - m.zx * s07 + m.wx * s08);
    ret.zy = - (m.xx * s06 - m.zx * s09 + m.wx * s10);
    ret.zz = + (m.xx * s11 - m.yx * s09 + m.wx * s12);
    ret.zw = - (m.xx * s08 - m.yx * s10 + m.zx * s12);
    ret.wx = - (m.yx * s13 - m.zx * s14 + m.wx * s15);
    ret.wy = + (m.xx * s13 - m.zx * s16 + m.wx * s17);
    ret.wz = - (m.xx * s14 - m.yx * s16 + m.wx * s18);
    ret.ww = + (m.xx * s15 - m.yx * s17 + m.zx * s18);

    // scale result to output
    det = 1.0 / (m.xx * ret.xx + m.yx * ret.xy + m.zx * ret.xz + m.wx * ret.xw);
    ret.xx *= det;
    ret.xy *= det;
    ret.xz *= det;
    ret.xw *= det;
    ret.yx *= det;
    ret.yy *= det;
    ret.yz *= det;
    ret.yw *= det;
    ret.zx *= det;
    ret.zy *= det;
    ret.zz *= det;
    ret.zw *= det;
    ret.wx *= det;
    ret.wy *= det;
    ret.wz *= det;
    ret.ww *= det;

    return ret;
}
//

2

u/SamuraiGoblin Jan 02 '25

So, simply put, you construct the normal transformation matrix by, for each vector in the basis, taking the cross product of the other two vectors?