1. 程式人生 > IOS開發 >ARKit 中矩陣的簡單再理解

ARKit 中矩陣的簡單再理解

矩陣的資料型別

一般在蘋果的 ARKit 和 SceneKit 中,用到的矩陣有三種SCNMatrix4simd_float4x4GLKMatrix4

GLKMatrix4是從 OpenGL 框架 GLKit 中帶過來的,各種函式很全面。
SCNMatrix4最初是給 SceneKit 使用的,後來擴充套件到 ARKit 中,矩陣操作函式不夠全面,比如沒有轉置函式 transpose。
simd_float4x4看名字就知道是 simd 型別的,是後來為了取代SCNMatrix4來推出的,各種函式也很全面。

上面三種類型本質都是結構體,互相轉換的方法也在蘋果文件中給出了,分別為:

SCNMatrix4
SCNMatrix4FromMat4(simd_float4x4 m) { return *(SCNMatrix4 *)&m; } simd_float4x4 SCNMatrix4ToMat4(SCNMatrix4 m) { return (simd_float4x4){ .columns[0] = simd_make_float4(m.m11,m.m12,m.m13,m.m14),.columns[1] = simd_make_float4(m.m21,m.m22,m.m23,m.m24),.columns[2] = simd_make_float4(m.m31,m.m32,m.m33,m.m34),.columns[3
] = simd_make_float4(m.m41,m.m42,m.m43,m.m44) }; } GLKMatrix4 SCNMatrix4ToGLKMatrix4(SCNMatrix4 mat);//未公開方法實現的程式碼,不過都是結構體,估計是直接強制轉換的 SCNMatrix4 SCNMatrix4FromGLKMatrix4(GLKMatrix4 mat);//未公開方法實現的程式碼 複製程式碼

行主序row-order與列主序column-order

一般在蘋果的 Demo 和說明中,用到的矩陣SCNMatrix4simd_float4x4GLKMatrix4都是列主序的。

比如在 SCNMatrix4 結構體的定義,及相關平移方法SCNMatrix4MakeTranslation中就能看出,平移向量被賦值.m41 = tx,.m42 = ty,.m43 = tz,就說明被賦值的是第四列的第 1,2,3 個元素:

//m11 指第一列第一個元素
typedef struct SCNMatrix4 {
    float m11,m12,m13,m14;
    float m21,m22,m23,m24;
    float m31,m32,m33,m34;
    float m41,m42,m43,m44;
} SCNMatrix4;

SCNMatrix4 SCNMatrix4MakeTranslation(float tx,float ty,float tz) {
    return (SCNMatrix4){
        .m11 = 1.f,.m12 = 0.f,.m13 = 0.f,.m14 = 0.f,.m21 = 0.f,.m22 = 1.f,.m23 = 0.f,.m24 = 0.f,.m31 = 0.f,.m32 = 0.f,.m33 = 1.f,.m34 = 0.f,.m41 =  tx,.m42 =  ty,.m43 =  tz,.m44 = 1.f
    };
}
複製程式碼

而 simd_float4x4 更為直接,採用了名為columns[4]的陣列,這就告訴了我們,這個矩陣是列主序的


/*! @abstract A matrix with 4 rows and 4 columns.*/
typedef struct { simd_float4 columns[4]; } simd_float4x4;
複製程式碼

SCNMatrix4ToMat4()函式將 SCNMatrix4 矩陣轉換成 simd_float4x4 矩陣,也會看到 .m12 處數值對應的是 columns[0][1] 位置。這也說明了 SCNMatrix4 是列主序的。

轉置transpose

如果我們需要對矩陣進行變換,從行主序變為列主序,或從列主序變成行主序,那就需要轉置 transpose:

SCNMatrix4 scnMat;
simd_transpose(SCNMatrix4ToMat4(scnMat));
GLKMatrix4Transpose(SCNMatrix4ToGLKMatrix4(scnMat));
複製程式碼

一般什麼時候需要轉置呢?
主要看用途:一般線性代數教材中的公式,都是行主序的,所以經常會出現在工具方法中,為了更好的抄公式採用了行主序,最後再轉置為列主序矩陣。

還有一種常見的是:利用正交矩陣的特性,用矩陣轉置來代替矩陣求逆運算。比如,矩陣 A 代表將一個小球從 a 點變換(只有平移和旋轉)到 b 點,矩陣 B 則相反,代表將一個小球從 b 點變換(只有平移和旋轉)到 a 點。由於 A 不好求解,那隻需要求出 B 矩陣,轉置後就得到了 A 矩陣。需要注意的是,在求解 B 的過程中,如果進行平移,應操作.m14 = tx,.m24 = ty,.m34 = tz,即當做行主序矩陣來操作,最後轉置後才是正確的結果。

附蘋果文件中的轉置方法程式碼:

