说明
在 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 数学基础:图形与游戏开发》