2つの軸でオブジェクトを回転させているのに、なぜ3番目の軸を中心にねじれ続けるのですか?


38

この根本的な問題を抱える質問が頻繁に出てきますが、それらはすべて特定の機能やツールの詳細に追いついています。これが出てきたときにユーザーに紹介できる標準的な回答を作成する試みです-多くのアニメーションの例があります!:)


一人称カメラを作っているとしましょう。基本的な考え方は、左右に見えるようにヨーし、上下に見えるようにピッチすることです。そのため、次のようなコードを少し書きます(例としてUnityを使用):

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    // Yaw around the y axis using the player's horizontal input.        
    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f);

    // Pitch around the x axis using the player's vertical input.
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f);
}

または多分

// Construct a quaternion or a matrix representing incremental camera rotation.
Quaternion rotation = Quaternion.Euler(
                        -Input.GetAxis("Vertical") * speed,
                         Input.GetAxis("Horizontal") * speed,
                         0);

// Fold this change into the camera's current rotation.
transform.rotation *= rotation;

そして、それはほとんど機能しますが、時間が経つにつれて、ビューが曲がり始めます。カメラは、xとyだけで回転するように指示したのに、ロール軸(z)で回転しているようです!

横向きに傾斜した一人称カメラのアニメーション例

これは、カメラの前にあるオブジェクトを操作しようとしている場合にも発生する可能性があります-周りを見回したい地球儀だとします。

地球が横向きに傾いているアニメーションの例

同じ問題-しばらくすると、北極が左右にさまようようになります。2つの軸で入力を行っていますが、3番目の軸でこの混乱する回転を取得しています。そして、オブジェクトのローカル軸または世界のグローバル軸の周りにすべての回転を適用するかどうかが起こります。

多くのエンジンでは、インスペクターでもこれを見ることができます-世界でオブジェクトを回転させると、触れることさえしなかった軸で突然数字が変化します!

回転角の読み取り値の横にあるオブジェクトを操作するアニメーションの例。 ユーザーがその軸を操作しなかった場合でも、z角度は変化します。

それで、これはエンジンのバグですか?余分な回転を追加したくないプログラムにどのように伝えるのですか?

オイラー角と関係がありますか?代わりにクォータニオンまたは回転行列または基底ベクトルを使用する必要がありますか?



1
次の質問に対する答えも非常に役立ちました。gamedev.stackexchange.com/questions/123535/...
トラヴィスPettry

回答:


51

いいえ、これはエンジンのバグや特定の回転表現のアーティファクトはありません(これらも発生する可能性がありますが、この効果は回転を表すすべてのシステムに適用され、クォータニオンが含まれます)。

3次元空間で回転がどのように機能するかについての本当の事実を発見しました。これは、翻訳などの他の変換に関する直感からはずれています。

異なる順序で回転を適用すると異なる結果が得られることを示すアニメーションの例

複数の軸で回転を構成する場合、得られる結果は、各軸に適用した合計/正味の値だけではありません(変換に期待されるように)。次の回転が適用される軸(オブジェクトのローカル軸を中心に回転する場合)、またはオブジェクトと軸の関係(世界を中心に回転する場合)が移動するたびに、回転を適用する順序によって結果が変わります軸)。

時間の経過に伴う軸の関係の変化は、各軸が何を「想定」するかについての直感を混乱させる可能性があります。特に、ヨーとピッチ回転の特定の組み合わせは、ロール回転と同じ結果をもたらします!

ローカルピッチヨーピッチのシーケンスを示すアニメーション例では、単一のローカルロールと同じ出力が得られます

各ステップが、要求した軸を中心に正しく回転していることを確認できます-表記にエンジンの不具合やアーティファクトが入力を妨害したり推測したりすることはありません-回転の球面(または超球面/四元数)の性質は単に変換周りに」。小さな回転の場合、これらは局所的に直交する場合がありますが、積み重なっているため、グローバルに直交していないことがわかります。

これは、上記のような90度の回転に対して最も劇的で明確ですが、質問で示されているように、さまよう軸も多くの小さな回転にわたってクリープします。

