この記事はUnity #3 Advent Calendar 2018 20日目の記事です。
こんにちは。
今回はジンバルロックに焦点を当てた記事を書きました。
[2019/6/16]加筆修正しました。
#はじめに
Unityを使っていると回転の話で度々Quaternion
が出現します。下記画像の右上にあるメニューからDebug
モードにするとrotation
が4変数になりますがそれがQuaternion
の生の値です。
通常時のInspectorビュー | Debugモード時のInspectorビュー |
---|---|
Quaternion
は、オイラー角表現で発生していたジンバルロックを回避できるというメリットがあります。しかしジンバルロックとは具体的にどのような現象なのでしょうか?
今更rotation
の話をするのは二番煎じ感はありますが自分の中で把握できていなかったので、それについて調べたものをできる限り分かりやすく記したいと思います。
- GameObjectの回転が意図通りにならない方
- ジンバルロックを話では聞いたことあるが、具体的にどんな現象なのか分からない方
- どのような条件でジンバルロックが発生するか知りたい方
上記向けになればいいなと思います。すでに理解していらっしゃる方は、間違いなどあればご指摘いただけると嬉しいです。
簡単に実験 (30秒!)
私が思う最も簡単にジンバルロックを発生させる方法はこの通りです。
- 適当なGameObject(Cubeなど)を作成
- インスペクタのTransformコンポーネントにある
rotation
のx
を90
に設定 - 同じく
rotation
のy,z
を動かす
y
を変化させてもz
を変化させても同じ軸周りの回転になっていることが分かります。これがジンバルロックの簡単な再現となります。以下のgifアニメーションにその光景を示します。
gifアニメでは分かりやすさのために、Cubeの代わりにFayeというモデルを動かしております。また、空間に固定された座標軸を細い線、Fayeに固定された座標軸を太い線で示しています。
ジンバルロックとは?
オイラー角表現
において、2つの回転軸が同じ向き
になってしまったときに回転の自由度が1つ落ちてしまう
ことです。
-
オイラー角表現
: Unityのtransform.rotation
と言い換えておけばいいと思います。 -
2つの回転軸が同じ向き
: 先ほどの実験結果で見た「y
を変化させてもz
を変化させても同じ軸周りの回転」となった現象における、y軸とz軸のこと -
自由度が1つ落ちてしまう
: 3軸ではなく2軸の回転表現に成り下がることです。回転に必要な3つの数値 $(x,y,z)$ のうち1つの変数が意味を失っているとも言えそうです。
もう一つ分かりやすい動画を当記事でも貼り付けさせていただきます。
0:59〜1:14が分かりやすいです。赤いリングが90
度回転したとき、青いリングと緑色のリングの回転軸は等しくなっています。これがジンバルロックです。
もう一つの簡単な実験
先ほどの現象をさらに考察するために、先ほど作成したGameObjectに以下のコードをアタッチします。
using UnityEngine;
public class QuatByEulerAngle : MonoBehaviour {
[ContextMenu("Experiment")]
public void Experiment()
{
transform.localRotation = Quaternion.Euler(90, 90, 0);
}
[ContextMenu("Experiment2")]
public void Experiment2()
{
transform.localRotation = Quaternion.Euler(90, 0, -90);
}
[ContextMenu("Experiment3")]
public void Experiment3()
{
transform.localRotation = Quaternion.Euler(90, 0, 90);
}
}
Experiment()
,Experiment2()
,Experiment3()
という3つのメソッドがあり、それぞれ $(x,y,z)=(90,90,0),(90,0,-90),(90,0,90)$度のオイラー角が表す回転を表しています。このメソッドを呼び出すと一体どうなるでしょうか。それぞれ異なる数値なので3つとも異なる位置に回転するに違いないと思うかもしれません。また、回転でなにかと便利なQuaternion
を利用しているからジンバルロックは発生しないと思うかもしれません。
それぞれの回転がどのような回転なのか想像してから実行してみるといいかもしれませんね。
結果は次のgifアニメのようになります。
Experiment()
とExperiment2()
のどちらを実行しても向きは同じで、Experiment3()
ではそれらとは反対側に向きました。数値が違うのに向きが同じとは驚くべき結果であり、予想と反した方々も少なくないのではと思います。
y
,z
を変えたときに同じ軸周りの回転をしており、回転の自由度が1つ落ちていたので、ジンバルロックが発生していました。Quaternion
を使っていても関係ありませんでしたね。
意図していたであろう回転
先ほどは予想だにしない挙動を見せましたが、Experiment()
だったら次のように考えた人もいたのではないでしょうか。
Experiment()
は $(x,y,z)=(90,90,0)$ が表現する回転なのだから、x
を90
度回すとFayeが前方向に倒れるような回転をして、その次にy
を90
度回すのだからFayeの中心軸周りに90
度回るだろう。
しかし結論的にはx,y,z
の順番では回転してくれません。もしそのように回転させたい場合は次のようにする必要があります。
transform.localRotation *= Quaternion.Euler(90, 0, 0);
transform.localRotation *= Quaternion.Euler(0, 90, 0);
こうすると先ほどの意図通りになります。
Experiment2()
とExperiment3()
も同様に次のようにすると意図通りの回転になります。
transform.localRotation *= Quaternion.Euler(90, 0, 0);
transform.localRotation *= Quaternion.Euler(0, 0, -90);
transform.localRotation *= Quaternion.Euler(90, 0, 0);
transform.localRotation *= Quaternion.Euler(0, 0, 90);
なぜ意図した回転と違ったのか
先ほどの数値(x,y,z)
がx軸
->y軸
->z軸
の順番で回転すると思っていたことが間違いだからです。以下の引用はQuaternion.Euler(x,y,z)
のリファレンスですが、オイラー角(x,y,z)
が入力されたとき、z軸回転
→x軸回転
→y軸回転
の順番で回転の計算をしていると書かれています。
Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).
https://docs.unity3d.com/jp/460/ScriptReference/Quaternion.Euler.html
このようにオイラー角は回す順序が大事で、回す順序が違うとたとえ数値が同じでも違った方位になります。
本当はx軸
->y軸
->z軸
で回したいのにいちいちz軸
→x軸
→y軸
で回転したい場合の角度を考え直すことはかなり大変ですね。このように、回転順序にz軸
→x軸
→y軸
という縛りが効いているオイラー角の定義は人間の直感に反することがあるので、もし複雑な回転を考えている場合はQuaternion.Euler(x,y,z)
を利用する前に、transform.LookAt()
,Quaternion.AngleAxis()
などといった別の回転系の関数が使えないか検討することが望ましいかと思います。
さらに例を挙げるとQuaternion.Euler(90,45,45)
はQuaternion.Euler(90,0,0)
と等しいですし、Quaternion.Euler(80,45,45)
はどこに45度らしさが出ているのかちょっと分かりづらいです。(以下のスクショを見てください)
ただ一つ言えることは、Quaternion.Euler(90,0,0)
,Quaternion.Euler(0,45,0)
,Quaternion.Euler(0,0,30)
などのように、どれか一つの成分のみが非ゼロである場合は想像通りに回るということです。つまりGameObjectに固定された座標系(今までの図の太線の軸)の周りで回転してくれます。
またジンバルロックは、2番目に回す軸周りの角度が90
度をとるときに発生することが数学的に証明できます。
#まとめ
- 何かしらの物体の向きをAからBに変化させるとき、オイラー角だと数値を一意に定めることができない
- Unityではオイラー角による回転は、
z軸回転
->x軸回転
->y軸回転
の順に計算される - ジンバルロックとは2番目に回す角度が
-90,90
度のときに、1番目と3番目に回す軸が同じになってしまう現象。すなわち回転の自由度が1つ落ちる - Unityでは
rotation.x
が-90,90
度のときにジンバルロックが発生し、回転の自由度が落ちる - ワールド座標系のある1軸周り、またはローカル座標系のある1軸周りといった回転をしたい場合は
Quaternion.Euler(x,y,z)
が楽 - 任意の軸周りの回転を考える場合は
transform.LookAt()
,Quaternion.AngleAxis()
などが使えないか検討するのが吉
#参考