Help us understand the problem. What is going on with this article?

Unityのジンバルロックを30秒で体感する

More than 1 year has passed since last update.

この記事はUnity #3 Advent Calendar 2018 20日目の記事です。

こんにちは。
今回はジンバルロックに焦点を当てた記事を書きました。

[2019/6/16]加筆修正しました。

はじめに

Unityを使っていると回転の話で度々Quaternionが出現します。インスペクタをDebugモードにするとrotationが4変数になりますがそれがQuaternionの生の値です。

右上のメニューからDebugを選択すると・・・ 各CompomentがDebugモードになる
スクリーンショット 2018-12-22 17.25.35.png スクリーンショット 2018-12-22 17.25.46.png

Quaternionは、オイラー角表現で発生していたジンバルロックを回避できるというメリットがあります。しかしジンバルロックとは具体的にどのような現象なのでしょうか?

今更rotationの話をするのは二番煎じ感はありますが自分の中で把握できていなかったので、それについて調べたものをできる限り分かりやすく記したいと思います。

  • GameObjectの回転が意図通りにならない方
  • ジンバルロックを話では聞いたことあるが、具体的にどんな現象なのか分からない方
  • どのような条件でジンバルロックが発生するか知りたい方

上記向けになればいいなと思います。すでに理解していらっしゃる方は、間違いなどあればご指摘いただけると嬉しいです。

簡単に実験 (30秒!)

私が思う最も簡単にジンバルロックを発生させる方法はこの通りです。

  1. 適当なGameObject(Cubeなど)を作成
  2. インスペクタのTransformコンポーネントにあるrotationx90に設定
  3. 同じくrotationy,zを動かす

yを変化させてもzを変化させても同じ軸周りの回転になっていることが分かります。これがジンバルロックの簡単な再現となります。以下のgifアニメーションにその光景を示します。
gifアニメでは分かりやすさのために、Cubeの代わりにFayeというモデルを動かしております。また、空間に固定された座標軸を細い線、Fayeに固定された座標軸を太い線で示しています。

ezgif-1-71538e82118e.gif

ジンバルロックとは?

オイラー角表現において、2つの回転軸が同じ向きになってしまったときに回転の自由度が1つ落ちてしまうことです。

  • オイラー角表現: Unityのtransform.rotationと言い換えておけばいいと思います。
  • 2つの回転軸が同じ向き: 先ほどの実験結果で見た「yを変化させてもzを変化させても同じ軸周りの回転」となった現象における、y軸とz軸のこと
  • 自由度が1つ落ちてしまう: 3軸ではなく2軸の回転表現に成り下がることです。回転に必要な3つの数値 $(x,y,z)$ のうち1つの変数が意味を失っているとも言えそうです。

もう一つ分かりやすい動画を当記事でも貼り付けさせていただきます。

https://www.youtube.com/watch?v=zc8b2Jo7mno

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アニメのようになります。

インスペクタからQE.gif

Experiment()Experiment2()のどちらを実行しても向きは同じで、Experiment3()ではそれらとは反対側に向きました。数値が違うのに向きが同じとは驚くべき結果であり、予想と反した方々も少なくないのではと思います。
y,zを変えたときに同じ軸周りの回転をしており、回転の自由度が1つ落ちていたので、ジンバルロックが発生していました。Quaternionを使っていても関係ありませんでしたね。

意図していたであろう回転

先ほどは予想だにしない挙動を見せましたが、Experiment()だったら次のように考えた人もいたのではないでしょうか。

Experiment() $(x,y,z)=(90,90,0)$ が表現する回転なのだから、x90度回すとFayeが前方向に倒れるような回転をして、その次にy90度回すのだからFayeの中心軸周りに90度回るだろう。

しかし結論的にはx,y,zの順番では回転してくれません。もしそのように回転させたい場合は次のようにする必要があります。

transform.localRotation *= Quaternion.Euler(90, 0, 0);
transform.localRotation *= Quaternion.Euler(0, 90, 0);

こうすると先ほどの意図通りになります。

ezgif-1-99da0bd69489.gif

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度らしさが出ているのかちょっと分かりづらいです。(以下のスクショを見てください)

スクリーンショット 2018-12-21 00.17.59.png

ただ一つ言えることは、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()などが使えないか検討するのが吉

参考

fullmated
まったりしてます
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした