ワールドアラインされた軸の周りでオブジェクトを回転させる方法は?


15

各軸にオイラー角を持つVector3があります。

通常、回転行列を作成するときは、上記の回転ベクトルからそれぞれの角度を渡すD3DXMatrixRotationXなどの関数を使用し、行列(ZXY)を乗算して、完全なオブジェクト変換行列を形成するために使用される全体的な回転行列を作成します。

ただし、このメソッドはオブジェクト空間で一連の回転を生成します。つまり、(90、0、90)のベクトルをメソッドに渡すと、効果的に(90、90、0)のワールド空間で回転が作成されます。

回転ベクトルの各コンポーネントが、それぞれのワールド空間に配置された軸を中心に回転することを常に保証する方法はありますか?

編集:

これは、現在起こっていることのアニメーションです。赤ではなく、青の軸を中心に回転する方法が必要です。

オイラー角

編集2:

ただ、オイラー角を含む解決策を探しているのではなく、単に世界の軸を中心とした複数の回転の変換を表現できる方法を探していることに注意してください。


Differnet関数を3回呼び出して、不要なベクトルの部分を除外する(関数を呼び出す前にそれらを0に設定する)ことの何が問題になっていますか?そうでなければ、あなたが何を達成しようとしているのか分かりません。
TravisG

何をフィルタリングしますか?3つの個別の関数を呼び出してから、それらを乗算して変換行列を作成します。ただし、これはローカルローテーションを実現します。
Syntac_

オイラー角、または世界の軸を中心とした回転が必要ですか?オイラー角の定義(例:en.wikipedia.org/wiki/Euler_angles)により、アルファ角のみが厳密に世界軸を中心としていることに注意してください。他の2つの角度は、ワールド軸と必ずしも一致しない傾斜軸に対して相対的です。
DMGregory

1
オイラー角を使用すると、頂点に適用する前に3つの回転行列すべてを乗算します。M、N、Oが回転行列の場合、結果の演算はMNO vです。私が提案したのは、各行列を個別に適用することです:v1 = O v0、次にv2 = N v1、最後にv3 = M v2。これにより、各viはワールド座標になり、ワールド座標でも現在の軸に回転行列を使用するだけで済みます。
dsilva.vinicius

3
@ dsilva.vinicius分離された変換は、結合された変換とまったく同じです。つまり、別の言い方をすれば、MNO v == M *(N *(O v))
GuyRT

回答:


1

あなたのコメントに基づいて、オブジェクトの向きをオイラー角のセットとして保存し、プレイヤーがオブジェクトを回転させるときに角度を増減しているようです。つまり、この擬似コードのようなものがあります。

// in player input handling:
if (axis == AXIS_X) object.angleX += dir;
else if (axis == AXIS_Y) object.angleY += dir;
else if (axis == AXIS_Z) object.angleZ += dir;

// in physics update and/or draw code:
matrix = eulerAnglesToMatrix(object.angleX, object.angleY, object.angleZ);

Charles Beattieが述べているように、回転は通勤しないため、プレイヤーが回転をeulerAnglesToMatrix()適用するのと同じ順序でオブジェクトを回転させない限り、これは期待通りに機能しません。

特に、次の一連の回転を考慮してください。

  1. X軸を中心にオブジェクトをx度回転します。
  2. Y軸を中心にオブジェクトをy度回転します。
  3. オブジェクトをX軸の周りに-x度回転させます。
  4. Y軸を中心にオブジェクトを-y度回転します。

上記の擬似コードで実装されたナイーブオイラー角表現では、これらの回転はキャンセルされ、オブジェクトは元の方向に戻ります。現実の世界では、これは起こりません。あなたが私を信じないなら、6面のダイスまたはルービックキューブをつかみ、x = y = 90°にして、自分で試してください!

あなた自身の答えで述べているように、解決策はオブジェクトの方向を回転行列(または四元数)として保存し、ユーザー入力に基づいてその行列を更新することです。つまり、上記の擬似コードの代わりに、次のようなことをします。

// in player input handling:
if (axis == AXIS_X) object.orientation *= eulerAnglesToMatrix(dir, 0, 0);
else if (axis == AXIS_Y) object.orientation *= eulerAnglesToMatrix(0, dir, 0);
else if (axis == AXIS_Z) object.orientation *= eulerAnglesToMatrix(0, 0, dir);

// in physics update and/or draw code:
matrix = object.orientation;  // already in matrix form!

(技術的には、任意の回転行列または四元数はオイラー角のセットとして表すことできるため、それらを使用してオブジェクトの向きを格納することできます。しかし、それぞれがオイラー角として表される2つの連続回転単一の回転への変換はかなり複雑であり、基本的に回転を行列/四元数に変換し、それらを乗算し、結果をオイラー角に戻します。)


