矩阵相乘

发布于 2024-10-01 06:51:27 字数 861 浏览 2 评论 0原文

这两段伪代码有什么区别?

// 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 *= 旋转;
第 2 帧:旋转 = 1 弧度
worldMatrix *= 旋转;
等等...

第 1 帧:旋转 = 1 弧度
世界矩阵=旋转;
第 2 帧:旋转 = 2 弧度
世界矩阵=旋转;
等等...

What is the difference between the two pieces of pseudo-code?

// 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

There are only very minor differences between each piece of code, but lead to big visual differences. The input looks like this:

Frame 1: Rotation = 1 radian
worldMatrix *= rotation;
Frame 2: Rotation = 1 radian
worldMatrix *= rotation;
etc...

Frame 1: Rotation = 1 radian
worldMatrix = rotation;
Frame 2: Rotation = 2 radians
worldMatrix = rotation;
etc...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

婴鹅 2024-10-08 06:51:27

实际上,结果应该有所不同(即使您没有考虑累积误差,如上所述)。原因是旋转中的顺序很重要:先绕 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 是矩阵求幂:将 (matrix)N 个副本按顺序相乘。

对于累积情况,您的 OQ 从单位矩阵开始,将其连续乘以小旋转 RxRy;你的旋转等于我的(Rx*Ry)。然后将 worldMatrix 乘以该矩阵多次;这意味着对于任何给定的帧,worldMatrix = initial_worldMatrix * (Rx*Ry)^frame

对于分配情况,您将角度计算为frame *total_angle/total_frames。这相当于按顺序旋转 total_angle/total_framesframe 次,这一点很重要,因为这些小旋转与累积情况中使用的小旋转完全相同。因此,在分配情况下,您的代码正在计算 (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 之间的差值以二次方方式接近零 - - 将角度减半可将差异减少 4 倍),但是当您将所有小旋转合并为两个大旋转时,您需要进行大量重新排序,从而产生巨大差异。即使每个单独的交换 (Rx * Ry -> Ry * Rx) 仅产生很小的影响,将 N 个 Rx 迁移到一侧实际上是冒泡排序:您需要 O(N^2) 交换才能做到这一点......

Actually, the result should be different (even if you aren't considering cumulative error, as above). The reason is that order matters in rotations: rotating about X, then about Y, is different from rotating about Y, then about X.

In matrix notation (as far as I understand your setup), you have the following ideal behavior:

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

Some notes on the pseudocode: the convention here is that we multiply matrices from left to right (which means that points are row vectors). Also, in case it's not clear, (matrix)^N is matrix exponentiation: multiply N copies of (matrix) together in sequence.

For the cumulative case, your OQ starts with a unit matrix, multiplies it by the small rotations Rx and Ry in succession; your rotation is equal to my (Rx*Ry). It then multiplies worldMatrix by that matrix multiple times; this means that for any given frame, worldMatrix = initial_worldMatrix * (Rx*Ry)^frame.

For the assignment case, you compute the angle to be frame * total_angle/total_frames. This is equivalent to rotating total_angle/total_frames sequentially frame times, which is important because these small rotations are exactly the same as the small rotations used in the cumulative case. So, in the assignment case, your code is computing (Rx)^frame * (Ry)^frame, and resetting worldMatrix to that value each time.


The point is that these are different matrices; even with perfect math, they ought to look different.

Which one you should choose depends on what behavior you want. The cumulative version will closely approximate rotation about an axis diagonally between the X and Y axes; the assignment version acts like rotating gimbals instead.

If you do want the cumulative behavior, there are better ways than multiplying up to 500 matrices together (as mentioned above, your matrices will drift due to floating point error). Specifically, you can rotate 45 degrees about the Z axis, then rotate frame/500 about the X axis, then rotate -45 degrees about the Z axis, to get a similar effect.


To elaborate on the difference between the two cases:

In the cumulative case, you are rotating a little bit about X, then a little bit about Y, repeat many times. If the rotations are small, the result of combining the two small rotations will be a small rotation about some axis (if they aren't all that small, the axis might not be exactly between the two, but it will still be a specific rotation). The point is, if you repeat that pair of rotations, the result will be more and more rotation on that axis, whatever it is.

In the assignment case, you're doing all your rotation about X, then all your rotation about Y. This makes the rotations large, and that makes a difference. One way to visualize the difference is to imagine a set of coordinate axes: the large X rotation will rotate the original Y axis out of line, so that the Y rotation is applied differently.

In mathematical terms, the reason why there's such a big difference is that, in general, rotations are not commutative: in other words, order matters. Note that small rotations are approximately commutative (as the rotation angle approaches zero, the difference between Rx * Ry and Ry * Rx approaches zero quadratically -- cutting the angle in half reduces the difference by a factor of 4), but when you combine all the mini-rotations into two large ones, you're doing so much re-ordering that it makes a huge difference. Even if each individual swap (Rx * Ry -> Ry * Rx) has only a small effect, migrating N Rx's to the one side is effectively a bubble sort: you'll need O(N^2) swaps to do it....

遥远的绿洲 2024-10-08 06:51:27

区别似乎在于第一个样本将通过旋转矩阵改变当前的世界矩阵。第二个示例用旋转矩阵替换世界矩阵。如果在此之前没有对世界矩阵应用任何其他操作,您可能不会看到任何差异,但如果在此之前应用了任何操作,第二个代码示例将丢弃这些操作。

问题是,您是否希望对世界矩阵的更改是累积的?第一个代码示例会给您带来累积效应,第二个代码示例不会。

The difference seems to be that the first sample will alters the current world matrix by the rotation matrix. The second sample replaces the world matrix by the rotation matrix. You might not see any difference if no other operations have been applied to the world matrix before this one, but if any operations were applied before this, the second code sample would discard those.

The question is, do you want your changes to the world-matrix to be cumulative or not? The first code sample will give you a cumulative effect, the second will not.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文