C++ 使用四元数进行 OpenGL 旋转
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9715776/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Quaternions for OpenGL Rotations
提问by GarrickW
So I'm writing a program where objects move around spacesim-style, in order to learn how to move things smoothly through 3D space. After messing around with Euler angles a bit, it seems they aren't really appropriate for free-form 3D movement in arbitrary directions, so I decided to move on to what seems to be best for the job - quaternions. I intend for the object to rotate around its local X-Y-Z axes at all times, never around the global X-Y-Z axes.
因此,我正在编写一个程序,其中对象以 spacesim 风格移动,以便学习如何在 3D 空间中平滑移动物体。在对 Euler 角进行了一些处理之后,它们似乎并不适合在任意方向上进行自由形式的 3D 运动,因此我决定继续研究似乎最适合这项工作的四元数。我打算让对象始终围绕其局部 XYZ 轴旋转,而不是围绕全局 XYZ 轴旋转。
I've tried to implement a system of rotation using quaternions, but something isn't working. When rotating the object along a single axis, if no previous rotations were undertaken, the thing rotates fine along a given axis. However, when applying one rotation after another has been performed, the second rotation is not always along the local axis it's supposed to be rotating along - for instance, after a rotation of about 90° around the Z axis, a rotation around the Y axis still takes place around the global Y axis, rather than the new local Y axis which is aligned with the global X axis.
我尝试使用四元数实现旋转系统,但有些东西不起作用。沿单个轴旋转对象时,如果之前没有进行任何旋转,则物体会沿给定轴精细旋转。但是,当执行一个接一个旋转时,第二个旋转并不总是沿着它应该沿着的局部轴旋转 - 例如,在绕 Z 轴旋转约 90° 后,绕 Y 轴旋转仍然围绕全局 Y 轴发生,而不是与全局 X 轴对齐的新局部 Y 轴。
Huh. So let's go through this step by step. The mistake must be in here somewhere.
呵呵。因此,让我们一步一步地完成此操作。错误一定是在这里的某个地方。
STEP 1 - Capture Input
步骤 1 - 捕获输入
I figured it would be best to use Euler angles (or a Pitch-Yaw-Roll scheme) for capturing player input. At the moment, arrow keys control Pitch and Yaw, whereas Q and E control Roll. I capture player input thus (I am using SFML 1.6):
我认为最好使用 Euler 角(或 Pitch-Yaw-Roll 方案)来捕获玩家输入。目前,方向键控制 Pitch 和 Yaw,而 Q 和 E 控制 Roll。我因此捕获玩家输入(我使用的是 SFML 1.6):
///SPEEDS
float ForwardSpeed = 0.05;
float TurnSpeed = 0.5;
//Rotation
sf::Vector3<float> Rotation;
Rotation.x = 0;
Rotation.y = 0;
Rotation.z = 0;
//PITCH
if (m_pApp->GetInput().IsKeyDown(sf::Key::Up) == true)
{
Rotation.x -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Down) == true)
{
Rotation.x += TurnSpeed;
}
//YAW
if (m_pApp->GetInput().IsKeyDown(sf::Key::Left) == true)
{
Rotation.y -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Right) == true)
{
Rotation.y += TurnSpeed;
}
//ROLL
if (m_pApp->GetInput().IsKeyDown(sf::Key::Q) == true)
{
Rotation.z -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::E) == true)
{
Rotation.z += TurnSpeed;
}
//Translation
sf::Vector3<float> Translation;
Translation.x = 0;
Translation.y = 0;
Translation.z = 0;
//Move the entity
if (Rotation.x != 0 ||
Rotation.y != 0 ||
Rotation.z != 0)
{
m_Entity->ApplyForce(Translation, Rotation);
}
m_Entity is the thing I'm trying to rotate. It also contains the quaternion and rotation matrices representing the object's rotation.
m_Entity 是我想要旋转的东西。它还包含表示对象旋转的四元数和旋转矩阵。
STEP 2 - Update quaternion
第 2 步 - 更新四元数
I'm not 100% sure this is the way it's supposed to be done, but this is what I tried doing in Entity::ApplyForce():
我不是 100% 确定这是应该完成的方式,但这是我在 Entity::ApplyForce() 中尝试做的:
//Rotation
m_Rotation.x += Rotation.x;
m_Rotation.y += Rotation.y;
m_Rotation.z += Rotation.z;
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(m_Rotation.x, m_Rotation.y, m_Rotation.z);// * m_qRotation;
m_qRotation.RotationMatrix(m_RotationMatrix);
As you can see, I'm not sure whether it's best to just build a new quaternion from updated Euler angles, or whether I'm supposed to multiply the quaternion representing the change with the quaternion representing the overall current rotation, which is the impression I got when reading this guide. If the latter, my code would look like this:
如您所见,我不确定是否最好仅从更新的欧拉角构建新的四元数,或者是否应该将表示变化的四元数与表示当前整体旋转的四元数相乘,这是印象我在阅读本指南时得到了。如果是后者,我的代码将如下所示:
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;
m_Rotation is the object's current rotation stored in PYR format; Rotation is the change demanded by player input. Either way, though, the problem might be in my implementation of my Quaternion class. Here is the whole thing:
m_Rotation 是以 PYR 格式存储的对象的当前旋转;轮换是玩家输入要求的变化。但是,无论哪种方式,问题都可能出在我对 Quaternion 类的实现中。这是整个事情:
Quaternion::Quaternion(float Pitch, float Yaw, float Roll)
{
float Pi = 4 * atan(1);
//Set the values, which came in degrees, to radians for C++ trig functions
float rYaw = Yaw * Pi / 180;
float rPitch = Pitch * Pi / 180;
float rRoll = Roll * Pi / 180;
//Components
float C1 = cos(rYaw / 2);
float C2 = cos(rPitch / 2);
float C3 = cos(rRoll / 2);
float S1 = sin(rYaw / 2);
float S2 = sin(rPitch / 2);
float S3 = sin(rRoll / 2);
//Create the final values
a = ((C1 * C2 * C3) - (S1 * S2 * S3));
x = (S1 * S2 * C3) + (C1 * C2 * S3);
y = (S1 * C2 * C3) + (C1 * S2 * S3);
z = (C1 * S2 * C3) - (S1 * C2 * S3);
}
//Overload the multiplier operator
Quaternion Quaternion::operator* (Quaternion OtherQuat)
{
float A = (OtherQuat.a * a) - (OtherQuat.x * x) - (OtherQuat.y * y) - (OtherQuat.z * z);
float X = (OtherQuat.a * x) + (OtherQuat.x * a) + (OtherQuat.y * z) - (OtherQuat.z * y);
float Y = (OtherQuat.a * y) - (OtherQuat.x * z) - (OtherQuat.y * a) - (OtherQuat.z * x);
float Z = (OtherQuat.a * z) - (OtherQuat.x * y) - (OtherQuat.y * x) - (OtherQuat.z * a);
Quaternion NewQuat = Quaternion(0, 0, 0);
NewQuat.a = A;
NewQuat.x = X;
NewQuat.y = Y;
NewQuat.z = Z;
return NewQuat;
}
//Calculates a rotation matrix and fills Matrix with it
void Quaternion::RotationMatrix(GLfloat* Matrix)
{
//Column 1
Matrix[0] = (a*a) + (x*x) - (y*y) - (z*z);
Matrix[1] = (2*x*y) + (2*a*z);
Matrix[2] = (2*x*z) - (2*a*y);
Matrix[3] = 0;
//Column 2
Matrix[4] = (2*x*y) - (2*a*z);
Matrix[5] = (a*a) - (x*x) + (y*y) - (z*z);
Matrix[6] = (2*y*z) + (2*a*x);
Matrix[7] = 0;
//Column 3
Matrix[8] = (2*x*z) + (2*a*y);
Matrix[9] = (2*y*z) - (2*a*x);
Matrix[10] = (a*a) - (x*x) - (y*y) + (z*z);
Matrix[11] = 0;
//Column 4
Matrix[12] = 0;
Matrix[13] = 0;
Matrix[14] = 0;
Matrix[15] = 1;
}
There's probably something in there to make somebody wiser than me cringe, but I can't see it. For converting from Euler angles to a quaternion, I used the "first method" according to this source, which also seems to suggest that the equation automatically creates a unit quaternion ("clearly normalized"). For multiplying quaternions, I again drew on this C++ guide.
里面可能有什么东西能让比我更聪明的人畏缩,但我看不到。为了从欧拉角转换为四元数,我根据这个来源使用了“第一种方法” ,这似乎也表明该方程会自动创建一个单位四元数(“明确归一化”)。为了乘以四元数,我再次借鉴了这个 C++ 指南。
STEP 3 - Deriving a rotation matrix from the quaternion
第 3 步 - 从四元数导出旋转矩阵
Once that is done, as per R. Martinho Fernandes' answer to this question, I try to build a rotation matrix from the quaternion and use that to update my object's rotation, using the above Quaternion::RotationMatrix() code in the following line:
一旦完成,根据 R. Martinho Fernandes 对这个问题的回答,我尝试从四元数构建一个旋转矩阵并使用它来更新我的对象的旋转,使用下面一行中的上述 Quaternion::RotationMatrix() 代码:
m_qRotation.RotationMatrix(m_RotationMatrix);
I should note that m_RotationMatrix is GLfloat m_RotationMatrix[16]
, as per the required parameters of glMultMatrix, which I believe I am supposed to use later on when displaying the object. It is initialized as:
我应该注意到 m_RotationMatrix 是GLfloat m_RotationMatrix[16]
,根据glMultMatrix所需的参数,我相信我应该在稍后显示对象时使用它。它被初始化为:
m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
Which I believe is the "neutral" OpenGL rotation matrix (every 4 values together represent a column, correct? Again, I get this from the glMultMatrix page).
我认为这是“中性”OpenGL 旋转矩阵(每 4 个值一起代表一列,对吗?同样,我从glMultMatrix 页面得到了这个)。
STEP 4 - Display!
第 4 步 - 显示!
Finally, we get to the function run each cycle for the object that is supposed to display it.
最后,我们会在每个循环中为应该显示它的对象运行该函数。
glPushMatrix();
glTranslatef(m_Position.x, m_Position.y, m_Position.z);
glMultMatrixf(m_RotationMatrix);
//glRotatef(m_Rotation.y, 0.0, 1.0, 0.0);
//glRotatef(m_Rotation.z, 0.0, 0.0, 1.0);
//glRotatef(m_Rotation.x, 1.0, 0.0, 0.0);
//glRotatef(m_qRotation.a, m_qRotation.x, m_qRotation.y, m_qRotation.z);
//[...] various code displaying the object's VBO
glPopMatrix();
I have left my previous failed attempts there, commented out.
我已经把我以前失败的尝试留在那里,注释掉了。
Conclusion - Sad panda
结论 - 悲伤的熊猫
That is the conclusion of the life cycle of player input, from cradle to OpenGL-managed grave.
这就是玩家输入生命周期的结束,从摇篮到 OpenGL 管理的坟墓。
I've obviously not understood something, since the behavior I get isn't the behavior I want or expect. But I'm not particularly experienced with matrix math or quaternions, so I don't have the insight required to see the error in my ways.
我显然没有理解一些东西,因为我得到的行为不是我想要或期望的行为。但是我对矩阵数学或四元数并不是特别有经验,所以我没有必要的洞察力以我的方式看到错误。
Can somebody help me out here?
有人可以帮我吗?
回答by Nicol Bolas
All you have done is effectively implement Euler angles with quaternions. That's not helping.
您所做的就是使用四元数有效地实现欧拉角。那没有帮助。
The problem with Euler angles is that, when you compute the matrices, each angle is relative to the rotation of the matrix that came before it. What you want is to take an object's current orientation, and apply a rotation along some axis, producing a new orientation.
欧拉角的问题在于,当您计算矩阵时,每个角度都相对于它之前的矩阵的旋转。您想要的是获取对象的当前方向,并沿某个轴应用旋转,从而产生新的方向。
You can't do that with Euler angles. You can with matrices, and you can with quaternions (as they're just the rotation part of a matrix). But you can't do it by pretending they are Euler angles.
你不能用欧拉角做到这一点。您可以使用矩阵,也可以使用四元数(因为它们只是矩阵的旋转部分)。但是你不能通过假装它们是欧拉角来做到这一点。
This is done by not storing angles at all. Instead, you just have a quaternion which represents the current orientation of the object. When you decide to apply a rotation to it (of some angle by some axis), you construct a quaternion that represents that rotation by an angle around that axis. Then you right-multiply that quaternion with the current orientation quaternion, producing a new current orientation.
这是通过在不存储角度做所有。相反,您只有一个表示对象当前方向的四元数。当您决定对其应用旋转(通过某个轴的某个角度)时,您可以构建一个四元数,该四元数表示该旋转围绕该轴的角度。然后将该四元数与当前方向四元数右乘,产生一个新的当前方向。
When you render the object, you use the current orientation as... the orientation.
渲染对象时,您将当前方向用作...方向。
回答by Homer
Quaternions represent orientations around 3D compound axes. But they can also represent 'delta-rotations'.
四元数表示围绕 3D 复合轴的方向。但它们也可以代表“增量旋转”。
To 'rotate an orientation', we need an orientation (a quat), and a rotation (also a quat), and we multiply them together, resulting in (you guessed it) a quat.
要“旋转方向”,我们需要一个方向(一个 quat)和一个旋转(也是一个 quat),我们将它们相乘,得到(你猜对了)一个 quat。
You noticed they are not commutative, that means the order we multiply them in absolutely matters, just like for matrices. The order tends to depend on the implementation of your math library, but really, there's only two possible ways to do it, so it shouldn't take you too long to figure out which one is the right one - if things are 'orbiting' instead of 'rotating', then you have them the wrong way around.
你注意到它们不是可交换的,这意味着我们将它们相乘的顺序绝对重要,就像矩阵一样。顺序往往取决于你的数学库的实现,但实际上,只有两种可能的方法来做到这一点,所以你不应该花太长时间找出哪个是正确的——如果事情是“轨道”的而不是“旋转”,那么你就错了。
For your example of yaw and pitch, I would build my 'delta-rotation' quaternion from yaw, pitch and roll angles, with roll set to zero, and then apply that to my 'orientation' quaternion, rather than doing the rotations one axis at a time.
对于偏航和俯仰的示例,我将从偏航、俯仰和滚转角度构建我的“增量旋转”四元数,滚转设置为零,然后将其应用于我的“方向”四元数,而不是旋转一个轴一次。