static simd_float4x4 SIMD_CFUNC simd_transpose(simd_float4x4 __x) {
#if defined __SSE__
    simd_float4 __t0 = _mm_unpacklo_ps(__x.columns[0],__x.columns[2]);
    simd_float4 __t1 = _mm_unpackhi_ps(__x.columns[0],__x.columns[2]);
    simd_float4 __t2 = _mm_unpacklo_ps(__x.columns[1],__x.columns[3]);
    simd_float4 __t3 = _mm_unpackhi_ps(__x.columns[1],__x.columns[3]);
    simd_float4 __r0 = _mm_unpacklo_ps(__t0,__t2);
    simd_float4 __r1 = _mm_unpackhi_ps(__t0,__t2);
    simd_float4 __r2 = _mm_unpacklo_ps(__t1,__t3);
    simd_float4 __r3 = _mm_unpackhi_ps(__t1,__t3);
    return simd_matrix(__r0,__r1,__r2,__r3);
#else
    return simd_matrix((simd_float4){__x.columns[0][0],__x.columns[1][0],__x.columns[2][0],__x.columns[3][0]},(simd_float4){__x.columns[0][1],__x.columns[1][1],__x.columns[2][1],__x.columns[3][1]},(simd_float4){__x.columns[0][2],__x.columns[1][2],__x.columns[2][2],__x.columns[3][2]},(simd_float4){__x.columns[0][3],__x.columns[1][3],__x.columns[2][3],__x.columns[3][3]});
#endif
}

GLKMatrix4 GLKMatrix4Transpose(GLKMatrix4 matrix)
{
#if defined(__ARM_NEON__)
    float32x4x4_t m = vld4q_f32(matrix.m);
    return *(GLKMatrix4 *)&m;
#else
    GLKMatrix4 m = { matrix.m[0],matrix.m[4],matrix.m[8],matrix.m[12],matrix.m[1],matrix.m[5],matrix.m[9],matrix.m[13],matrix.m[2],matrix.m[6],matrix.m[10],matrix.m[14],matrix.m[3],matrix.m[7],matrix.m[11],matrix.m[15] };
    return m;
#endif
}

複製程式碼

左手系與右手系

SceneKit 和 ARKit 中採用的是右手系。

在專案中不管是對世界座標原點矩陣還是 SCNNode 的矩陣進行操作,如果反轉 x,y,z 任意一根座標軸,矩陣就會改變手性。

SCNNode如果被放在左手系下,那其中的 3D 模型很可能會顯示不正常,表面顯示黑色或破損狀。這是因為法向量也隨著座標系的變換而改變了,導致的顯示不正常。

教材中對此理論進行了說明:

在三維空間中,由3D向量V1,V2和V3組成的座標系的基B具有偏手性,
當(V1 × V2) ∙ V3 >0時,B稱為右手螺旋基。
在右手座標系中,向量V1和V2外積的方向遵循右手螺旋定律,
與向量V3的方向之間的夾角為銳角。
如果B為右手螺旋正交基,則V1 × V2=V3。
反之,當(V1 × V2) ∙ V3<0時,B稱為左手螺旋基。


奇數次反射變換將改變基B的偏手性,偶數次反射變換總是等效於旋轉變換,因此一系列任意多的反射變換可以當做一個旋轉變換加最多一個反射變換。 

通過計算一個3X3矩陣的行列式可以判斷該矩陣是否包含反射變換,
如果一個3X3矩陣M的行列式是負值,則該矩陣包含一個反射變換,則矩陣M將改變它所變換的基向量集的偏手性,
反之,如果矩陣M的行列式是正值,該矩陣將保持變換基向量集的偏手性。

正交矩陣M的行列式的值只能為1或者-1,如果 detM=1,則矩陣M表示一個純粹的旋轉變換,如果detM=-1,則矩陣M表示一個旋轉變換和一個反射變換。
複製程式碼

所以,要檢查 AR 中世界座標系的手性,可以直接設定ARSCNDebugOptionShowWorldOrigin顯示世界座標軸,直接目視判斷。也可以輸出其矩陣,計算 3x3 行列式的值,進行判斷。

3D 中的矩陣轉換 API

SCNNode 中提供了兩個矩陣轉換的 API,用來將矩陣變換從一個座標系下轉換到另一個 Node 的座標系下。

//SCNNode 中的物件方法,作用是將 self(即一個 SCNNode 物件)上的某個矩陣,轉換到另一個 Node 的座標系中。
- (SCNMatrix4)convertTransform:(SCNMatrix4)transform toNode:(nullable SCNNode *)node;

//SCNNode 中的物件方法,作用是將另一個 Node 座標系下的某個矩陣,轉換到 self(即 SCNNode 物件)的座標系中。
- (SCNMatrix4)convertTransform:(SCNMatrix4)transform fromNode:(nullable SCNNode *)node;
複製程式碼