opengl - 矩陣逆精度




math matrix (2)

對於初學者,請參閱 了解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
            }
    //---------------------------------------------------------------------------

    向量運算看起來像這樣:

    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 精度的矩陣。 最安全的方法是至少基於 double 數據類型(例如我的 reper 類)使用 GLM 或自己的矩陣數學。

    便宜的選擇是使用 double 精度函數,例如

    glTranslated
    glRotated
    glScaled
    ...

    這在某些情況下有幫助但不安全,因為 OpenGL 實現可以將其截斷為 float 。 此外,還沒有64位 HW 插值器,因此,流水線級之間的所有迭代結果都被截斷為 float

    有時,相對參考框架會有所幫助(因此,請對相似的幅度值進行操作),例如,請參見:

    • 射線和橢球相交精度提高

    另外,如果您使用自己的矩陣數學函數,則還必須考慮運算順序,這樣一來,您始終會損失盡可能小的精度。

  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 的所有非對角元素的絕對和應接近 0.0
    • C 所有對角線元素應接近 +1.0

我的世界很大,大約有5,000,000 x 1,000,000單位。 相機可以靠近某個物體,也可以足夠遠以看到整個世界。
我通過不投影獲得鼠標在世界坐標中的位置(Z來自深度緩衝區)。 問題在於它涉及 矩陣逆 。 同時使用大小數字(例如,遠離原點平移並縮放以查看更多世界)時,計算將變得不穩定。

為了查看該 逆矩陣 的準確性,我看了行列式。 理想地,由於轉換矩陣的性質,它永遠不會為零。 我知道“小”值本身並不意味著什麼,這可能是由於矩陣中的小值所致。 但這也可能是數字錯誤的跡象。

我也知道我可以通過反轉每個變換並將它們相乘來計算逆。 它提供更多準確性嗎?

我怎麼知道我的矩陣是否退化,遭受數值問題?


經過一些實驗,我發現(談到變換,而不是任何矩陣)矩陣( m ,反轉之前)的對角線(即縮放因子)是決定性值的主要負責人。

因此,我將乘積 p= m[0] · m[5] · m[10] · m[15] (如果它們全部都是= 0)與行列式進行比較。 如果它們相似,則 0.1 < p/det < 10 我可以在逆矩陣中以某種方式“信任”。 否則,我會遇到一些數字問題,建議您更改渲染策略。





matrix