S03E04:矩阵爬行校正

992 阅读3分钟

说明

在 AR 和 3D 中,我们使用的很多都是正交矩阵。判别矩阵是否是正交矩阵:

  • 1,矩阵的每一列都是单位矩阵
  • 2,矩阵的所有列互相垂直。

注意,有一个术语上的差别可能会导致轻微的混淆。线性代数中,如果一组向量互相垂直,这组向量就被认为是正交基(orthogonal basis)。它只要求所有向量互相垂直,并不要求所有向量都是单位向量。如果它们都是单位向量,则称它们为标准正交基(Orthonormal basis)。这里所讲的正交矩阵的行或列向量都是指标准正交基向量(orthogonal basis vectors),所以由一组正交基向量构造的矩阵并不一定是正交矩阵(除非基向量是标准正交的)。

几何

矩阵正交化:有时候可能会遇到略微违反正交性的矩阵,例如:外部的坏数据或者浮点数运算的累积错误(称作“矩阵爬行”)。这些情况,需要做矩阵正交化,得到一个正交矩阵。

也就是说,一个局部坐标系的 x、y、z 轴互相不垂直,或者长度不等于 1了。这时我们就需要对矩阵进行正交化处理,得到一个正交矩阵,这个矩阵要尽可能地和原矩阵相同。

构造一组正交基向量(矩阵的列)的标准算法是施密特正交化。它的基本思想是,对每一列,从中减去它平行于已处理过的列的部分,最后得到垂直向量。

以3x3矩阵为例,和以前一样,用r1、r2、r3代表3x3阶矩阵M的列。正交向量组r1'、r2'、r3'的计算如公式9.9所示:

如下图(动画来自知乎马同学的回答),蓝色不变的就是 r1,红色的是 r2,最后投影处理的是 r3 施密特正交化是有偏差的,这取决于基向量列出的顺序。一个明显的例子是,r1总不用改变。该算法的一个改进是不在一次正交化过程中将整个矩阵完全正交化。而是选择一个小的因子k,每次只减去投影的k倍,而不是一次将投影全部减去。改进还体现在,在最初的轴上也减去投影。这种方法避免了因为运算顺序不同带来的误差。算法总结如下:

该算法的每次迭代都会使这些基向量比原来的基向量更为正交化,但可能不是完全正交的,多次重复这个过程,最终将得到一组正交基。要得到完美的结果,就得选择一个适当的因子k并迭代足够多次(如:10次)。接着,进行标准化,最后就会得到一组正交基。

代码

func orthogonalization(iterationTimes:Int = 10) -> simd_float3x3 {
    var r1 = columns.0
    var r2 = columns.1
    var r3 = columns.2
    
    let k:Float = 0.3
    
    for _ in 0..<iterationTimes {
        r1 = r1 - k * (orth(u: r1, v: r2) + orth(u: r1, v: r3))
        r2 = r2 - k * (orth(u: r2, v: r1) + orth(u: r2, v: r3))
        r3 = r3 - k * (orth(u: r3, v: r1) + orth(u: r3, v: r2))
    }
    
    r2 = r2 - orth(u: r2, v: r1)
    r3 = r3 - orth(u: r3, v: r1) - orth(u: r3, v: r2)
    
    r1 = normalize(r1)
    r2 = normalize(r2)
    r3 = normalize(r3)
    
    return simd_float3x3(r1, r2, r3)
}

private func orth(u:simd_float3, v:simd_float3) -> simd_float3 {
    return dot(u, v) / dot(v, v) * v
}

参考

  • 《3D 数学基础:图形与游戏开发》