矩阵逆精度

时间:2017-02-16 22:51:54

标签: opengl math matrix

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

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

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

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

2 个答案:

答案 0 :(得分:4)

初学者请参阅Understanding 4x4 homogenous transform matrices

  1. 提高累积矩阵的准确度(标准化)

    避免退化变换矩阵选择一个轴作为主轴。我通常选择Z,因为它通常是我的应用中的查看或前进方向。然后利用交叉乘积来重新计算/标准化其余的轴(它们应该彼此垂直,除非使用比例,然后是单位大小)。这只能用于正交/正交矩阵,因此不会出现偏斜或投影......

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

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

    以下是 C ++ 中我的变换矩阵规范化( orthonormal 矩阵)的样子:

    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
            }
    //---------------------------------------------------------------------------
    

    矢量操作如下所示:

    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];
            }
    
  2. 提高非累积矩阵的准确性

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

    廉价替代方案是使用double精度函数,如

    glTranslated
    glRotated
    glScaled
    ...
    

    在某些情况下有帮助但不安全,因为 OpenGL 实施可以将其截断为float。此外,还没有64位 HW 插值器,因此管道阶段之间的所有迭代结果都被截断为float s。

    有时相对参考帧有帮助(因此保持对类似幅度值的操作)例如见:

    此外,如果您使用自己的矩阵数学函数,您还必须考虑操作的顺序,这样您总是会失去最小的精度。

  3. 伪逆矩阵

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

    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;
            }
    

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

    GLfloat a[16]={values,...}
    matrix_inv(a,a);
    

    也会产生有效的结果。这种计算Inverse的方式更快更安全,因为它可以减少操作(没有递归或减少没有划分)。粗这仅适用于正交均匀4x4矩阵!!! *

  4. 检测错误的反向

    因此,如果你得到矩阵A及其倒数B,那么:

    A*B = C = ~unit_matrix
    

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

    • C的所有非对角线元素的abs总和应接近0.0
    • C的所有对角线元素都应接近+1.0

答案 1 :(得分:0)

在一些实验之后,我看到(说到变换,而不是任何矩阵)矩阵的对角线(即比例因子)(m,在反转之前)是决定性值的主要因素。

所以我将产品p= m[0] · m[5] · m[10] · m[15](如果所有这些都是!= 0)与行列式进行比较。如果他们相似0.1 < p/det < 10我可以&#34;信任&#34;不知何故,在逆矩阵中。否则,我有数字问题,建议改变渲染策略。