如何使用累积矩阵变换解决万向节锁定问题


12

我正在阅读Jason L. McKesson的在线“学习现代3D图形编程”书

到目前为止,我要解决万向节锁定问题以及如何使用四元数来解决它。

然而,就在这里,在四元数页面上

问题的一部分是,我们试图将方向存储为一系列3个累积的轴向旋转。方向是方向,而不是旋转。方向当然不是一系列旋转。因此,我们需要将船舶的方向视为方向,作为特定数量。

我想这是我开始感到困惑的第一个地方,原因是因为我看不到方向和旋转之间的巨大差异。我也不明白为什么方向不能通过一系列旋转来表示...

也:

为此,首先想到的是将方向保持为矩阵。当需要修改方向时,我们只需对此矩阵进行转换,将结果存储为新的当前方向。

这意味着应用于当前方向的每个偏航,俯仰和横滚都将相对于该当前方向。正是我们所需要的。如果用户应用正偏航,则希望该偏航相对于它们当前指向的位置而不是相对于某个固定坐标系旋转它们。

我理解这个概念,但是我不理解如果累积矩阵变换可以解决此问题,那么上一页中给出的代码不仅如此。

这是代码:

void display()
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glutil::MatrixStack currMatrix;
    currMatrix.Translate(glm::vec3(0.0f, 0.0f, -200.0f));
    currMatrix.RotateX(g_angles.fAngleX);
    DrawGimbal(currMatrix, GIMBAL_X_AXIS, glm::vec4(0.4f, 0.4f, 1.0f, 1.0f));
    currMatrix.RotateY(g_angles.fAngleY);
    DrawGimbal(currMatrix, GIMBAL_Y_AXIS, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
    currMatrix.RotateZ(g_angles.fAngleZ);
    DrawGimbal(currMatrix, GIMBAL_Z_AXIS, glm::vec4(1.0f, 0.3f, 0.3f, 1.0f));

    glUseProgram(theProgram);
    currMatrix.Scale(3.0, 3.0, 3.0);
    currMatrix.RotateX(-90);
    //Set the base color for this object.
    glUniform4f(baseColorUnif, 1.0, 1.0, 1.0, 1.0);
    glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(currMatrix.Top()));

    g_pObject->Render("tint");

    glUseProgram(0);

    glutSwapBuffers();
}

据我了解,他所做的(修改堆栈上的矩阵)不是考虑累积矩阵,因为作者将所有单独的旋转变换组合为一个矩阵,该矩阵存储在堆栈的顶部。

我对矩阵的理解是,它们用于获取相对于原点(比如说……模型)的点,并使它相对于另一原点(相机)。我很确定这是一个安全的定义,但是我觉得缺少一些东西使我无法理解此万向节锁定问题。

对我来说没有意义的一件事是:如果矩阵确定了两个“空间”之间的相对差,那么绕着Y轴旋转(比如说滚动)的结果如何不会将点放在“滚动空间”中“然后可以相对于该滚动再次进行转换。换句话说,不应对此点进行任何进一步的转换以与此新的“滚动空间”相关,因此旋转不应相对于先前的“模型空间”,这会导致云台锁定。

这就是为什么发生万向节锁定的原因?这是因为我们绕着X,Y和Z轴旋转对象,而不是绕着自己的相对轴旋转对象。还是我错了?

既然我链接的这段代码显然不是矩阵变换的累积,您可以举一个使用此方法的解决方案示例。

因此,总而言之:

  • 旋转和方向之间有什么区别?
  • 为什么链接的代码不是矩阵转换累积的示例?
  • 如果矩阵错误,矩阵的真正特定目的是什么?
  • 如何使用矩阵变换的累积来实现万向节锁定问题的解决方案?
  • 另外,作为奖励:为什么旋转后的变换仍然相对于“模型空间”?
  • 另一个好处是:我认为在转换之后,相对于当前的转换将发生进一步的转换,我是否错了?

另外,如果没有暗示,则我使用的是OpenGL,GLSL,C ++和GLM,因此,如果没有必要,则非常感谢您提供有关这些示例和说明的信息。

细节越多越好!

提前致谢。

Answers:


11

我不确定是否有一个好的方法可以对此进行介绍,除了我希望它最终能很好地结合在一起。也就是说,让我们深入:

旋转和方向不同,因为前者描述了变换,而后者描述了状态。旋转是对象进入方向的方式,方向是对象在本地旋转的空间。这可以与数学上如何表示两者直接相关:矩阵存储从一个坐标空间到另一个坐标空间的转换(您确实有正确的表示),四元数直接描述方向。因此,矩阵只能描述对象如何通过一系列旋转进入定向。但是,与此有关的问题是万向节锁。

万向节锁演示了使用一系列旋转使对象进入定向的困难。当至少两个旋转轴对齐时,会发生此问题:

