使用Quaternion进行OpenGL旋转

因此,我正在编写一个程序,让对象在空间样式中移动,以便学习如何在3D空间中平滑地移动事物。 在将Euler角度搞乱之后,似乎他们不适合在任意方向上进行自由形式的3D运动,所以我决定转向看起来最适合工作的东西 - 四元组。 我打算让对象随时围绕其本地XYZ轴旋转,而不是围绕全局XYZ轴旋转。

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

呵呵。 所以让我们一步一步来完成。 这个错误一定在这里。

第1步 - 捕获输入

我认为最好使用欧拉角(或者Pitch-Yaw-Roll方案)来捕捉玩家的输入。 此时,箭头键控制俯仰和偏航,而Q和E控制滚动。 我从而捕捉玩家输入(我正在使用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是我想旋转的东西。 它还包含表示对象旋转的四元数和旋转矩阵。

第2步 - 更新四元数

我不是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);

正如你所看到的,我不确定是最好从更新的欧拉角度建立一个新的四元数,还是我应该把表示变化的四元数与表示当前总旋转的四元数相乘,这是印象阅读本指南时我得到了。 如果是后者,我的代码将如下所示:

//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;

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

可能有些东西让人比我更畏缩,但我看不到它。 为了从欧拉角转换为四元数,我根据这个源使用了“第一种方法”,这似乎也表明方程式会自动创建一个单位四元数(“明确归一化”)。 为了乘四元数,我再次使用这个C ++指南。

第3步 - 从四元数派生旋转矩阵

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

m_qRotation.RotationMatrix(m_RotationMatrix);

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

m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};

我认为这是“中性”的OpenGL旋转矩阵(每4个值一起表示一列,是否正确?我再次从glMultMatrix页面中获得这个结果)。

第4步 - 显示!

最后,我们得到这个函数运行每个周期的应该显示它的对象。

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

我已经把我以前失败的尝试留在了那里,评论说。

结论 - 悲伤的熊猫

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

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

有人可以帮助我吗?


你所做的一切都是有效地实现与四元数的欧拉角。 这没有帮助。

欧拉角的问题是,当你计算矩阵时,每个角度都是相对于之前矩阵旋转的。 你想要的是采取一个对象的当前方向,并沿着某个轴进行旋转,产生一个新的方向。

你不能用欧拉角来做到这一点。 你可以用矩阵,你可以用四元数(因为它们只是矩阵的旋转部分)。 但你不能假装他们是欧拉角。

这是通过不存储角度完成的。 相反,你只需要一个表示对象当前方向的四元数。 当您决定对其应用旋转时(某个轴的某个角度),您可以构造一个四元数来表示围绕该轴的角度的旋转。 然后,将这个四元数与当前的方向四元数正确地相乘,产生一个新的当前方向。

在渲染对象时,将使用当前方向作为方向。


四元数表示围绕3D复合轴的方向。 但他们也可以代表'delta-rotation'。

为了'旋转一个方向',我们需要一个方向(一个quat)和一个旋转(也是一个quat),我们将它们相乘,导致(你猜对了)一个quat。

你注意到它们不是可交换的,这意味着我们把它们放在绝对的问题上,就像矩阵一样。 顺序往往取决于你的数学库的实现,但实际上,只有两种可能的方式来做它,所以它不应该花太长的时间来找出哪一个是正确的 - 如果事情是'轨道'而不是“旋转”,那么你有他们错误的方式。

对于偏航和俯仰的例子,我会用偏航,俯仰和滚转角度来建立我的'三角旋转'四元数,并将滚动设置为零,然后将其应用于我的'方向'四元数,而不是执行一个轴的旋转一次。

链接地址: http://www.djcxy.com/p/14345.html

上一篇: Using Quaternions for OpenGL Rotations

下一篇: Rotating quaternions based on mouse movement (OpenGL and Java)