MRが楽しい

MRやVRについて学習したことを書き残す

Unityのジンバルロックの動きを理解して制御する

本日は Unity の技術調査枠です。
Unityのジンバルロックの動きを理解して制御してみたので記事にしてみます。
f:id:bluebirdofoz:20210916040718j:plain

ジンバルロック

複数の軸の回転を組み合わせたとき、1つの回転軸の自由度が失われてしまう事象のことです。Unity のトランスフォームの回転では X 軸の回転に 90 度または -90 度を設定したときに発生します。

発生事例

以下の Plane オブジェクトの Rotation を変化させてジンバルロックを発生させてみます。
・X:0, Y:0, Z:0
f:id:bluebirdofoz:20210916040728j:plain

Rotation に X:90, Y:0, Z:0 を設定します。
・X:90, Y:0, Z:0
f:id:bluebirdofoz:20210916040737j:plain

この後、Y 軸と Z 軸の値をそれぞれ変更してみます。
すると異なる回転軸を設定しているはずが、回転方向が同じになってしまいます。
この1つの回転軸の自由度が失われてしまう事象がジンバルロックです。
・X:90, Y:90, Z:0
f:id:bluebirdofoz:20210916040746j:plain

・X:90, Y:0, Z:-90
f:id:bluebirdofoz:20210916040757j:plain

ジンバルロックの動き

ジンバルロックを視覚的に理解するには以下の動画が分かりやすいです。
赤のリングを 90 度の回転で固定したとき、青のリングと緑のリングの回転方向が同じになります。
youtu.be

Unityでの発生の仕組み

オブジェクトのローカル座標で回転軸を考えた時、Unityでは、Y軸 -> X軸 -> Z軸 の順に回転が処理されます。
(ワールド座標で回転軸を捉えた場合は Z軸 -> X軸 -> Y軸 の順になります)
先ほどの動画では赤のリングがX軸にあたり、このため、X軸を 90 度または -90 度に設定したときジンバルロックが発生します。

発生事例では以下の順で処理が行われていたことになります。

X:90, Y:90, Z:0の例

初めにオブジェクトのローカル座標のY軸方向に 90 度回転する計算が行われる。
f:id:bluebirdofoz:20210916040815j:plain

次にオブジェクトのローカル座標のX軸方向に 90 度回転してこの方向になる。
f:id:bluebirdofoz:20210916040825j:plain

X:90, Y:0, Z:-90の例

初めにオブジェクトのローカル座標のX軸方向に 90 度回転する計算が行われる。
f:id:bluebirdofoz:20210916040834j:plain

その次にオブジェクトのローカル座標のZ軸方向に -90 度回転してこの方向になる。
f:id:bluebirdofoz:20210916040842j:plain

ジンバルロックを回避する

上記の通り、内部の処理では回転の順番が Y軸 -> X軸 -> Z軸 で行われてしまうことがジンバルロックが発生する原因です。
つまり、回転の順番を制御すればジンバルロックにより回転軸の自由度が失われてしまう事象を回避して意図通りの回転が行えます。

サンプルスクリプト

以下の2種類の回転を行うサンプルスクリプトを作成しました。
ChangeSameTime 関数は X:90 度と Y:90 度を同時に設定してオブジェクトを回転させます。
ChangeOrder 関数は順に X:90 度を設定して次に Y:90 度を設定してオブジェクトを回転させます。
・RotationTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotationTest : MonoBehaviour
{
    [ContextMenu("ChangeSame")]
    public void ChangeSameTime()
    {
        this.transform.localRotation = Quaternion.Euler(90, 90, 0);
    }

    [ContextMenu("ChangeOrder")]
    public void ChangeOrder()
    {
        this.transform.localRotation = Quaternion.Euler(90, 0, 0);
        this.transform.localRotation *= Quaternion.Euler(0, 90, 0);
    }
}

f:id:bluebirdofoz:20210916040906j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20210916040926j:plain

ChangeSameTime 関数を実行すると、ジンバルロックが発生したときと同じ方向になります。
また、エディターでの表記上は X:90, Y:0, Z:-90 と表示されます。
f:id:bluebirdofoz:20210916040936j:plain

方向をリセットするため、シーンを一旦停止して再び開始します。
f:id:bluebirdofoz:20210916041004j:plain

ChangeOrder 関数を実行すると、ジンバルロックを回避して異なる方向を向けることができました。
エディターでの表記上は X:0, Y:90, Z:90 と表示されます。
f:id:bluebirdofoz:20210916041013j:plain

参考ページ

qiita.com