はい、あなたは正しいです、これが解決策でした。クォータニオンが必要であるという印象を与えるので、これはconcept3dの答えよりも若干良いと思いますが、それは真実ではありません。3つのオイラー角ではなく、マトリックスとして現在の回転を保存している限り、問題ありませんでした。
Syntac_

14

回転の問題は、ほとんどの人が理解しやすいので、オイラー角の観点でそれを考えることです。

しかし、ほとんどの人は、オイラー角が3つの連続した角であるという点を忘れています。最初の軸の周りの回転は、次の回転を最初の元の回転に対して相対的にすることを意味します。したがって、オイラー角を使用して3軸のそれぞれを中心にベクトルを独立して回転することはできません。

これは、2つの行列を乗算すると直接行列に変換されます。この乗算は、1つの行列を他の行列の空間に変換すると考えることができます。

これは、クォータニオンを使用している場合でも、3つの連続した回転で発生することを意味します。

ここに画像の説明を入力してください

四元数はジンブルロックの解決策ではないという事実を強調したいと思います。四元数を使用してオイラー角を表現した場合、実際にはジンブルロックが常に発生します。問題は、問題がある、表現ではない3つの連続ステップ。

ソリューション?

ベクトルを3軸の周りに独立して回転させるための解決策は、単一の軸と単一の角度に結合することです。この方法で、逐次乗算を行う必要があるステップを取り除くことができます。これは効果的に次のように変換されます。

私の回転行列は、X、Y、Zの周りの回転の結果を表します。

オイラーの解釈ではなく

私の回転行列は、X、Y、Zの周りの回転を表します。

これを明確にするために、ウィキペディアのオイラーの回転定理から引用します。

オイラーの回転定理によれば、固定点を中心とする剛体または座標系の回転または一連の回転は、固定点を通る固定軸(オイラー軸と呼ばれる)を中心とした特定の角度θの単一回転に相当します。オイラー軸は通常、単位ベクトルu→で表されます。したがって、3次元の回転は、ベクトルu→とスカラーθの組み合わせとして表すことができます。クォータニオンは、この軸角度表現を4つの数字でエンコードし、対応する回転をR3の原点を基準とした点を表す位置ベクトルに適用する簡単な方法を提供します。

3つの行列を乗算すると、常に 3つの連続した回転表さ。

ここで、3軸周りの回転を結合するには、X、Y、Z周りの回転を表す単一の軸と単一の角度を取得する必要があります。言い換えれば、軸/角度または四元数表現を使用して、連続回転を取り除く必要があります。

これは通常、通常はクォータニオンまたは軸角度として表される初期方向(軸角度と見なすことができます)で開始し、目的の方向を表すようにその方向を変更することによって行われます。たとえば、アイデンティティクォータテリオンから始めて、差だけ回転して目的の方向に到達します。このようにして、自由度を失うことはありません。


洞察力があると思われるため、回答としてマークされています。
Syntac_

この回答であなたが言おうとしていることを理解するのに苦労しています。単に「オブジェクトの向きをオイラー角として保存しないでください」か?もしそうなら、なぜそう言うだけではありませんか?
イルマリカロネン

@IlmariKaronenより明確に述べることもできますが、concept3dは軸角度表現を促進していると思います。軸角度と四元数の関係については、このドキュメントのセクション1.2.2を参照してください。軸角度表現は上記の理由で実装が簡単で、ジンバルロックの影響を受けません。(少なくとも私にとっては)オイラー角度と同じくらい簡単に理解できます。
NauticalMile

@ concept3d、それは非常に興味深いです、そしてあなたの答えが本当に好きです。ただし、キーボードとマウスを使用してコンピューターを操作する人がいません。マウスのことを考えると、xとyのマウスデルタについて話しているのです。オブジェクトの向きを変更するなど、回転行列を生成するために使用できる単一のクォータニオンでこれらのx、yデルタを表現する方法は?
gmagno

@gmagnoのアプローチは通常、マウスの動きをオブジェクトまたはシーンに投影し、その空間のデルタを計算することです。これを行うには、レイをキャストして交差を計算します。レイキャスティング、プロジェクト、アンプロジェクトを検索します。何年もCGで働いていなかったので、詳細は大雑把です。それが役立つことを願っています。
concept3d

2

回転の組み合わせをオブジェクト空間からワールド空間に切り替えるのは簡単です。回転が適用される順序を逆にするだけです。

あなたの場合、行列を乗算する代わりに、Z × X × Y計算するだけですY × X × Z

この理由はWikipediaにあります:固有の回転と外部の回転の間の変換


それが本当だった場合、ソースからの次の文は真実ではありません。なぜなら、回転は異なるからです。 」
Syntac_

1
ここに矛盾はありません。私の答えとその声明の両方が真実です。はい、オブジェクト空間とワールド空間で回転を実行すると、異なる回転が生成されます。それがまさにポイントですよね?
サムホセバー

このステートメントは、順序を変更すると常に同じローテーションになると述べています。1つの順序が間違った回転を生成している場合、他の順序も同様になります。つまり、それは解決策ではありません。
Syntac_

