如何围绕世界对齐的轴旋转对象?


15

我有一个Vector3,每个轴都有一个欧拉角。

通常,当我想创建旋转矩阵时,我将使用D3DXMatrixRotationX之类的函数从上方的旋转矢量传递相应的角度,然后将矩阵(ZXY)相乘以创建整体旋转矩阵,以用于形成完整的对象变换矩阵。

但是,此方法将在对象空间中产生一组旋转。也就是说,将向量(90,0,90)传递到我的方法中将在世界空间中有效地产生(90,90,0)的旋转。

有没有一种方法可以始终确保我的旋转矢量的每个分量导致围绕各自的世界空间对齐轴的旋转?

编辑:

这是当前情况的动画-我想要一种绕蓝色轴而不是红色轴旋转的方法。

欧拉角

编辑2:

只是要注意,我并不是在寻找涉及欧拉角的解决方案,而只是寻找一种可以表示围绕世界轴的多次旋转的变换的方法。


仅三次调用differnet函数并过滤掉不需要的矢量部分(在调用函数之前将它们设置为0)有什么问题?否则,我不确定您要达到的目标。
TravisG

过滤掉什么?我确实调用了3个单独的函数,然后将它们相乘以创建转换矩阵。但是,这会存档局部旋转。
Syntac_

您要欧拉角还是绕世界轴旋转?请注意,根据欧拉角的定义(例如en.wikipedia.org/wiki/Euler_angles),只有Alpha角严格围绕世界轴。另外两个角度是相对于倾斜轴的,不一定与世界坐标轴重合。
DMGregory

1
使用欧拉角,您要先将所有三个旋转矩阵相乘,然后再应用到顶点上。如果M,N,O是旋转矩阵,则结果运算为MNO v。我建议分别应用每个矩阵:v1 = O v0,然后v2 = N v1,最后v3 = M v2。这样,每个vi都将位于世界坐标中,并且您只需要为世界坐标中的当前轴使用旋转矩阵。
dsilva.vinicius

3
@ dsilva.vinicius您分离的变换与组合的变换完全相同,或者换句话说:MNO v == M *(N *(O v))
GuyRT 2013年

Answers:


1

根据您的评论,您似乎将对象的方向存储为一组欧拉角,并在玩家旋转对象时增加/减少了角度。也就是说,您有类似以下的伪代码:

// in player input handling:
if (axis == AXIS_X) object.angleX += dir;
else if (axis == AXIS_Y) object.angleY += dir;
else if (axis == AXIS_Z) object.angleZ += dir;

// in physics update and/or draw code:
matrix = eulerAnglesToMatrix(object.angleX, object.angleY, object.angleZ);

正如查尔斯·比蒂(Charles Beattie)所指出的那样,因为旋转不会进行换向,所以除非玩家按照应用旋转的相同顺序旋转对象,否则旋转将无法按预期进行eulerAnglesToMatrix()

特别是,请考虑以下旋转顺序:

  1. 围绕X轴将对象旋转x度;
  2. 围绕Y轴将对象旋转y度;
  3. 围绕X轴将对象旋转− x度;
  4. 将对象绕Y轴旋转-y度。

在上面的伪代码中实现的朴素欧拉角表示中,这些旋转将抵消,并且对象将返回其原始方向。在现实世界中,这不会发生–如果您不相信我,抓住一个六边形的模具或魔方,让x = y = 90°,然后自己尝试!

如您在自己的答案中所述,解决方案是将对象的方向存储为旋转矩阵(或四元数),并根据用户输入更新该矩阵。也就是说,您可以执行以下操作来代替上面的伪代码:

// in player input handling:
if (axis == AXIS_X) object.orientation *= eulerAnglesToMatrix(dir, 0, 0);
else if (axis == AXIS_Y) object.orientation *= eulerAnglesToMatrix(0, dir, 0);
else if (axis == AXIS_Z) object.orientation *= eulerAnglesToMatrix(0, 0, dir);

// in physics update and/or draw code:
matrix = object.orientation;  // already in matrix form!

(在技术上,因为任何旋转矩阵或四元数可以被表示为一组欧拉角,它能够使用它们来存储该对象的方向。但是,用于组合两个相继的旋转的物理上正确的规则,作为欧拉角的每个表示旋转成单个旋转相当复杂,从本质上讲等于将旋转转换为矩阵/四元数,将它们相乘,然后将结果转换回欧拉角。)