それで、私たちはそれについて何をしますか?

既にピッチヨー回転システムを使用している場合、不要なロールを除去する最も簡単な方法の1つは、回転の1つを変更して、オブジェクトのローカル軸ではなくグローバルまたは親変換軸で動作することです。この方法では、2つの間で相互汚染を取得することはできません-1つの軸は完全に制御されたままです。

上記の例でロールになった同じピッチ-ヨー-ピッチのシーケンスがありますが、オブジェクトの代わりにグローバルY軸の周りにヨーを適用します

ロールの問題がない、ローカルピッチとグローバルヨーで回転するマグカップのアニメーション例グローバルヨーを使用した一人称カメラのアニメーション例

したがって、マントラ「ローカルにピッチ、グローバルにヨー」で一人称カメラを修正できます。

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f, Space.World);
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f, Space.Self);
}

乗算を使用して回転を合成している場合、乗算のいずれかの左/右の順序を反転させて同じ効果を得ます。

// Yaw happens "over" the current rotation, in global coordinates.
Quaternion yaw = Quaternion.Euler(0f, Input.GetAxis("Horizontal") * speed, 0f);
transform.rotation =  yaw * transform.rotation; // yaw on the left.

// Pitch happens "under" the current rotation, in local coordinates.
Quaternion pitch = Quaternion.Euler(-Input.GetAxis("Vertical") * speed, 0f, 0f);
transform.rotation = transform.rotation * pitch; // pitch on the right.

(特定の順序は環境内の乗算規則によって異なりますが、左=よりグローバル/右=よりローカルが一般的な選択です)

これは、必要な正味の総ヨーと総ピッチをフロート変数として保存し、常に正味の結果を一度に適用し、これらの角度のみから単一の新しい方向のクォータニオンまたは行列を構築することと同等です(totalPitchクランプを保持した場合):

// Construct a new orientation quaternion or matrix from Euler/Tait-Bryan angles.
var newRotation = Quaternion.Euler(totalPitch, totalYaw, 0f);
// Apply it to our object.
transform.rotation = newRotation;

または同等に...

// Form a view vector using total pitch & yaw as spherical coordinates.
Vector3 forward = new Vector3(
                    Mathf.cos(totalPitch) * Mathf.sin(totalYaw),
                    Mathf.sin(totalPitch),
                    Mathf.cos(totalPitch) * Mathf.cos(totalYaw));

// Construct an orientation or view matrix pointing in that direction.
var newRotation = Quaternion.LookRotation(forward, new Vector3(0, 1, 0));
// Apply it to our object.
transform.rotation = newRotation;

このグローバル/ローカル分割を使用すると、独立した軸のセットに適用されるため、回転が互いに影響を及ぼし合ったり合成したりする機会がありません。

同じアイデアは、それが回転したい世界のオブジェクトである場合に役立ちます。地球のような例では、それを反転させてヨーをローカルに適用し(常に極を中心に回転する)、グローバルにピッチする(オーストラリアに向かって/からではなく、ビューに向かって/から離れるように)したいことがよくあります。 、それが指しているところはどこでも...)

振る舞いの良い回転を示すグローブのアニメーション例

制限事項

このグローバル/ローカルハイブリッド戦略は、常に正しい修正とは限りません。たとえば、3Dの飛行/水泳を使用するゲームでは、真上/真下を向いても完全に制御できるようにしたい場合があります。しかし、この設定ではジンバルロックかかります -ヨー軸(グローバルアップ)はロール軸(ローカルフォワード)と平行になり、ねじれずに左右を見る方法はありません。

このような場合に代わりにできることは、上記の質問で始めたように純粋なローカル回転を使用することです(したがって、コントロールは見ているところに関係なく同じように感じます)。私たちはそれを修正します。

たとえば、ローカル回転を使用して「前方」ベクトルを更新し、その前方ベクトルを参照「上方」ベクトルとともに使用して、最終的な方向を構築できます。(たとえば、UnityのQuaternion.LookRotationメソッドを使用するか、これらのベクトルから手動で直交行列を作成します)アップベクトルを制御することにより、ロールまたはツイストを制御します。

