土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

#unity ジンバルロックをゼロから理解する

 3Dオブジェクトの回転についてネットで調べると、決まって「ジンバルロック」という用語と、その説明にぶつかります。この「ジンバルロック」がどういう物なのか、長い事ピンと来ていなかったのですが、ようやく腑に落ちたのでまとめておきます。

 以下、土屋の理解の範囲での説明になります。間違いなどありましたらコメント頂ければと思います(本文の修正はお約束出来ないけど、コメントを含めて記事という事で)。

実験その①:ジンバルロックを実際に起こしてみる。

 百聞は一見にしかずということで、まずはUnityを使ってジンバルロックを実際に確認してみましょう。Unityがあると、こういう確認が簡単に出来て良いですね。

 Unityで空のプロジェクトを作成し、シーンにCubeを配置します。分かりやすさのためにここでは板状に変型していますが、これは必須ではないです。

 オブジェクトのtransform.rotationを見て下さい。rotatinはオブジェクトの回転角度を格納しています。初期状態ではxyz全てにゼロが設定されています。

 rotationのxの値を変えてみます。名称の部分を左右にドラッグすると値が変えられます。ちなみに、SHIFTキーを押しながらドラッグすると数字の増減速度が上がります*1

 xの値を増減させると、オブジェクトがX軸(赤色の矢印)を基準に回転します。これを「X軸周りで回転する」と言います。

 xをゼロに戻してから、y/zについても同じ事をしてみます。

 yを弄るとY軸(緑色の矢印)周りで、zを弄るとZ軸(青色の矢印)周りでそれぞれ回転します。ここまでは、特におかしな事はありませんね。

 それでは次に、予めxに90.0を設定した状態で、先ほどと同じようにy/zについて値を増減してみます。

 すると、xを90.0に設定した状態では、y/zの値のどちらを増減させても、常にY軸(緑色の矢印)周りで回転してしまいます。直感に反する挙動で、動かしていて不思議な気持ちになれるので、是非、実際にunityで試してみてください。

 理由は後回しにして、ここで起きているのは「Y軸がZ軸と同じ向きになってしまっている」という物です。実はxが90度に設定されている時は、yとzはどちらを動かしても、オブジェクトはY軸周りでしか回転しないのです*2。そして、このような現象のことを「ジンバルロック」と呼ぶのです。

オイラー角による回転

 ここで一旦ジンバルロックから離れて「オブジェクトの向き」という物について改めて考えてみます。
 実は、「オブジェクトの向き」を記述する方法というのは幾つかありまして、代表的な物は以下の3つです。

・回転行列
・オイラー角
・クォータニオン

 それぞれ、要素数や計算量などにトレードオフの関係があります。その中で、必要な要素数が最も少なく、かつ値の見た目が人間の直感に近いのが「オイラー角(Euler angles)」です*3

 オイラー角では、xyzの3要素で向きを記述します。先ほどのインスペクタの表記がこれに対応しています。つまり、インスペクタのrotationの3つの角度は、オイラー角におけるオブジェクトの向きを表しているのです*4

 オイラー角では、3つの要素をxyzそれぞれの軸に対応させ、それぞれの軸の周りで、要素が示す角度の分だけオブジェクトを回転させます。この処理によって、オブジェクトを任意の向きに回転することが出来ます。

実験その②:回転する軸の順番を変える

 オイラー角による回転のルールは、シンプルで直感的に思えます。が、実はこれだけではオブジェクトの向きを示すにはルールが不足しています。オイラー角で回転させる時は、先にxyzの3つの軸をどの順番で回転させるかを決めておく必要があります。

 何故回転する軸の順序を決める必要があるのでしょうか? これを確認するために、2つ目の実験をしてみましょう。
 オブジェクトの向きが分かりやすいように、ここからは板の代わりにユニティちゃんを回転させることにします。

 ここでは、ユニティちゃんがrotation(0,0,0)の時、頭(上)はZ方向、顔(前)はY方向に向いている物とします。何故初期状態でユニティちゃんを仰向けにしているのかと言いますと、この方が説明がしやすい為です。
 なお、このようにユニティちゃんを配置するのはちょっと手間がかかるので、皆さんは引き続き板オブジェクトを使うか、現実空間で板チョコなりフィギュアなり、向きが分かる物を実際に動かして確認して下さい。

 Unity上で実験する場合、rotationの値は直接操作せず、回転ツールを使って回転します。これは、rotationの値を直接操作すると、望む結果を得られない為です(これについては後述します)。下図に丸で囲んだアイコンをクリックして回転ツールを有効にしてからオブジェクトをクリックします(Unity2021.3の場合)。

 また、回転角度は、回転前の座標系(この場合はグローバル空間の座標系)を基準に設定します。回転ツールのハンドル(回転に使うUI)の基準が"Global"になっている事を確認して下さい("Local"だと望む結果になりません)。
 
 準備(あるいは板チョコの用意)は出来ましたか? それではオイラー角「X=90度、Y=45度、Z=0度」について、軸の順序を変えて回転させてみます。

