いいえ、これはエンジンのバグや特定の回転表現のアーティファクトではありません(これらも発生する可能性がありますが、この効果は回転を表すすべてのシステムに適用され、クォータニオンが含まれます)。
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メソッドを使用するか、これらのベクトルから手動で直交行列を作成します)アップベクトルを制御することにより、ロールまたはツイストを制御します。
飛行/水泳の例では、これらの修正を徐々に適用していきます。急すぎると、視界が邪魔になります。代わりに、プレーヤーの現在のアップベクトルを使用して、ビューが水平になるまでフレームごとに垂直方向にヒントを与えることができます。ターン中にこれを適用すると、プレーヤーのコントロールがアイドル状態のときにカメラをひねるよりも吐き気を軽減できる場合があります。