飛行/水泳の例では、これらの修正を徐々に適用していきます。急すぎると、視界が邪魔になります。代わりに、プレーヤーの現在のアップベクトルを使用して、ビューが水平になるまでフレームごとに垂直方向にヒントを与えることができます。ターン中にこれを適用すると、プレーヤーのコントロールがアイドル状態のときにカメラをひねるよりも吐き気を軽減できる場合があります。


1
どうやってgifを作ったのか聞いてもいいですか?見栄えがいい。
バリント

1
@Bálint私はLICEcapと呼ばれる無料のプログラムを使用します。これにより、画面の一部をgifに記録できます。次に、アニメーションをトリミングし、キャプションテキストを追加し、Photoshopを使用してgifを圧縮します。
DMGregory

この回答はUnityのみですか?Webにはもっと一般的な答えがありますか?
posfan12

2
@ posfan12このサンプルコードでは、具体性のためにUnity構文を使用していますが、関係する状況と数学は全面的に等しく適用されます。サンプルを環境に適用するのに苦労していませんか?
DMGregory

2
数学はすでに上にあります。あなたの編集から判断すると、あなたはそれの間違った部分をマッシュアップしているだけです。ここで定義されているベクタータイプを使用しているようです。これには、上記の一連の角度から回転行列を作成する方法が含まれます。単位行列から始めて呼び出しますTCVector::calcRotationMatrix(totalPitch, totalYaw, identity)-これは上記の「すべて一度にオイラー」の例と同等です。
DMGregory

1

子育てと回転機能の16時間後に笑。

私は、コードに空の、基本的には円を描くようにし、またそれが回転するときに前面を反転させるようにしたかっただけです。

または言い換えれば、目標はロールに影響を与えずにヨーとピッチを回転させることです。

ここに私のアプリケーションで実際に働いたいくつかがあります

Unityの場合:

  1. 空を作成します。ForYawと呼びます。
  2. ForPitchという名前の子を空にします。
  3. 最後に、立方体を作成し、z = 5(前方)に移動します。これで、回避しようとしているものを確認できます。これにより、キューブがねじれます(ヨーイングとピッチング中の偶発的な「ロール」)

Visual Studioでスクリプトを作成し、セットアップを実行して、Updateでこれを実行します。

objectForYaw.Rotate(objectForYaw.up, 1f, Space.World);
    objectForPitch.Rotate(objectForPitch.right, 3f, Space.World);

    //this will work on the pitch object as well
    //objectForPitch.RotateAround
    //    (objectForPitch.position, objectForPitch.right, 3f);

子育ての順序を変えようとしたときに、すべてが壊れてしまったことに注意してください。または、子ピッチオブジェクトのSpace.Worldを変更した場合(親YawはSpace.Selfを介して回転させても構いません)。紛らわしい。なぜローカル軸を取り、ワールド空間に適用しなければならないのか、Space.Selfがすべてを台無しにしなければならない理由を明確に説明できます。


これが質問に答えることを意図したものなのか、あなたが書いたコードがこのように動作する理由を理解する助けを求めているのかどうかは、私には明らかではありません。後者の場合は、新しい質問を投稿し、必要に応じてこのページにリンクしてコンテキストを確認してください。ヒントとして、はとme.Rotate(me.right, angle, Space.World)同じme.Rotate(Vector3.right, angle, Space.Selfであり、ネストされた変換は、受け入れられた回答の「ローカルにピッチ、グローバルにヨー」の例と同等です。
DMGregory

両方の少し。部分的に答えを共有することもできます(たとえば、ここに正確にどのように機能したかを示します)。また、質問を紹介します。そのヒントは私に考えさせられました。どうもありがとう!
ジェ

驚くべきことに、これは支持されませんでした。何時間も包括的な答えを読んだ後、教育はまだ機能しませんでしたが、一般的なVector3.right軸の代わりにオブジェクト回転軸を使用するという簡単な答えは、オブジェクトの回転を固定するために必要なすべてです。私が探していた信じられないほどのものは、ここに0票で、そして多くの、同様の質問のGoogleの2ページ目に埋葬されました。
user4779
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.