1
あなたは誤解しています。順序を変更しても、同じローテーションは発生しません。順序変更し、固有の回転から外部の回転に切り替えると、同じ回転になります。
サムホセバー

1
私はあなたの質問を理解していないと思います。GIFは、約50度回転Z(オブジェクト空間)、次に50度回転X(オブジェクト空間)、次に45度回転Y(オブジェクト空間)します。これは、45度Yワールド空間)、50度Xワールド空間)、50度Zワールド空間)の回転とまったく同じです。
サムホセバー

1

これがなぜ機能するのかを誰かが説明できるまで、答えとしてソリューションを提供します。

回転ベクトルに保存された角度を使用してクォータニオンを再構築し、クォータニオンを最終的な変換に適用するたびにレンダリングしました。

ただし、ワールド軸の周りに保持するには、すべてのフレームで四元数を保持し、角度の違いを使用してオブジェクトを回転するだけでした。

// To rotate an angle around X - note this is an additional rotation.
// If currently rotated 90, apply this function with angle of 90, total rotation = 180.
D3DXQUATERNION q;
D3DXQuaternionRotation(&q, D3DXVECTOR3(1.0f, 0.0f, 0.0f), fAngle);
m_qRotation *= q; 

//...

// When rendering rebuild world matrix
D3DXMATRIX mTemp;
D3DXMatrixIdentity(&m_mWorld);

// Scale
D3DXMatrixScaling(&mTemp, m_vScale.x, m_vScale.y, m_vScale.z);
m_mWorld *= mTemp;

// Rotate
D3DXMatrixRotationQuaternion(&mTemp, m_qRotation);
m_mWorld *= mTemp;

// Translation
D3DXMatrixTranslation(&mTemp, m_vPosition.x, m_vPosition.y, m_vPosition.z);
m_mWorld *= mTemp;

(読みやすさの詳細)

dsilva.viniciusはこの点に到達しようとしていたと思います。


1

回転の順序を保存する必要があります。

Rotating around x 90 then rotate around z 90 !=
Rotating around z 90 then rotate around x 90.

現在の回転行列を保存し、各回転が事前に乗算されます。


0

@ concept3dの回答に加えて、3つの外部回転行列を使用して、世界座標の軸を中心に回転できます。ウィキペディアからの引用:

外部回転は、固定座標系xyzの軸を中心に発生する要素回転です。XYZシステムは回転しますが、xyzは固定です。XYZオーバーラップxyzから始めて、3つの外部回転の合成を使用して、XYZの任意のターゲット方向に到達できます。オイラー角またはテイトブライアン角(α、β、γ)は、これらの元素回転の振幅です。たとえば、次のようにターゲットの方向に到達できます。

XYZシステムは、αだけz軸を中心に回転します。X軸は、X軸に対して角度αになります。

XYZシステムは、βだけx軸を中心に再び回転します。Z軸は、Z軸に対して角度βになります。

XYZシステムは、γだけz軸を中心に3回回転します。

回転行列を使用して、一連の外部回転を表すことができます。例えば、

R = Z(γ)Y(β)X(α)

列ベクトルを事前に乗算するために使用される場合、軸xyzの外部回転の構成を表します。

R = X(α)Y(β)Z(γ)

行ベクトルのポスト乗算に使用される場合、まったく同じ構成を表します。

したがって、必要なのは、固有の(またはローカル空間の)回転を使用して行うことに関連して、回転の順序を逆にすることです。@Syntacはzxy回転を要求したので、同じ結果を得るにはyxz外部回転を行う必要があります。コードは次のとおりです。

ここでマトリックス値の説明。

// Init things.
D3DXMATRIX *rotationMatrixX = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixY = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixZ = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix0 = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix1 = new D3DXMATRIX();

D3DXMatrixRotationX(rotationMatrixX, angleX);
D3DXMatrixRotationY(rotationMatrixY, angleY);
D3DXMatrixRotationZ(rotationMatrixZ, angleZ);

// yx extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix0, rotationMatrixY, rotationMatrixX);
// yxz extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix1, resultRotationMatrix0, rotationMatrixZ);

D3DXVECTOR4* originalVector = // Original value to be transformed;
D3DXVECTOR4* transformedVector = new D3DXVECTOR4();

// Applying matrix to the vector.
D3DXVec4Transform(transformedVector, originalVector, resultRotationMatrix1);

// Don't forget to clean memory!

複数のD3DXMATRIXマトリックスを再利用できるため、このコードは教訓的であり、最適ではありません。


1
申し訳ありませんが、これは正しくありません。行列/ベクトルの乗算は結合的です。これは、複合行列乗算とまったく同じです。
concept3d

あなたが正しいです。私は、外因性と内因性の回転の概念を混ぜています。
dsilva.vinicius

この答えを修正します。
dsilva.vinicius

答えは修正されました。
dsilva.vinicius
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.