22

私は空間的視覚化のために3Dエンジンを実装しており、次のナビゲーション機能を備えたカメラを作成しています。

  • カメラを回転させます(つまり、頭を回転させるのと同じです)
  • 任意の3Dポイント(おそらく画面の中央にない空間内のポイント。カメラは同じ相対的な視線方向を維持しながらこの周りを回転する必要があります。つまり、視線方向も変化します。これは直接見ません。選択した回転点)
  • カメラの平面でパンします(カメラのルックベクトルに直交する平面で上下または左右に移動します)

カメラが回転することは想定されていません。つまり、「上」は上のままです。このため、位置と2つの角度、X軸とY軸を中心とした回転(Zはロール)でカメラを表します。次に、カメラの位置とこれら2つの角度を使用してビューマトリックスが再計算されます。これは、パンや目の回転には最適ですが、任意のポイントを中心に回転する場合には機能しません。代わりに、次の動作が得られます。

  • 目自体は明らかに本来よりも上下に動いています
  • m_dRotationXが0またはpiの場合、目はまったく上下に移動しません。(ジンバルロック?どうすればこれを回避できますか?)
  • m_dRotationX円周率がpiと2piの間にある場合、目の回転が反転します(回転を変更すると、下を向いている場合は上を向いて、上を向いている場合は下を向いています) 。

(a)この「ドリフト」の回転の原因は何ですか?

これはジンバルロックの可能性があります。もしそうなら、これに対する標準的な答えは「回転を表すためにクォータニオンを使用する」であり、ここで何度もSO(1、2、3など)で述べられていますが、残念ながら具体的な詳細はありません(。これは私が見つけた最良の答えですこれまでのところ、まれです。)上記の2種類の回転を組み合わせたクォータニオンを使用してカメラを実装するのに苦労しました。実際、私は2つの回転を使用してクォータニオンを構築していますが、以下のコメント投稿者は理由がないと述べています。すぐにマトリックスを構築するのは問題ありません。

これは、ポイントを中心に回転するときにX回転とY回転(カメラの視線方向を表す)を変更するときに発生しますが、回転を直接変更するとき、つまりカメラを回転させるときに発生するわけではありません。私には、これは意味がありません。同じ値です。

(b)このカメラには、別のアプローチ(たとえば、クォータニオン)の方が適していますか?もしそうなら、どうすれば上記の3つのカメラナビゲーション機能すべてを実装できますか?

別のアプローチの方がよい場合は、そのアプローチの具体的な実装例を提供することを検討してください。(私はDirectX9とC ++、およびSDKが提供するD3DX *ライブラリを使用しています。)この2番目のケースでは、質問に1つ追加できる場合、数日以内に報奨金を追加して授与します。これは私が銃を飛び越えているように聞こえるかもしれませんが、時間に余裕がなく、これを迅速に実装または解決する必要があります(これは締め切りの厳しい商用プロジェクトです)。詳細な回答は、SOアーカイブも改善します。私がこれまで読んだカメラの答えは、コードが軽いです。

ご協力いただきありがとうございます :)


いくつかの説明

これまでのコメントと回答に感謝します!問題についていくつかのことを明確にしようと思います。

  • ビューマトリックスは、カメラの位置と2つの角度のいずれかが変更されるたびに、それらから再計算されます。マトリックス自体は決して蓄積されません(つまり更新されません)-それは新たに再計算されます。ただし、カメラの位置と2つの角度変数は累積されます(たとえば、マウスが移動するたびに、マウスが上下に移動したピクセル数に基づいて、一方または両方の角度に少量が加算または減算されます。または画面上の左右。)

  • コメンテーターのJCooperは、私がジンバルロックに苦しんでいると述べています。

変換を適用する前に、eyePosを完全にyz平面に回転させる別の回転を変換に追加し、その後、それを元に戻す別の回転を追加します。ヨーピッチロールマトリックスを適用する直前と直後に、y軸を中心に次の角度で回転します(角度の1つを無効にする必要があります。試してみるのが、どちらかを決定する最も速い方法です)。 double fixAngle = atan2(oEyeTranslated.z,oEyeTranslated.x);

