web-dev-qa-db-ja.com

OpenGLローテーションにクォータニオンを使用する

そこで、3D空間をスムーズに移動する方法を学ぶために、オブジェクトがspacesimスタイルで移動するプログラムを作成しています。オイラー角を少しいじった後、任意の方向への自由形式の3D移動にはあまり適していないように思われるので、この仕事に最適と思われるクォータニオンに移ることにしました。オブジェクトは、グローバルX-Y-Z軸を中心にではなく、常にローカルX-Y-Z軸を中心に回転するようにしています。

クォータニオンを使用して回転システムを実装しようとしましたが、何かが機能していません。オブジェクトを単一の軸に沿って回転させるときに、以前に回転が行われていなければ、オブジェクトは特定の軸に沿って細かく回転します。ただし、次々に回転を適用する場合、2番目の回転は必ずしもローカル軸に沿って回転するとは限りません。たとえば、Z軸を中心に約90°回転した後、Y軸を中心に回転します。グローバルX軸に位置合わせされた新しいローカルY軸ではな​​く、グローバルY軸の周りで引き続き発生します。

ええと。それでは、このステップを段階的に見ていきましょう。間違いはここのどこかにあるに違いありません。

ステップ1-入力のキャプチャ

プレーヤーの入力をキャプチャするには、オイラー角(またはピッチ-ヨー-ロールスキーム)を使用するのが最善であると考えました。現時点では、矢印キーはピッチとヨーを制御し、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。MartinhoFernandesの この質問 に対する回答に従って、クォータニオンから回転行列を作成し、それを使用して、上記のQuaternion :: RotationMatrixを使用してオブジェクトの回転を更新しようとします。 ()次の行のコード:

m_qRotation.RotationMatrix(m_RotationMatrix);

glMultMatrixの必須パラメーター に従って、m_RotationMatrixは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が管理するGraveまでのプレイヤー入力のライフサイクルの結論です。

私が得る行動は私が望むまたは期待する行動ではないので、私は明らかに何かを理解していません。しかし、私は行列の数学やクォータニオンの経験が特にないので、自分のやり方でエラーを確認するために必要な洞察がありません。

誰かがここで私を助けてくれますか?

18
GarrickW

あなたがしたことは、クォータニオンでオイラー角を効果的に実装することだけです。それは役に立たない。

オイラー角の問題は、行列を計算するときに、各角度がその前に来た行列の回転に相対的であるということです。必要なのは、オブジェクトの現在のorientationを取得し、ある軸に沿って回転を適用して、新しい方向を生成することです。

オイラー角ではそれを行うことはできません。行列を使用することも、クォータニオンを使用することもできます(行列の回転部分にすぎないため)。しかし、それらがオイラー角であると偽ってそれを行うことはできません。

これは、allで角度を保存しないことによって行われます。代わりに、オブジェクトの現在の方向を表すクォータニオンがあります。 (ある軸によるある角度の)回転を適用することにした場合、その回転をその軸の周りの角度で表すクォータニオンを作成します。次に、そのクォータニオンに現在の方向のクォータニオンを右乗算して、新しい現在の方向を生成します。

オブジェクトをレンダリングするときは、現在の方向を...方向として使用します。

23
Nicol Bolas

クォータニオンは、3D複合軸の周りの方向を表します。ただし、「デルタ回転」を表すこともできます。

「方向を回転させる」には、方向(クアット)と回転(クアット)が必要です。これらを乗算すると、(ご想像のとおり)クアットになります。

あなたはそれらが可換ではないことに気づきました。つまり、行列の場合と同じように、それらを乗算する順序が絶対的に重要です。順序は数学ライブラリの実装に依存する傾向がありますが、実際には2つの方法しか考えられないため、どちらが正しいかを判断するのにそれほど時間はかかりません-物事が「軌道を回っている」場合「回転」する代わりに、間違った方法でそれらを使用します。

ヨーとピッチの例では、ロールをゼロに設定して、ヨー、ピッチ、ロールの角度から「デルタ回転」クォータニオンを構築し、それを1軸の回転ではなく、「方向」クォータニオンに適用します。一度に。

2
Homer