矩阵乘法

3

这两段伪代码有何不同之处?

// Multiplying a matrix by the difference between each frame
float difference = current - previous; // Time since previous frame
float angle = difference / 500;
matrix rotation;
rotation.RotateX(angle);
rotation.RotateY(angle);
worldMatrix *= rotation; // Note multiply

// Multiplying a matrix by the difference between current and start
float difference = current - start; // Time since first frame
float angle = difference / 500;
matrix rotation;
rotation.RotateX(angle);
rotation.RotateY(angle);
worldMatrix = rotation; // Note assignment

每段代码之间只有非常微小的差异,但会导致视觉上的巨大差异。输入看起来像这样:

帧1:旋转= 1弧度
worldMatrix *= rotation;
帧2:旋转= 1弧度
worldMatrix *= rotation;
等等...

帧1:旋转= 1弧度
worldMatrix = rotation;
帧2:旋转= 2弧度
worldMatrix = rotation;
等等...

2个回答

8

实际上,结果应该是不同的(即使您没有考虑上述的累积误差)。原因是旋转顺序很重要:先绕X轴旋转,再绕Y轴旋转,与先绕Y轴旋转,再绕X轴旋转是不同的。

按照矩阵符号(就我理解您的设置而言),您有以下理想行为:

let angle = (end - start)/500
  Rx = rotate.RotateX(angle)
  Ry = rotate.RotateY(angle)

then, foreach frame in (0..500):
cumulative: Rc = Rx * Ry * Rx * Ry * ... * Rx * Ry
               = (Rx * Ry)^frame
assignment: Ra = Rx * Rx * ... * Ry * Ry * ....
               = (Rx)^frame * (Ry)^frame

关于伪代码的一些说明:这里的约定是从左到右乘矩阵(这意味着点是行向量)。此外,如果不清楚的话,“(matrix)^N”是矩阵乘方:按顺序连续乘“N”个“(matrix)”。
对于累积情况,您的OQ从单位矩阵开始,依次将其乘以旋转角度小的“Rx”和“Ry”;您的“rotation”等同于我的“(Rx*Ry)”。“worldMatrix”然后多次乘以该矩阵;这意味着对于任何给定帧,“worldMatrix”=“initial_worldMatrix * (Rx*Ry)^frame”。
对于赋值情况,您要计算的角度为“frame * total_angle/total_frames”。这相当于依次旋转“total_angle/total_frames”“frame”次,这很重要,因为这些小旋转恰好与累积情况中使用的小旋转相同。因此,在赋值情况下,您的代码正在计算“(Rx)^frame * (Ry)^frame”,并每次将“worldMatrix”重置为该值。
问题在于这些是不同的矩阵;即使进行完美的数学推导,它们应该看起来也会有所不同。
选择哪个取决于您想要的行为。累积版本将紧密地近似于围绕X轴和Y轴之间的对角线进行旋转;赋值版本则像旋转万向节一样。
如果您确实想要累积行为,则有更好的方法,而不是将多达500个矩阵相乘在一起(如上所述,由于浮点误差,您的矩阵会漂移)。具体来说,您可以围绕Z轴旋转45度,然后围绕X轴旋转frame/500度,然后再围绕Z轴旋转-45度,以获得类似的效果。
进一步解释两种情况之间的区别:
在累积情况下,您正在围绕X轴旋转一点,然后围绕Y轴旋转一点,重复多次。如果旋转很小,则组合这两个小旋转的结果将是围绕某个轴的小旋转(如果它们不是很小,则轴可能不完全在两者之间,但仍将是特定旋转)。重要的是,如果重复执行该旋转对,则其结果将越来越多地绕该轴旋转,无论该轴是什么。
在赋值情况下,您正在围绕X轴进行所有旋转,然后围绕Y轴进行所有旋转。这使得旋转变大,这就有所不同。一种可视化差异的方法是想象一组坐标轴:大的X旋转将把原始Y轴旋转出线,以便Y旋转会以不同的方式应用。

在数学术语中,造成如此大的差异的原因是一般情况下旋转不是可交换的:也就是说,顺序很重要。请注意,小的旋转近似是可交换的(当旋转角度趋近于零时,Rx * RyRy * Rx之间的差异呈二次方逼近 -- 旋转角度减半会将差异减小四倍),但当您将所有小旋转组合成两个大旋转时,您要做很多重新排序才能使它们产生巨大的差异。即使每个单独的交换(Rx * Ry -> Ry * Rx)只有很小的影响,将N个Rx迁移到一侧实际上是一个冒泡排序:您需要O(N^2)个交换才能完成它....


1
累积=轴旋转&赋值=旋转万向节正是我所看到的,感谢您在原始问题中完全理解我所尝试询问的内容。您能否再详细解释一下累积/赋值伪代码?我正在尝试理解为什么数学计算如此不同。谢谢! - Mark Ingram

2
似乎两者的区别在于第一个示例会通过旋转矩阵改变当前世界矩阵。而第二个示例则用旋转矩阵来替换世界矩阵。如果在此之前未对世界矩阵进行任何操作,则可能看不到任何区别。但如果在此之前应用了任何操作,则第二个代码示例将舍弃那些操作。
问题是,您是否希望对世界矩阵进行的更改是累积的还是非累积的?第一个代码示例将给您一个累积效果,而第二个则不会。

2
通常情况下,赋值版本比累积版本更可取。如果您将500个旋转矩阵相乘,由于浮点问题,结果往往会漂移 - 结果可能看起来越来越不像旋转矩阵... - comingstorm
第一个示例旨在展示累积效应 - 但实际上它并不起作用。我得到的旋转完全不同于第二个示例。 - Mark Ingram

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接