残念ながら、これを説明したように実装すると、回転の1つが原因で、私の目が非常に速い速度でシーンの上に飛び出します。私のコードは単にこの説明の悪い実装であると確信していますが、それでももっと具体的なものが必要です。一般に、アルゴリズムの不特定のテキストによる説明は、コメントされ、説明された実装よりも有用ではないと思います。 以下のコードと統合する(つまり、他のナビゲーション方法と統合する)具体的で実用的な例に報奨金を追加します。これは、解決策を理解し、機能するものが必要なためです。締め切りが厳しいので、すぐに機能するものを実装する必要があります。

アルゴリズムの説明文で答える場合は、実装するのに十分詳細であることを確認してください(「Yを中心に回転し、変換してから、元に戻す」は意味があるかもしれませんが、意味を理解するための詳細が不足しています。 良い答えは明確で、道標があり、他の人が異なる基準でさえ理解することを可能にし、「堅実な耐候性の情報掲示板」です。

順番に、私は問題を明確に説明しようとしました、そして私がそれをより明確にすることができるならば、私に知らせてください。


私の現在のコード

上記の3つのナビゲーション機能を実装するには、カーソルが移動したピクセルに基づいて移動するマウス移動イベントで、次のようにします。

// Adjust this to change rotation speed when dragging (units are radians per pixel mouse moves)
// This is both rotating the eye, and rotating around a point
static const double dRotatePixelScale = 0.001;
// Adjust this to change pan speed (units are meters per pixel mouse moves)
static const double dPanPixelScale = 0.15;

switch (m_eCurrentNavigation) {
    case ENavigation::eRotatePoint: {
        // Rotating around m_oRotateAroundPos
        const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
        const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;

        // To rotate around the point, translate so the point is at (0,0,0) (this makes the point
        // the origin so the eye rotates around the origin), rotate, translate back
        // However, the camera is represented as an eye plus two (X and Y) rotation angles
        // This needs to keep the same relative rotation.

        // Rotate the eye around the point
        const D3DXVECTOR3 oEyeTranslated = m_oEyePos - m_oRotateAroundPos;
        D3DXMATRIX oRotationMatrix;
        D3DXMatrixRotationYawPitchRoll(&oRotationMatrix, dX, dY, 0.0);
        D3DXVECTOR4 oEyeRotated;
        D3DXVec3Transform(&oEyeRotated, &oEyeTranslated, &oRotationMatrix);
        m_oEyePos = D3DXVECTOR3(oEyeRotated.x, oEyeRotated.y, oEyeRotated.z) + m_oRotateAroundPos;

        // Increment rotation to keep the same relative look angles
        RotateXAxis(dX);
        RotateYAxis(dY);
        break;
    }
    case ENavigation::ePanPlane: {
        const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dPanPixelScale;
        const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dPanPixelScale;
        m_oEyePos += GetXAxis() * dX; // GetX/YAxis reads from the view matrix, so increments correctly
        m_oEyePos += GetYAxis() * -dY; // Inverted compared to screen coords
        break;
    }
    case ENavigation::eRotateEye: {
        // Rotate in radians around local (camera not scene space) X and Y axes
        const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
        const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
        RotateXAxis(dX);
        RotateYAxis(dY);
        break;
    }

およびメソッドは非常に単純ですRotateXAxisRotateYAxis

void Camera::RotateXAxis(const double dRadians) {
    m_dRotationX += dRadians;
    m_dRotationX = fmod(m_dRotationX, 2 * D3DX_PI); // Keep in valid circular range
}

void Camera::RotateYAxis(const double dRadians) {
    m_dRotationY += dRadians;

    // Limit it so you don't rotate around when looking up and down
    m_dRotationY = std::min(m_dRotationY, D3DX_PI * 0.49); // Almost fully up
    m_dRotationY = std::max(m_dRotationY, D3DX_PI * -0.49); // Almost fully down
}

そして、これからビューマトリックスを生成するには:

void Camera::UpdateView() const {
    const D3DXVECTOR3 oEyePos(GetEyePos());
    const D3DXVECTOR3 oUpVector(0.0f, 1.0f, 0.0f); // Keep up "up", always.

    // Generate a rotation matrix via a quaternion
    D3DXQUATERNION oRotationQuat;
    D3DXQuaternionRotationYawPitchRoll(&oRotationQuat, m_dRotationX, m_dRotationY, 0.0);
    D3DXMATRIX oRotationMatrix;
    D3DXMatrixRotationQuaternion(&oRotationMatrix, &oRotationQuat);

    // Generate view matrix by looking at a point 1 unit ahead of the eye (transformed by the above
    // rotation)
    D3DXVECTOR3 oForward(0.0, 0.0, 1.0);
    D3DXVECTOR4 oForward4;
    D3DXVec3Transform(&oForward4, &oForward, &oRotationMatrix);
    D3DXVECTOR3 oTarget = oEyePos + D3DXVECTOR3(oForward4.x, oForward4.y, oForward4.z); // eye pos + look vector = look target position
    D3DXMatrixLookAtLH(&m_oViewMatrix, &oEyePos, &oTarget, &oUpVector);
}
4

4 に答える 4

8

ビューマトリックスの作成方法を考えると、「ロール」は不可能であるように思われます。他のすべてのコード(一部は少しおかしいように見えます)に関係なく、呼び出しは、アップベクトルと平行でない限り、「アップ」ベクトルとしてD3DXMatrixLookAtLH(&m_oViewMatrix, &oEyePos, &oTarget, &oUpVector);指定されたときにロールなしの行列を作成する必要があります。(-.49pi、+。49pi)以内に 制限しているため、これは当てはまらないようです。[0,1,0]oTarget-oEyePosm_dRotationY

おそらく、「ロール」が発生していることをどのように知っているかを明確にすることができます。グランドプレーンがあり、そのグランドプレーンの地平線が水平から外れていますか?

余談ですが、UpdateViewでは、D3DXQuaternionRotationYawPitchRollすぐに向きを変えて行列に変更するため、は完全に不要のようです。D3DXMatrixRotationYawPitchRollマウスイベントで行ったのと同じように使用します。クォータニオンは、目の座標で発生する回転を蓄積するための便利な方法であるため、カメラで使用されます。厳密な順序で2つの回転軸のみを使用しているため、角度を累積する方法は適切です。(0,0,1)のベクトル変換も実際には必要ありません。はoRotationMatrixすでに(_31,_32,_33)エントリにこれらの値を持っているはずです。


アップデート

ロールではない場合、問題は次のとおりです。回転行列を作成して目を世界座標で移動しますが、ピッチはカメラ座標で発生させます。ロールは許可されておらず、ヨーは最後に実行されるため、ヨーはワールドとカメラの参照フレームの両方で常に同じです。以下の画像を検討してください。

ローカルローテーション

これらはカメラ座標で実行されるため、コードはローカルピッチとヨーに対して正常に機能します。

ポイントの周りの通常のピッチ

ただし、基準点を中心に回転すると、ワールド座標にある回転行列が作成され、それを使用してカメラの中心が回転します。これは、カメラの座標系が世界の座標系と一致している場合は問題なく機能します。ただし、カメラの位置を回転させる前にピッチ制限に達しているかどうかを確認しないと、その制限に達したときにクレイジーな動作が発生します。カメラは突然世界中でスケートを開始します。基準点を中心に「回転」しますが、向きは変わりません。

ポイントの周りのロックされたピッチ

カメラの軸が世界の軸と一致しないと、奇妙なことが起こります。極端な場合、カメラを回転させようとしているため、カメラはまったく動きません。

軸外ピッチはロールを引き起こします

上記は通常発生することですが、カメラの向きを個別に処理するため、カメラは実際には回転しません。

カメラの向きは、翻訳とは別に処理されます

代わりに、それは直立したままですが、奇妙な翻訳が行われています。

これを処理する1つの方法は、(1)常にカメラを基準点に対して標準的な位置と方向に配置し、(2)回転させ、(3)完了したら元に戻すことです(例:基準点を原点に平行移動し、ヨーピッチ回転を適用してから逆方向に平行移動する方法と同様です)。しかし、それについてもっと考えると、これはおそらく最善の方法ではありません。


アップデート2

GenericHumanの答えはおそらく最高だと思います。回転が軸外の場合にどのくらいのピッチを適用する必要があるかについては疑問が残りますが、今のところ、それは無視します。多分それはあなたに許容できる結果を与えるでしょう。

答えの本質は次のとおりです。マウスを動かす前は、カメラはc 1 =にあり、 M1 =m_oEyePosによって方向付けられています。基準点a =を考えてみましょう。カメラの観点から、この点はa'= M 1(ac 1です。 D3DXMatrixRotationYawPitchRoll(&M_1,m_dRotationX,m_dRotationY,0)m_oRotateAroundPos

カメラの向きをM2 =に変更しますD3DXMatrixRotationYawPitchRoll(&M_2,m_dRotationX+dX,m_dRotationY+dY,0)。[重要:m_dRotationY特定の範囲外に入ることができないため、dYがその制約に違反しないようにする必要があります。]カメラの向きが変わると、カメラの位置もaを中心に新しいポイントに回転するようにします。 c2_ これは、カメラの観点からは変化しないことを意味します。つまり、M 1(ac 1)== M 2(ac 2

したがって、 c 2について解きます(回転行列の転置は逆行列と同じであることに注意してください)。

M 2 T M 1(ac 1)==(ac 2 =>

-M 2 T M 1(ac 1)+ a == c 2

これをc1に適用される変換として見ると、最初に否定され、次にaによって変換され、次にM 1によって回転され、次にM 2 Tによって回転され、再び否定され、次にによって変換されることがわかります。もう一度これらはグラフィックライブラリが得意とする変換であり、すべて単一の変換行列にまとめることができます。

@Generic Humanはその答えを称賛するに値しますが、ここにそのコードがあります。もちろん、適用する前にピッチの変化を検証する関数を実装する必要がありますが、それは簡単です。私はコンパイルを試みていないので、このコードにはおそらくいくつかのタイプミスがあります:

case ENavigation::eRotatePoint: {
    const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
    double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
    dY = validatePitch(dY); // dY needs to be kept within bounds so that m_dRotationY is within bounds

    D3DXMATRIX oRotationMatrix1; // The camera orientation before mouse-change
    D3DXMatrixRotationYawPitchRoll(&oRotationMatrix1, m_dRotationX, m_dRotationY, 0.0);

    D3DXMATRIX oRotationMatrix2; // The camera orientation after mouse-change
    D3DXMatrixRotationYawPitchRoll(&oRotationMatrix2, m_dRotationX + dX, m_dRotationY + dY, 0.0);

    D3DXMATRIX oRotationMatrix2Inv; // The inverse of the orientation
    D3DXMatrixTranspose(&oRotationMatrix2Inv,&oRotationMatrix2); // Transpose is the same in this case

    D3DXMATRIX oScaleMatrix; // Negative scaling matrix for negating the translation
    D3DXMatrixScaling(&oScaleMatrix,-1,-1,-1);

    D3DXMATRIX oTranslationMatrix; // Translation by the reference point
    D3DXMatrixTranslation(&oTranslationMatrix,
         m_oRotateAroundPos.x,m_oRotateAroundPos.y,m_oRotateAroundPos.z);

    D3DXMATRIX oTransformMatrix; // The full transform for the eyePos.
    // We assume the matrix multiply protects against variable aliasing
    D3DXMatrixMultiply(&oTransformMatrix,&oScaleMatrix,&oTranslationMatrix);
    D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oRotationMatrix1);
    D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oRotationMatrix2Inv);
    D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oScaleMatrix);
    D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oTranslationMatrix);

    D3DXVECTOR4 oEyeFinal;
    D3DXVec3Transform(&oEyeFinal, &m_oEyePos, &oTransformMatrix);

    m_oEyePos = D3DXVECTOR3(oEyeFinal.x, oEyeFinal.y, oEyeFinal.z) 

    // Increment rotation to keep the same relative look angles
    RotateXAxis(dX);
    RotateYAxis(dY);
    break;
}
于 2012-07-16T20:53:17.020 に答える
5

