线性变化、仿射变换、正交矩阵概念及矩阵求逆

"游戏引擎"

Posted by A-SHIN on July 19, 2018

“Yeah It’s on. ”

前言

基础概念:
3D向量(x,y,z)变换的处理方式如下:
旋转变换:乘法运算
缩放变换:乘法运算
平移变换:加法运算
加入齐次坐标(x,y,z,w)概念后,使得平移变换也能使用矩阵乘法。点w为1,向量w为0。
其中旋转缩放属于线性变化,平移为非线性。
仿射变换:先进行线性变换,再进行平移变换的变换称之为仿射变换。
如果矩阵第四行为[0,0,0,1]则为仿射矩阵。
3D游戏开发中常用MVP,其中M、V、MV都属于仿射矩阵。P不属于仿射矩阵。常用的矩阵求逆操作(如:求视图空间法线)多数为仿射矩阵求逆。
正交矩阵:矩阵中的任意两个行向量的乘积为0,矩阵中每一个行向量都是单位向量。
没有经过缩放的旋转矩阵就是一个正交矩阵。 其转置矩阵即为逆矩阵。

正文

仿射矩阵求逆:
3x3一般矩阵:
If cannot find inverse (det=0), set identity matrix
M^-1 = adj(M) / det(M)
| m4m8-m5m7 m5m6-m3m8 m3m7-m4m6 |
= | m7m2-m8m1 m0m8-m2m6 m6m1-m7m0 | / det(M)
| m1m5-m2m4 m2m3-m0m5 m0m4-m1m3 |

Matrix3& Matrix3::invert()
{
    float determinant, invDeterminant;
    float tmp[9];

    tmp[0] = m[4] * m[8] - m[5] * m[7];
    tmp[1] = m[7] * m[2] - m[8] * m[1];
    tmp[2] = m[1] * m[5] - m[2] * m[4];
    tmp[3] = m[5] * m[6] - m[3] * m[8];
    tmp[4] = m[0] * m[8] - m[2] * m[6];
    tmp[5] = m[2] * m[3] - m[0] * m[5];
    tmp[6] = m[3] * m[7] - m[4] * m[6];
    tmp[7] = m[6] * m[1] - m[7] * m[0];
    tmp[8] = m[0] * m[4] - m[1] * m[3];

    // check determinant if it is 0
    determinant = m[0] * tmp[0] + m[1] * tmp[3] + m[2] * tmp[6];
    if(fabs(determinant) <= EPSILON)
    {
        return identity(); // cannot inverse, make it idenety matrix
    }

    // divide by the determinant
    invDeterminant = 1.0f / determinant;
    m[0] = invDeterminant * tmp[0];
    m[1] = invDeterminant * tmp[1];
    m[2] = invDeterminant * tmp[2];
    m[3] = invDeterminant * tmp[3];
    m[4] = invDeterminant * tmp[4];
    m[5] = invDeterminant * tmp[5];
    m[6] = invDeterminant * tmp[6];
    m[7] = invDeterminant * tmp[7];
    m[8] = invDeterminant * tmp[8];

    return *this;
}

4x4仿射矩阵:
M = [ R | T ]
[ –+– ] (R 表示 3x3 rotation/scale/shear matrix)
[ 0 | 1 ] (T 表示 1x3 translation matrix)

y = Mx -> y = Rx + T -> x = R^-1(y - T) -> x = R^-1y - R^-1*T

[ R | T ]-1 [ R^-1 | -R^-1 * T ]
[ –+– ] = [ —–+———- ]
[ 0 | 1 ] [ 0 + 1 ]

Matrix4& Matrix4::invertAffine()
{
    // R^-1
    Matrix3 r(m[0],m[1],m[2], m[4],m[5],m[6], m[8],m[9],m[10]);
    r.invert();
    m[0] = r[0];  m[1] = r[1];  m[2] = r[2];
    m[4] = r[3];  m[5] = r[4];  m[6] = r[5];
    m[8] = r[6];  m[9] = r[7];  m[10]= r[8];

    // -R^-1 * T
    float x = m[12];
    float y = m[13];
    float z = m[14];
    m[12] = -(r[0] * x + r[3] * y + r[6] * z);
    m[13] = -(r[1] * x + r[4] * y + r[7] * z);
    m[14] = -(r[2] * x + r[5] * y + r[8] * z);

    // last row should be unchanged (0,0,0,1)
    //m[3] = m[7] = m[11] = 0.0f;
    //m[15] = 1.0f;

    return * this;
}

后记

容易混淆概念:线性变化、仿射变换、正交矩阵