使用四元数进行 OpenGL 旋转 [英] Using Quaternions for OpenGL Rotations

查看:91
本文介绍了使用四元数进行 OpenGL 旋转的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我正在编写一个程序,其中对象以 spacesim 风格移动,以便学习如何在 3D 空间中平滑移动物体.在对 Euler 角进行了一些处理之后,它们似乎并不适合在任意方向上进行自由形式的 3D 运动,因此我决定继续研究似乎最适合这项工作的四元数.我打算让对象始终围绕其局部 X-Y-Z 轴旋转,而不是围绕全局 X-Y-Z 轴旋转.

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.

我尝试使用四元数实现旋转系统,但有些东西不起作用.当沿单个轴旋转对象时,如果没有进行先前的旋转,则物体沿给定的轴精细旋转.但是,当执行一个接一个旋转时,第二个旋转并不总是沿着它应该沿着旋转的局部轴 - 例如,在绕 Z 轴旋转约 90° 后,绕 Y 轴旋转仍然围绕全局 Y 轴发生,而不是与全局 X 轴对齐的新局部 Y 轴.

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.

嗯.因此,让我们一步一步来完成.错误一定是在这里的某个地方.

Huh. So let's go through this step by step. The mistake must be in here somewhere.

第 1 步 - 捕获输入

我认为最好使用 Euler 角(或 Pitch-Yaw-Roll 方案)来捕获玩家输入.目前,方向键控制 Pitch 和 Yaw,而 Q 和 E 控制 Roll.我因此捕获玩家输入(我使用的是 SFML 1.6):

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):

    ///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 是我想要旋转的东西.它还包含表示对象旋转的四元数和旋转矩阵.

m_Entity is the thing I'm trying to rotate. It also contains the quaternion and rotation matrices representing the object's rotation.

第 2 步 - 更新四元数

我不是 100% 确定这是应该完成的方式,但这是我在 Entity::ApplyForce() 中尝试做的:

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():

//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 是以 PYR 格式存储的对象的当前旋转;轮换是玩家输入要求的变化.但是,无论哪种方式,问题都可能出在我对 Quaternion 类的实现中.这是整个事情:

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:

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;
}

里面可能有什么东西能让比我更聪明的人畏缩,但我看不到.为了从欧拉角转换为四元数,我根据 this source,这似乎也表明该方程自动创建了一个单位四元数(明确归一化").对于四元数的乘法,我再次参考了这个 C++ 指南.

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.

第 3 步 - 从四元数导出旋转矩阵

一旦完成,根据 R. Martinho Fernandes 对这个问题的回答,我尝试从四元数构建一个旋转矩阵并使用它来更新我的对象的旋转,使用以下行中的上述 Quaternion::RotationMatrix() 代码:

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:

m_qRotation.RotationMatrix(m_RotationMatrix);

我应该注意到 m_RotationMatrix 是 GLfloat m_RotationMatrix[16],根据 glMultMatrix 的必需参数,我相信我应该在稍后显示对象时使用它.它被初始化为:

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 = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};

我认为哪个是中性"OpenGL 旋转矩阵(每 4 个值一起代表一列,对吗?同样,我从 glMultMatrix 页面).

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).

第 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.

结论 - 悲伤的熊猫

这就是玩家输入的生命周期结束,从摇篮到OpenGL管理的坟墓.

That is the conclusion of the life cycle of player input, from cradle to OpenGL-managed grave.

我显然没有理解某些东西,因为我得到的行为不是我想要或期望的行为.但我对矩阵数学或四元数并不是特别有经验,所以我没有必要的洞察力以我的方式看到错误.

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.

有人可以帮我吗?

推荐答案

您所做的就是有效地使用四元数实现欧拉角.这没有帮助.

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.

这是通过不存储all角度来实现的.相反,您只有一个表示对象当前方向的四元数.当您决定对其应用旋转(通过某个轴的某个角度)时,您构建了一个四元数,该四元数表示该旋转通过围绕该轴的角度.然后将该四元数与当前方向四元数右乘,产生一个新的当前方向.

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.

这篇关于使用四元数进行 OpenGL 旋转的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