すべての回転の問題を回避できる、はるかに簡単な解決策があると思います。

表記法:Aは回転させたいポイント、Cは元のカメラの位置、Mはグローバル座標をカメラのローカルビューポートにマップする元のカメラ回転行列です。

  1. A'= M ×(A --C )に等しいAのローカル座標をメモします。
  2. 通常の「目の回転」モードと同じようにカメラを回転させます。ビューマトリックスMを更新して、 M 2に変更され、Cが変更されないようにします。
  3. ここで、A'= M 2 ×(A --C 2 となるC2を見つけたいと思います。これは、式C 2 = A --M2-1 × A' によって簡単に実行できます。
  4. Voilà、カメラは回転しました。Aのローカル座標は変更されていないため、Aは同じ場所にあり、同じスケールと距離にあります。

追加のボーナスとして、回転動作は「目の回転」モードと「点回転」モードの間で一貫しています。

于 2012-07-27T08:26:25.487 に答える
4

小さな回転行列を繰り返し適用することでポイントを中心に回転します。これにより、ドリフトが発生する可能性があり(小さな精度のエラーが加算されます)、しばらくすると完全な円を描くことができなくなるでしょう。ビューの角度は単純な1次元のダブルを使用しているため、ドリフトがはるかに少なくなります。

考えられる修正は、専用のヨー/ピッチと、そのビューモードに入ったときのポイントからの相対位置を保存し、それらを使用して計算を行うことです。カメラを動かすときにそれらを更新する必要があるので、これにはもう少し簿記が必要です。ポイントが動くとカメラも動くので注意してください。これは改善だと思います。