是的,您是对的,这就是解决方案。我觉得这比concept3d的回答略好,因为他给人的印象是需要四元数,但这不是事实。只要我将当前旋转存储为矩阵而不是三个欧拉角,就可以了。
Syntac_

15

旋转的问题在于,大多数人都以欧拉角来考虑,因为它们很容易理解。

但是大多数人忘记了欧拉角是三个连续角的观点。这意味着绕第一个轴旋转将使下一个旋转相对于第一个原始旋转,因此您不能使用欧拉角独立地围绕三个轴分别旋转矢量。

当您将两个矩阵相乘时,这将直接转换为矩阵,您可以将这种乘法视为将一个矩阵转换为另一个矩阵的空间。

即使使用四元数,这也意味着可以连续进行3次旋转。

在此处输入图片说明

我想强调一个事实,即四元数不是万向节锁的解决方案。实际上,如果使用四元数表示欧拉角,则始终会发生万向节锁定。问题不在于所述表示,问题是 3个相继步骤。

解决方案?

使向量绕3轴独立旋转的解决方案是将其合并为一个轴和一个角度,这样您就可以摆脱必须执行顺序乘法的步骤。这将有效地转换为:

我的旋转矩阵代表围绕X,Y和Z旋转的结果。

而不是欧拉的解释

我的旋转矩阵代表围绕X然后Y然后Z的旋转。

为了澄清这一点,我将引用维基百科的欧拉旋转定理:

根据欧拉旋转定理,刚体或坐标系围绕固定点的任何旋转或旋转序列等效于绕固定点的固定轴(称为欧拉轴)旋转给定角度θ的单个旋转。欧拉轴通常由单位矢量u→表示。因此,三维上的任何旋转都可以表示为向量u→和标量θ的组合。四元数提供了一种简单的方法来将该轴角表示形式编码为四个数字,并将相应的旋转应用于表示相对于R3中的原点的位置矢量。

请注意,将3个矩阵相乘将始终表示3个连续旋转。

现在,为了组合围绕3轴的旋转,您需要获得一个单轴和单个角度来表示围绕X,Y,Z的旋转。换句话说,您需要使用“轴/角度”或四元数表示法来消除顺序旋转。

通常,这是通过从初始方位(方位可以看作是轴角度)开始的(通常表示为四元数或轴角度),然后修改该方位以表示您的目标方位来完成的。例如,您从身份四元数开始,然后按差异旋转以达到目标方向。这样,您就不会失去任何自由度。


标记为答案,因为它似乎很有洞察力。
Syntac_

我在弄清楚您要用这个答案说些什么时遇到了麻烦。是否只是“不将对象的方向存储为欧拉角”?如果是这样,为什么不这样说呢?
Ilmari Karonen 2015年

@IlmariKaronen可以更清楚地说,但我认为concept3d促进了轴角表示;有关轴角和四元数之间的关系,请参见本文档的 1.2.2节。由于上述原因,轴角表示更易于实现,它不受万向节锁定的困扰,并且(至少对我而言)它与欧拉角一样容易理解。
NauticalMile

@ concept3d,这非常有趣,我真的很喜欢您的回答。但是,我缺少一件事,人们使用键盘和鼠标与计算机进行交互,如果我们想到了鼠标,那么我们在谈论的是x和y鼠标增量。如何用一个四元数来表示这些x,y增量,我们可以用它生成旋转矩阵,例如改变对象方向?
gmagno

@gmagno,方法通常是将鼠标移动投影到对象或场景上并计算该空间中的增量,方法是投射光线并计算相交。搜索射线投射,投影和非投影,由于我多年来不在CG工作,所以我对细节很粗糙。希望能有所帮助。
concept3d

2

将旋转的组合从对象空间切换到世界空间是微不足道的:您只需要颠倒应用旋转的顺序即可。

在您的情况下,Z × X × Y您无需计算矩阵,而只需计算Y × X × Z

可以在Wikipedia上找到其基本原理:内在和外在轮换之间的转换


如果这是真的,那么来自您的来源的以下陈述将是不正确的,因为旋转会有所不同:“任何外部旋转都等同于固有旋转相同角度但元素旋转的顺序相反,反之亦然。”
Syntac_

1
我在这里没有矛盾。我的回答和陈述都是正确的。是的,在对象空间和世界空间中执行旋转会产生不同的旋转。这就是重点,不是吗?
sam hocevar 2013年

该声明说,更改顺序将始终导致相同的轮换。如果一个订单产生了错误的轮换,则另一个订单也会产生错误,这意味着这不是解决方案。
Syntac_