実験②ーA:X軸→Y軸の順に回転

 改めて元の状態はこうです。

 まず、X軸周りで90度回転させます。先ほど説明した通り、rotationの値を増減するのではなく回転ツールのギズモ(球形のUI)をドラッグして角度を変えて下さい。

 オブジェクトが90度回転し、ユニティちゃんが上下逆さまで後ろを向きました。

 次に、Y軸周りで45度回転させます。

 ユニティちゃんの体がすこしこちらに向く形になりました。Zは0なので、Z軸周りの回転はありません。この状態を覚えておきましょう。

実験②ーB:Y軸→X軸の順に回転

 ユニティちゃんを元の状態に戻して、今度は先にY軸周りで回転し、その後でX軸の周りで回転してみます。

 くどいですが、元の状態はこうです。

 まず、Y軸で45度回転させます。

 ユニティは仰向けのまま回転して、頭の方が少しこちら側に近づきました。

 次に、X軸を90度回転させます。

 ユニティちゃんの体は真横になり、頭は斜め下に向いています。

 AとBでは、オブジェクトの最終的な向きが大きく異なっていますよね。これはつまり、同じ「X=90度、Y=45度、Z=0度」でも、回転させる軸の順序が変わると結果が変わる事を示しています。

回転の順番(ローテーションオーダー)

 実験②において、同じオイラー角で回転しても、回転させる軸の順によって結果が異なる事が示されました。これは、ある軸周りで回転させると、それ以前の軸周りで回転させていた分が一緒に回ってしまうからです。ですから、回転する軸の順序が変わると、結果も変わってしまうのです。

 これに対処するため、オイラー角では3つの軸をどの順序で回転するかを予め決めておく必要があります。これを便宜上「ローテーションオーダー」と呼ぶ事にします*5

 ローテーションオーダー、すなわちX、Y、Zの順序の組み合わせの種類は、全部で12通りあります*6。12通りのどれを使うのが正しいのかという事は無く、処理系全体で同じローテーションオーダーを採用していれば問題ありません。

 UnityではZ-X-Yの順のローテーションオーダーが採用されています*7。実験②の際に「rotationの値を直接操作すると望む結果を得られない」と書いたのは、直接操作した場合は常にZXYオーダーで回転してしまうからだったのです*8

実験その③:ZXYオーダーでの回転

 ローテーションオーダーが決まっていれば、オイラー角の回転は、オーダーで決められた順番に軸周りを回転させるだけで実現できます。

 ところが、このオイラー角による回転には、回避不能な現象があります。それが、実験①で確認した「X軸周りで90度回転している時、Z軸とY軸周りの回転が同じになってしまう」という物で、この現象を「ジンバルロック」と呼びます。

 何故ジンバルロックが発生するのかを知るために、今度はオイラー角「X=マイナス90度、Y=マイナス45度、Z=45度」で回転してみます。ローテーションオーダーはUnityに準じてZXYオーダーとします。今度はrotationの値を直接操作する必要があります(逆に、回転ツールでは期待した結果が得られません)。

 再びユニティちゃんに、初期状態に戻ってもらいます。今回はrotationを操作するので、画面にプロパティを入れ込んでいます。

 ZXYオーダーなので、まずZ軸周りで45度回転します。rotationの値を操作した場合は常に回転前の座標系(この場合はグローバル空間座標系)で回転します。

 ユニティちゃんが寝転がったまま左半身を持ち上げました。

 次にX軸周りでマイナス90度回転します。

 ユニティちゃんが起き上がりました。ここで、ユニティちゃんの頭の向きに注目して下さい*9。初期状態ではZ軸方向に向いていたユニティちゃんの頭が、今は上方向、つまりY軸(緑色)に向いています。
 また、X軸周りで回転した結果、最初にZ軸周りで回転した45°の分が、今はY軸周りでの45°回転に置き換えられています。

 この状態で、Y軸周りでマイナス45度回転してみましょう

 Y軸周りでマイナス45度回転した事により、Z軸周りで回転した時の分(45°)が相殺され、ユニティちゃんはまっすぐZ軸の負方向に向いています。これは、初期状態からXだけをマイナス90°にした時と同じ結果です(これも実際に試してみてください)。X軸周りで90°回転している時は、Y軸周りとZ軸周りの回転が、同じ軸周りでの回転として扱われてしまうのです。

