矩阵逆精度

Rip*_*pi2 5 opengl math matrix

我有一个大世界,大约5,000,000 x 1,000,000单位.相机可以靠近某个物体或足够远,以便看到整个世界.
我通过unprojecting(Z来自深度缓冲区)获得世界坐标中的鼠标位置.问题是它涉及矩阵逆.当使用大数字和小数字(例如,从原点平移并缩放以查看更多世界)时,计算变得不稳定.

试图看到这个逆矩阵的准确性,我看一下行列式.理想情况下,由于转换矩阵的性质,它永远不会为零.我知道,'det'一个小值本身并不意味着它,它可能是由于矩阵中的值很小.但它也可能是数字出错的标志.

我也知道我可以通过反转每个变换并乘以它们来计算逆.它提供更准确吗?

如何判断我的矩阵是否退化,是否存在数值问题?

Spe*_*tre 7

对于初学者,请参阅了解 4x4 齐次变换矩阵

  1. 提高累积矩阵的准确性(归一化)

    为了避免变换矩阵的退化,选择一个轴为主。我通常选择,Z因为它通常是我的应用程序中的视图或前进方向。然后利用叉积来重新计算/归一化其余的轴(它们应该彼此垂直,除非使用比例尺,否则也是单位尺寸)。这只能用于正交矩阵,因此没有偏斜或投影......正交矩阵必须缩放到正交矩阵,然后倒置,然后再缩放以使其可用。

    您不需要在每次操作后都执行此操作,只需在每个矩阵上进行操作的计数器,如果超过某个阈值,则对其进行归一化并重置计数器。

    检测此类矩阵的退化,您可以通过任意两个轴(应该为零或非常接近)之间的点积来测试正交性。对于正交矩阵,您还可以测试轴方向向量的单位大小......

    这是我的变换矩阵归一化在C++ 中的样子(对于正交矩阵):

    double reper::rep[16]; // this is my transform matrix stored as member in `reper` class
    //---------------------------------------------------------------------------
    void reper::orto(int test) // test is for overiding operation counter
    {
        double   x[3],y[3],z[3]; // space for axis direction vectors
        if ((cnt>=_reper_max_cnt)||(test)) // if operations count reached or overide
        {
            axisx_get(x);      // obtain axis direction vectors from matrix
            axisy_get(y);
            axisz_get(z);
            vector_one(z,z);   // Z = Z / |z|
            vector_mul(x,y,z); // X = Y x Z  ... perpendicular to y,z
            vector_one(x,x);   // X = X / |X|
            vector_mul(y,z,x); // Y = Z x X  ... perpendicular to z,x
            vector_one(y,y);   // Y = Y / |Y|
            axisx_set(x);      // copy new axis vectors into matrix
            axisy_set(y);
            axisz_set(z);
            cnt=0;             // reset operation counter
        }
    }
    
    //---------------------------------------------------------------------------
    void reper::axisx_get(double *p)
    {
        p[0]=rep[0];
        p[1]=rep[1];
        p[2]=rep[2];
    }
    //---------------------------------------------------------------------------
    void reper::axisx_set(double *p)
    {
        rep[0]=p[0];
        rep[1]=p[1];
        rep[2]=p[2];
        cnt=_reper_max_cnt; // pend normalize in next operation that needs it
    }
    //---------------------------------------------------------------------------
    void reper::axisy_get(double *p)
    {
        p[0]=rep[4];
        p[1]=rep[5];
        p[2]=rep[6];
    }
    //---------------------------------------------------------------------------
    void reper::axisy_set(double *p)
    {
        rep[4]=p[0];
        rep[5]=p[1];
        rep[6]=p[2];
        cnt=_reper_max_cnt; // pend normalize in next operation that needs it
    }
    //---------------------------------------------------------------------------
    void reper::axisz_get(double *p)
    {
        p[0]=rep[ 8];
        p[1]=rep[ 9];
        p[2]=rep[10];
    }
    //---------------------------------------------------------------------------
    void reper::axisz_set(double *p)
    {
        rep[ 8]=p[0];
        rep[ 9]=p[1];
        rep[10]=p[2];
        cnt=_reper_max_cnt; // pend normalize in next operation that needs it
    }
    //---------------------------------------------------------------------------
    
    Run Code Online (Sandbox Code Playgroud)

    向量操作如下所示:

    void  vector_one(double *c,double *a)
    {
        double l=divide(1.0,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
        c[0]=a[0]*l;
        c[1]=a[1]*l;
        c[2]=a[2]*l;
    }
    
    void  vector_mul(double *c,double *a,double *b)
    {
        double   q[3];
        q[0]=(a[1]*b[2])-(a[2]*b[1]);
        q[1]=(a[2]*b[0])-(a[0]*b[2]);
        q[2]=(a[0]*b[1])-(a[1]*b[0]);
        for(int i=0;i<3;i++) c[i]=q[i];
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 提高非累积矩阵的准确性

    您唯一的选择是至少使用double矩阵的准确性。最安全的是使用GLM或您自己的矩阵数学至少基于double数据类型(如我的reper班级)。

    便宜的替代方法是使用double精度函数,如

    glTranslated
    glRotated
    glScaled
    ...
    
    Run Code Online (Sandbox Code Playgroud)

    这在某些情况下会有所帮助,但并不安全,因为OpenGL实现可以将其截断为float. 此外,还没有 64 位硬件插值器,因此流水线级之间的所有迭代结果都被截断为floats。

    有时相对参考系会有所帮助(因此请保持对相似幅度值的操作),例如参见:

    射线和椭球相交精度提高

    此外,如果您使用自己的矩阵数学函数,您还必须考虑运算顺序,因此您总是会损失尽可能小的准确度。

  3. 伪逆矩阵

    在某些情况下,您可以避免通过行列式或霍纳方案或高斯消元法计算逆矩阵,因为在某些情况下,您可以利用正交旋转矩阵的转置也是其逆矩阵的事实。这是如何完成的:

    void matrix_inv(GLfloat *a,GLfloat *b) // a[16] = Inverse(b[16])
    {
        GLfloat x,y,z;
        // transpose of rotation matrix
        a[ 0]=b[ 0];
        a[ 5]=b[ 5];
        a[10]=b[10];
        x=b[1]; a[1]=b[4]; a[4]=x;
        x=b[2]; a[2]=b[8]; a[8]=x;
        x=b[6]; a[6]=b[9]; a[9]=x;
        // copy projection part
        a[ 3]=b[ 3];
        a[ 7]=b[ 7];
        a[11]=b[11];
        a[15]=b[15];
        // convert origin: new_pos = - new_rotation_matrix * old_pos
        x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
        y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
        z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
        a[12]=-x;
        a[13]=-y;
        a[14]=-z;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因此矩阵的旋转部分被转置,投影保持原样并重新计算原点位置,因此A*inverse(A)=unit_matrix编写此函数以便它可以就地使用,因此调用

    GLfloat a[16]={values,...}
    matrix_inv(a,a);
    
    Run Code Online (Sandbox Code Playgroud)

    也导致有效的结果。这种计算 Inverse 的方法更快且数值更安全,因为它挂起的操作更少(没有递归或减少没有除法)。粗略这仅适用于正交同质 4x4 矩阵!!!*

  4. 检测错误的逆

    因此,如果您有矩阵A及其逆矩阵,B则:

    A*B = C = ~unit_matrix
    
    Run Code Online (Sandbox Code Playgroud)

    因此,将两个矩阵相乘并检查单位矩阵...

    • 的所有非对角线元素的绝对和C应该接近于0.0
    • 的所有对角线元素都C应该接近+1.0