我在两个轴上旋转一个对象,那么为什么它一直绕着第三个轴扭曲?


38

我看到很多问题经常出现,但都涉及到潜在的问题,但是它们都被给定功能或工具的细节所困扰。这是尝试创建一个规范的答案,我们可以在出现时向用户推荐-提供大量动画示例!:)


假设我们正在制作第一人称相机。基本思想是左右摇摆应偏航,上下左右俯仰。因此,我们编写了如下代码(以Unity为例):

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    // Yaw around the y axis using the player's horizontal input.        
    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f);

    // Pitch around the x axis using the player's vertical input.
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f);
}

或许

// Construct a quaternion or a matrix representing incremental camera rotation.
Quaternion rotation = Quaternion.Euler(
                        -Input.GetAxis("Vertical") * speed,
                         Input.GetAxis("Horizontal") * speed,
                         0);

// Fold this change into the camera's current rotation.
transform.rotation *= rotation;

它通常可以正常工作,但是随着时间的流逝,视图开始变得歪斜。即使我们只是告诉相机它绕x和y旋转,相机似乎仍在绕其旋转轴(z)旋转!

第一人称摄影机向侧面倾斜的动画示例

如果我们要操作镜头前的物体,也可能发生这种情况-说这是我们要转过头看看的地球仪:

地球向侧面倾斜的动画示例

相同的问题-不久之后,北极开始向左或向右漂移。我们在两个轴上提供输入,但在第三轴上却得到这种令人困惑的旋转。无论我们是否围绕对象的局部轴或世界的全局轴应用所有旋转,都会发生这种情况。

在许多引擎中,您还会在检查器中看到这一点-旋转世界中的对象,突然数字在我们什至没有碰到的轴上发生变化!

动画示例显示了在旋转角度读数旁边操作对象的情况。 即使用户未操纵该轴,z角度也会改变。

那么,这是引擎错误吗?我们如何告诉程序我们不希望它增加额外的轮换?

它与欧拉角有关吗?我应该使用四元数,旋转矩阵还是基向量?



1
我发现以下问题的答案也很有帮助。gamedev.stackexchange.com/questions/123535/...
特拉维斯Pettry

Answers:


51

不,这不是引擎错误或特定旋转表示的人工产物(也可能发生,但是这种效果适用于表示旋转(包括四元数)的每个系统)。

您已经发现有关旋转在三维空间中如何工作的真实事实,它与我们对其他转换(如翻译)的直觉不同:

动画示例显示以不同顺序应用旋转会产生不同的结果

当我们在一个以上的轴上组合旋转时,得到的结果不只是应用于每个轴的总/净值(我们可能期望平移)。我们应用旋转的顺序会更改结果,因为每次旋转都会移动要应用下一个旋转的轴(如果绕对象的局部轴旋转),或者对象与轴之间的关系(如果绕着世界的轴旋转)。轴)。

轴关系随时间的变化会混淆我们对每个轴“应该”做什么的直觉。特别是偏航旋转和俯仰旋转的某些组合所产生的结果与侧倾旋转相同!

动画示例显示了一系列局部俯仰-偏航-俯仰角,其输出与单个局部侧倾相同

您可以验证每个步骤是否围绕我们要求的轴正确旋转-符号中没有引擎故障或伪影干扰或猜测我们的输入-球形(或超球形/四元数)旋转性质仅意味着我们的变换“自动换行周围”。对于较小的旋转,它们可能在局部正交,但随着它们堆积,我们发现它们不是全局正交的。

对于上述90度转弯来说,这是最生动和清晰的,但如问题所示,漂移轴也会在许多小的旋转中蠕动。

那么,我们该怎么办?

如果您已经有了俯仰-偏航旋转系统,消除不想要的侧倾的最快方法之一就是更改其中一个旋转,以对全局或父变换轴而不是对象的局部轴进行操作。这样一来,您就不会在两者之间产生交叉污染-一个轴仍然受到绝对控制。

这与上面示例中的俯仰-偏航-俯仰变距相同,但是现在我们绕全局Y轴而不是对象的

杯子以局部俯仰和整体偏航旋转的动画示例,没有滚动问题使用全局偏航的第一人称相机的动画示例

因此,我们可以使用“本地变音,全球偏航”的口号来修复第一人称摄影机:

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f, Space.World);
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f, Space.Self);
}

如果要使用乘法复合旋转,则可以翻转其中一个乘法的左/右顺序以获得相同的效果:

// Yaw happens "over" the current rotation, in global coordinates.
Quaternion yaw = Quaternion.Euler(0f, Input.GetAxis("Horizontal") * speed, 0f);
transform.rotation =  yaw * transform.rotation; // yaw on the left.

// Pitch happens "under" the current rotation, in local coordinates.
Quaternion pitch = Quaternion.Euler(-Input.GetAxis("Vertical") * speed, 0f, 0f);
transform.rotation = transform.rotation * pitch; // pitch on the right.

(具体顺序将取决于您环境中的乘法约定,但是左=全局=右=局部=更多)

这等效于将所需的净总偏航和总螺距存储为浮点变量,然后始终一次应用净结果,仅从这些角度构造一个新的新方向四元数或矩阵(前提是您要保持totalPitch钳制):

// Construct a new orientation quaternion or matrix from Euler/Tait-Bryan angles.
var newRotation = Quaternion.Euler(totalPitch, totalYaw, 0f);
// Apply it to our object.
transform.rotation = newRotation;

