ARKit 中矩阵的简单再理解

矩阵的数据类型

通常在苹果的 ARKit 和 SceneKit 中,用到的矩阵有三种SCNMatrix4simd_float4x4GLKMatrix4node

GLKMatrix4是从 OpenGL 框架 GLKit 中带过来的,各类函数很全面。
SCNMatrix4最初是给 SceneKit 使用的,后来扩展到 ARKit 中,矩阵操做函数不够全面,好比没有转置函数 transpose。
simd_float4x4看名字就知道是 simd 类型的,是后来为了取代SCNMatrix4来推出的,各类函数也很全面。数组

上面三种类型本质都是结构体,互相转换的方法也在苹果文档中给出了,分别为:bash

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 是列主序的。ui

转置transpose

若是咱们须要对矩阵进行变换,从行主序变为列主序,或从列主序变成行主序,那就须要转置 transpose:spa

SCNMatrix4 scnMat;
simd_transpose(SCNMatrix4ToMat4(scnMat));
GLKMatrix4Transpose(SCNMatrix4ToGLKMatrix4(scnMat));
复制代码

通常何时须要转置呢?
主要看用途:通常线性代数教材中的公式,都是行主序的,因此常常会出如今工具方法中,为了更好的抄公式采用了行主序,最后再转置为列主序矩阵。code

还有一种常见的是:利用正交矩阵的特性,用矩阵转置来代替矩阵求逆运算。好比,矩阵 A 表明将一个小球从 a 点变换(只有平移和旋转)到 b 点,矩阵 B 则相反,表明将一个小球从 b 点变换(只有平移和旋转)到 a 点。因为 A 很差求解,那只须要求出 B 矩阵,转置后就获得了 A 矩阵。须要注意的是,在求解 B 的过程当中,若是进行平移,应操做.m14 = tx, .m24 = ty, .m34 = tz,即当作行主序矩阵来操做,最后转置后才是正确的结果。orm

附苹果文档中的转置方法代码:

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;
复制代码
相关文章
相关标签/搜索