于 2012-07-13T14:15:03.080 に答える
2

私が正しく理解していれば、最終的な行列の回転コンポーネント(問題#3の逆回転コントロールを除く)には満足していますが、平行移動部分には満足していませんか?

問題は、それらを異なる方法で処理するという事実に起因しているようです。回転部分を毎回最初から再計算していますが、平行移動部分(m_oEyePos)を累積しています。他のコメントでは精度の問題について言及していますが、実際にはFPの精度よりも重要です。小さなヨー/ピッチ値から回転を累積することは、累積されたヨー/ピッチから1つの大きな回転を行うことと同じではありません---数学的に---。したがって、回転/平行移動の不一致。これを修正するには、「oTarget = oEyePos + ...」を見つける方法と同様に、回転部分と同時に目の位置を最初から再計算してみてください。

oEyePos = m_oRotateAroundPos - dist * D3DXVECTOR3(oForward4.x, oForward4.y, oForward4.z)

dist古い目の位置から修正または計算できます。これにより、回転ポイントが画面の中央に保持されます。より一般的なケース(興味のあるもの)では、-dist * oForwardここを古い/初期に置き換えて、m_oEyePos - m_oRotateAroundPos古い/初期のカメラ回転を掛けてカメラ空間に移動し(カメラの座標系で一定のオフセットベクトルを見つける)、反転した新しいカメラの回転を掛けて、世界の新しい方向を取得します。

もちろん、これはピッチが真っ直ぐ上または下にあるときにジンバルロックの対象になります。この部分を解決するには、これらの場合に予想される動作を正確に定義する必要があります。一方、m_dRotationX =0または=piでのロックはかなり奇妙であり(これはヨーであり、ピッチではありませんか?)、上記に関連している可能性があります。

于 2012-07-31T13:19:43.850 に答える