1
您看错了。更改顺序不会导致相同的轮换。更改顺序从固有旋转切换为外部旋转会导致相同的旋转。
sam hocevar 2013年

1
我想我不明白你的问题。您的GIF显示围绕Z(对象空间)旋转约50度,然后围绕(对象空间)旋转50度,然后围绕X(对象空间)旋转45度Y。这是完全一样的大约45度的旋转Y世界空间),然后50度左右X世界空间),然后50度左右Z世界空间)。
sam hocevar 2013年

1

我将提供我的解决方案作为答案,直到有人可以解释它为什么起作用为止。

每次渲染时,我都使用存储在旋转矢量中的角度重建四元数,然后将四元数应用于最终变换。

但是,为了使其绕世界坐标轴,我不得不在所有框架上都保留四元数,并且仅使用角度差来旋转对象,即。

// To rotate an angle around X - note this is an additional rotation.
// If currently rotated 90, apply this function with angle of 90, total rotation = 180.
D3DXQUATERNION q;
D3DXQuaternionRotation(&q, D3DXVECTOR3(1.0f, 0.0f, 0.0f), fAngle);
m_qRotation *= q; 

//...

// When rendering rebuild world matrix
D3DXMATRIX mTemp;
D3DXMatrixIdentity(&m_mWorld);

// Scale
D3DXMatrixScaling(&mTemp, m_vScale.x, m_vScale.y, m_vScale.z);
m_mWorld *= mTemp;

// Rotate
D3DXMatrixRotationQuaternion(&mTemp, m_qRotation);
m_mWorld *= mTemp;

// Translation
D3DXMatrixTranslation(&mTemp, m_vPosition.x, m_vPosition.y, m_vPosition.z);
m_mWorld *= mTemp;

(详细说明)

我认为dsilva.vinicius试图达到这一点。


1

您将需要存储旋转顺序。

Rotating around x 90 then rotate around z 90 !=
Rotating around z 90 then rotate around x 90.

存储当前的旋转矩阵,并在每次旋转时预乘它们。


0

除了@ concept3d答案,您还可以使用3个外部旋转矩阵围绕世界坐标中的轴旋转。引用维基百科

外在旋转是围绕固定坐标系xyz轴发生的基本旋转。XYZ系统旋转,而xyz固定。从XYZ重叠xyz开始,可以使用三个外部旋转的组合来达到XYZ的任何目标方向。欧拉角或泰特布赖恩角(α,β,γ)是这些元素旋转的幅度。例如,可以如下达到目标方向:

XYZ系统绕z轴旋转α。X轴现在相对于X轴成角度α。

XYZ系统再次绕x轴旋转β。现在,Z轴相对于z轴成角度β。

XYZ系统绕z轴第三次旋转γ。

旋转矩阵可用于表示外部旋转的序列。例如,

R = Z(γ)Y(β)X(α)

表示围绕xyz轴的外部旋转的组合(如果用于预乘列向量),而

R = X(α)Y(β)Z(γ)

当用于后乘行向量时,表示完全相同的组成。

因此,您需要的是相对于使用固有(或局部空间)旋转所做的旋转来反转旋转顺序。@Syntac要求zxy旋转,因此我们应该进行yxz外在旋转以达到相同的结果。代码如下:

矩阵值说明在这里

// Init things.
D3DXMATRIX *rotationMatrixX = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixY = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixZ = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix0 = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix1 = new D3DXMATRIX();

D3DXMatrixRotationX(rotationMatrixX, angleX);
D3DXMatrixRotationY(rotationMatrixY, angleY);
D3DXMatrixRotationZ(rotationMatrixZ, angleZ);

// yx extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix0, rotationMatrixY, rotationMatrixX);
// yxz extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix1, resultRotationMatrix0, rotationMatrixZ);

D3DXVECTOR4* originalVector = // Original value to be transformed;
D3DXVECTOR4* transformedVector = new D3DXVECTOR4();

// Applying matrix to the vector.
D3DXVec4Transform(transformedVector, originalVector, resultRotationMatrix1);

// Don't forget to clean memory!

这段代码是有说服力的,不是最佳的,因为您可以重用多个D3DXMATRIX矩阵。


1
对不起,这是不正确的。矩阵/向量乘法是关联的。这与组合矩阵乘法完全相同。
concept3d

你是对的。我混合了外部旋转和内部旋转的概念。
dsilva.vinicius

我会解决这个问题。
dsilva.vinicius

答案现已确定。
dsilva.vinicius 2013年
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.