私は空間的視覚化のために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;
}
およびメソッドは非常に単純ですRotateXAxis
。RotateYAxis
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);
}