ジンバルロック

 実験その③で確認した事をまとめましょう。
 ZXYオーダーにおいてオブジェクトをX軸周りで90°回転した時*10、Y軸周りとZ軸周りの回転は、同じ軸周りでの回転として扱われます。このようになる現象の事を「ジンバルロック」と呼びます。

 これはつまり、回転に用いる情報としてXYZの3軸分を設定しているにもかかわらず、ジンバルロックが起きている時にはそのうちの2軸(X軸とY軸)分しか回転出来ないという事です。言い換えると、X軸周りで90度回転している時、どう頑張ってもZ軸周りでは回転させられないのです。
 3つの軸を回転したいのに、(結果的に)2つの軸でしか回転出来ないので、これを「自由度が3から2になる(自由度が1落ちる)」と言います。

 この特異な現象は「3つの角度を順番に適用して物体を回転する」というオイラー角による回転のルールの適用結果として現れる物です。そして、このような現象を「ジンバルロック」と言うわけです(ようやく結論に到達した!)。

ジンバルロックが問題になる場合

 オイラー角で回転でオブジェクトをアニメーションさせようとした時、ジンバルロックが問題になります。物体をオイラー角で回転させる場合は、このジンバルロックを考慮しないと必要な回転が実現できません。

 unityで言うと「オブジェクトを(ローカル座標空間で)X軸90度回転させた後、X軸は90度のまま、Z軸周りで90度回転させる*11」というアニメーションをしたい場合、オイラー角の回転では、そのままでは実現できません。実際に、インスペクタのrotationの値をドラッグで動かして試してみてください。

 ジンバルロックはオイラー角の持つ性質なので回避できません。ではどうするのかというと、オイラー角以外の回転方法を使う事になります。通常はクォータニオンを使う事になります。クォータニオンではジンバルロックが起きません。ただ、クォータニオンは非常に複雑な概念でして、実際の値を見ても直感的に向きを把握することが出来ません*12。通常、ユーザーが直接値を操作する事は無いでしょう。

 実はUnityの内部では、オブジェクトのrotationはオイラー角ではなくクォータニオンで格納しています。クォータニオンの値は直感的では無いため、インスペクタ上にはオイラー角に変換してxyz要素として表示し、ユーザーが値を更新した時はその値を再びクォータニオンに戻して内部で管理しています。その為、インスペクタ上のrotationを直接操作する場合、ジンバルロックが発生します。

終わりに

 ジンバルロックがどういう物なのか把握出来ましたか? 土屋も今回の記事を書いてようやく「これがジンバルロックか」という気分になれました。それでは。

参考リンク

docs.unity3d.com
 UnityのRotationにおける、オイラー角の定義が記載されています。以下引用。

オイラー角の回転は、3 つの軸の周りに 3 つの別々の回転を実行します。
Unity では、Z 軸、X 軸、Y 軸の順にオイラー角回転を行います。
この回転方法は外的 (extrinsic) 回転で、回転が行われても元の座標系は変化しません。

yuki-koyama.hatenablog.com
 Extrinsic rotationと Intrinsic rotationについて。Intrinsic rotationは軸周りで回転した時、基準の座標系も一緒に回転する場合を指します。

qiita.com
 オイラー角についての学習を阻む5つの罠を解説しています。


www.sky-engin.jp
 オイラー角についての数学的な詳細な解説。


en.wikipedia.org
 「ジンバルロック」の解説記事なのに「ジンバル」に触れませんでした(リンク先は英語。日本語版wikipediaには項目が無かった)。いつか外的/内的回転と合わせて補足を追記するかもしれません。

参考書籍

 3次元の回転についてこの本の勉強しました。オススメです。

宣伝

 「Unityシェーダープログラミングの教科書シリーズ」既刊1~5巻のPDF版をBOOTHで頒布中です。
s-games.booth.pm

*1:場合によってSHIFT押さなくてもいい場合があるので実はSHIFTは関係ないかもしれない

*2:後述しますが、これはUnityにおいての話になります

*3:数学者オイラーが発明した事からこの名前がついています

*4:これは正確ではなく、後述する通り、Unityが実際に格納しているのはオイラー角ではなくクォータニオンです。

*5:この用語はネットで検索したら出て来た物なのですが、数学用語なのかはわかりませんでした

*6:これにはZ-Y-Zのように同じ軸を2回使う場合が含まれます

*7:これを「ZXYオイラー角」とか「ZXYオーダー」とか呼ぶ事もあるようですが、これも数学用語なのかは不明です

*8:Unityだけでなく、大抵の3DソフトはZXYオーダーを採用しているようですが、特にこのオーダーがメジャーという訳でも無く、何故そうなっているのかは分かりませんでした

*9:この為にユニティちゃんの頭をZ軸方向に向けていました

*10:このように書いているのは、ローテーションオーダーに何を採用しているかによって、ジンバルロックが発生する軸と角度の組み合わせが変わるからです

*11:これは例えば「寝っ転がった体が正面から起き上がり、そのまま真横に倒れる」のような動きです

*12:解説もしません(できません)