或同等...

// Form a view vector using total pitch & yaw as spherical coordinates.
Vector3 forward = new Vector3(
                    Mathf.cos(totalPitch) * Mathf.sin(totalYaw),
                    Mathf.sin(totalPitch),
                    Mathf.cos(totalPitch) * Mathf.cos(totalYaw));

// Construct an orientation or view matrix pointing in that direction.
var newRotation = Quaternion.LookRotation(forward, new Vector3(0, 1, 0));
// Apply it to our object.
transform.rotation = newRotation;

使用这种全局/局部拆分,旋转没有机会相互影响并相互影响,因为它们已应用于独立的轴集。

如果它是我们要旋转的世界中的物体,则相同的想法可能会有所帮助。对于像地球这样的示例,我们经常想将其反转并在本地应用偏航(因此,它总是绕其两极旋转)并在全球范围内倾斜(因此,它向着/远离我们的视线,而不是朝着/远离澳大利亚的方向倾斜) ,无论指向何处...)

球形动画的表现出更好的自转

局限性

这种全球/本地混合策略并不总是正确的解决方案。例如,在具有3D飞行/游泳功能的游戏中,您可能希望能够笔直向上/笔直向下并仍具有完全控制权。但是通过这种设置,您会碰到万向节锁定 -偏航轴(全局向上)变得平行于横滚轴(局部向前),并且您无法向左或向右看而不会扭曲。

在这种情况下,您可以做的是使用纯局部旋转,就像我们在上面的问题中开始的那样(这样,无论您在哪里看,您的控件都感觉相同),这最初会使一些滚动发生-但随后我们对此进行纠正。

例如,我们可以使用局部旋转来更新“前向”矢量,然后将该前向矢量与参考“上”矢量一起使用来构造最终方向。(例如,使用Unity的Quaternion.LookRotation方法,或从这些向量手动构建正交矩阵)。通过控制向上向量,我们可以控制滚动或扭曲。

对于飞行/游泳示例,您将希望随着时间的推移逐步应用这些更正。如果太突然,视图可能会分散注意力。取而代之的是,您可以使用播放器的当前向上矢量,并将其朝垂直方向逐帧提示,直到其视线变平为止。与在播放器控件闲置时扭曲相机相比,在转弯中应用此方法有时会更令人讨厌。


1
请问你是如何制作gif的?看起来不错
巴林特

1
@Bálint我使用一个名为LICEcap的免费程序-它可以让您将屏幕的一部分记录为gif。然后,我修剪动画/添加其他标题文本/使用Photoshop压缩gif。
DMGregory

这个答案仅适用于Unity吗?Web上是否有更通用的答案?
posfan12

2
@ posfan12示例代码使用Unity语法来具体说明,但是所涉及的情况和数学在各个方面均适用。您在将示例应用于您的环境时遇到困难吗?
DMGregory

2
数学已经在上面了。从您的编辑来看,您只是将错误的部分混在一起。看来您正在使用此处定义向量类型。如上所述,这包括一种从一组角度构造旋转矩阵的方法。从一个单位矩阵开始并进行调用TCVector::calcRotationMatrix(totalPitch, totalYaw, identity)-这等效于上面的“一次全部Euler”示例。
DMGregory

1

育儿和轮换功能16小时后,大声笑。

我只是想让代码基本上是空的,转圈,并且在转弯时向前翻转。

换句话说,目标是旋转偏航和俯仰而对侧倾没有任何影响。

这是在我的应用程序中实际起作用的几个

在Unity中:

  1. 创建一个空的。称之为ForYaw。
  2. 给那个孩子一个空的,叫做ForPitch。
  3. 最后,制作一个多维数据集并将其移动到z = 5(向前)。现在,我们可以看到要避免的操作,这将扭曲多维数据集(在偏航和俯仰时偶然发生“滚动”)

现在在Visual Studio中编写脚本,进行设置,并在Update中完成此操作:

objectForYaw.Rotate(objectForYaw.up, 1f, Space.World);
    objectForPitch.Rotate(objectForPitch.right, 3f, Space.World);

    //this will work on the pitch object as well
    //objectForPitch.RotateAround
    //    (objectForPitch.position, objectForPitch.right, 3f);

请注意,当我尝试更改育儿顺序时,一切都对我不利。或者,如果我为子音高对象更改Space.World (父偏航轴不介意通过Space.Self旋转)。令人困惑。我绝对可以澄清一下为什么我必须取局部坐标轴,然后将其应用于世界空间-为什么Space.Self会破坏一切?


对于我来说,尚不清楚这是否旨在回答问题,或者您是否要寻求帮助来理解编写的代码为何如此工作。如果是后者,则应发布一个新问题,并根据需要链接到此页面以获取上下文。提示me.Rotate(me.right, angle, Space.World)与相同me.Rotate(Vector3.right, angle, Space.Self,并且您嵌套的转换等效于已接受答案中的“局部变距,全局变距”示例。
DMGregory

两者都有。部分分享答案(例如,这正是它对我的工作方式)。并介绍问题。这个提示使我思考。非常感谢!

令人惊讶的是,这一点并未得到认可。在经过数小时的阅读后,尽管仍然无法解决问题,但仍然获得了全面的答案,但只需使用对象旋转轴而不是通用的Vector3.right轴,即可将旋转固定在对象上。我一直在寻找的难以置信的东西被以0票的价格埋在了这里,并在Google的第二页上出现了许多类似的问题。
user4779
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.