图片由deepmesh3d.com提供
在左上方的图像中,蓝色和橙色轴旋转相同!这是一个问题,因为这意味着三个自由度之一已丢失,并且从此点开始的额外旋转可能会产生意外结果。使用四元数解决了这一问题,因为应用四元数来变换对象的方向将直接将对象置于新的方向(这是我可以说的最好的方式),而不是将变换分解为滚动,俯仰和偏航操作。

现在,我实际上对累积矩阵是否是此问题的完整解决方案持怀疑态度,因为累积矩阵(因此累积旋转)恰恰是可能首先导致云台锁定问题的原因。处理四元数变换的正确方法是在一个点上执行四元数乘法:

pTransformed = q * pAsQuaternion * qConjugate

或将四元数转换为矩阵并使用该矩阵转换点。

平面矩阵旋转(例如45度偏航)将始终在全局空间中定义。如果要在局部空间中应用转换,则必须将转换转换为该对象的局部空间。听起来很奇怪,所以我会详细说明。这就是轮换顺序的重要性所在。我建议在这里抓一本书,以便您跟随转换。

首先将书本放平,其封面朝上,朝上,就像要打开书本并开始阅读一样。现在,将书籍的正面向上倾斜45度(正面应该大致面向您):

glutil::MatrixStack bookMatrix;
bookMatrix.RotateX(45);

现在,假设您想将书籍的偏航角调整为45度(我想我是假设是右手坐标系,因此这将朝左改变标题),并且您希望将此方法应用于书籍的本地协调空间,使书的封面仍然面对您:

bookMatrix.RotateY(45);

问题是,这种旋转发生在全局坐标空间中,因此书的封面将面朝您的右肩。为了使航向改变在局部坐标空间中发生,您应该首先应用它!

glutil::MatrixStack bookMatrix;
bookMatrix.RotateY(45);
bookMatrix.RotateX(45);

试试看!重新将书面朝上放在天花板上。更改其偏航45度,然后沿全局X轴将其倾斜45度(从左到右运行)。您在书本的本地空间中所期望的方向为45度和45度偏航。

这是什么意思?真正归结为操作顺序很重要。在随后进行的转换的背景下,首先完成的转换将成为局部转换。缠住头变得很困难,这就是四元数节省很多麻烦的方式。跳过所有与订单有关的东西。

四元数提供的另一个巨大优势是它们允许方向的内插。由于顺序依赖性,试图在欧拉角之间进行插值几乎是不可能的。四元数的数学性质允许在它们之间进行明确定义的球形线性插值。

总结一下并解决您的原始问题:累积矩阵变换确实不能解决Gimbal锁定问题,除非精心选择并精确地应用了变换。因此,请始终使用四元数,并使用四元数乘法将四元数应用于点。

希望这可以帮助 :)


4
仅作记录,如果通过欧拉角描述,四元数仍然可以引入万向节锁定;因为您将以不同的方式(四元数而不是矩阵)进行相同的计算
concept3d 2013年

1
@ concept3d-恭喜提及!重要的是要了解使万向架机构容易失去自由度的原因:这就像机器人关节固有地描述了超定方程组。如果您使用四元数,矩阵或魔术来构建这种机制,那么您仍然最终会感到模棱两可-它理解它,而不是首先使用它才是真正的解决方案(除非您需要出于某种说明性或技术性目的使用它) 。
teodron

四元数很难想象,我一直认为的方式是它们(单位四元数)代表一个3球面空间,因此可以代表任何方向,而我理解欧拉角每个都代表圆/ turos,因此不是一个完整的球面这不是一种非常准确的表示方向的方法(3个圆/环不能真正生成所有可能的方向,除非它们独立旋转,这在欧拉角的情况下是不可能的),不确定我是否能正确解释:)
concept3d 2013年

1

矩阵累积实际上可以解决云台锁定问题。通过累积旋转,可以添加万向节,允许任意旋转。ktodisco提供的图在左图中显示了万向架锁。此方向的矩阵可以定义为:

glutil::MatrixStack bookMatrix;
bookMatrix.RotateX(90);
bookMatrix.RotateY(90);
bookMatrix.RotateZ(90);

由于y万向节旋转,X和Z万向节现在被锁定,因此我们失去了一个度数的移动。在这一点上,我们没有使用这三个云台的偏航(局部y,全局z)。但是通过添加另一个云台,我可以围绕y局部旋转:

glutil::MatrixStack bookMatrix;
bookMatrix.RotateX(90);
bookMatrix.RotateY(90);
bookMatrix.RotateZ(90);
bookMatrix.RotateY(90);

对于每个新的滚动,俯仰和偏航,只需添加另一个万向节,并将它们累积到一个矩阵中即可。因此,每次需要另一个局部旋转时,都会创建一个旋转并将其乘以累加矩阵。如本章所述,仍然存在问题,但万向节锁定不是其中之一。

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.