オイラー角とは
ここまで,クォータニオンが万能であるかのように書いてきたが,いつでもクォータニオンを使えばいいというものでもない.クォータニオンを説明する記事だったのでクォータニオンのことしか書かなかったというだけである.
オブジェクトにごく簡単な動きしかさせない場合には「オイラー角」を使った方が計算の負担が少ないこともある.オブジェクトの姿勢を表すためには 3 つの角度を指定すれば十分であることが知られており,「オイラー角」というのは,そのための 3 つの角度のことである.
クォータニオンで使ったのは,オブジェクトの姿勢を表すのに,一つの回転軸とその周りの回転角をしてやればいいという考え方であった.(これはオイラーの定理と呼ばれていたのだった.オイラーばかりでややこしい.)この回転軸の方向を表すために 3 つの数値を使っているが,方向を指定したいだけなので単位ベクトルを使えば十分であり,2 つの数値があれば残りの数値も決まるのである.回転軸を表すための数値 2 つと,回転角とで,合計 3 つの情報があればいい.3 つの数値で姿勢を表すという点ではオイラー角と同じであるが,やり方が違うのである.
飛行機の操縦に例えて説明しよう.飛行機の姿勢を変える方法は 3 通りあり,直交する 3 つの軸の周りの回転として区別される.操縦席に座って右側を軸,正面を軸,上方向を軸としてみよう.軸の周りの回転は「ピッチ」と呼ばれている.機体を上昇させたり下降させたりするような動きである.軸の周りの回転は「ロール」と呼ばれている.機体を左右に傾ける動きである.軸の周りの回転は「ヨー」と呼ばれている.機体の進路を変えるような動きである.
まずはラダーペダルを踏んで「ヨー」を変化させる.次に操縦桿を引くなり押すなりして「ピッチ」を調整する.最後に操縦桿を横に倒して機体を「ロール」させる.こうすれば機体の姿勢は完全に決まるだろう.
これはコックピット視点で説明したわけだが,機体を客観的に見て同じ姿勢にする場合には逆の手順が必要である.まずは軸周りの回転「ロール」を行う.次に軸周りの回転「ピッチ」を行って上下角の調整を行う.最後に軸周りの回転「ヨー」を行う.
回転させる順番によって姿勢が変わってしまうし,視点によっても結果が変わってしまうわけだ.
私は 3D プログラムではこのようにの 3 軸を使うのが好みだが,2 軸だけを使って→→などのように 3 回の回転をさせても任意の姿勢を表現することが出来る.色んな流儀があるので資料によって書かれている内容が違うといって悩まないようにしてほしい.
具体的な計算法
軸周りの回転は平面での回転なので次のような回転行列を使えばいい. これはオブジェクトの各点をワールド座標系での位置に回転変換するものである.右ネジが座標軸の方向に進むときの回転方向を正としている.
続けて変換するためにはこうして得たをさらに変換するという意味にすればいいから,行列を左側に掛けて行けばいい.先ほど説明したような→→軸周りの順での回転の場合には次のような行列を使う. 行列の積を毎回やらせると負担になるので,あらかじめ計算したものを使った方がいいかもしれない.それは次のような形になる. この行列を,現在のオブジェクトの姿勢を表す情報として持っていればいい.
オブジェクトをこの姿勢を基準にさらに動かしたければ,この形の行列を使って今の姿勢からどう動かしたいかを表しておき,そこに現在の姿勢を表している行列を左から掛ければいい.
要するに,差分を表す回転行列を作っては右から次々と掛けて行けばいいのである.しかし,理論的にはそれでいいのだが,この方法はお勧めしない.誤差がどんどん蓄積してしまって行列のそれぞれの成分の数値に矛盾が出てきてしまうのである.壊れかけた行列の数値から,,を算出して,矛盾のない行列を新たに作り直すことが出来ればいいのだが,それもまた困難である.
そういうことをしたいのならクォータニオンの方がはるかに楽に矛盾を解消させられる.
追加情報:この記事の公開後,回転行列の誤差の修正法があることを教わったので共有しておきたい.回転行列というのは直交行列の一種である.行列の中の縦の 3 つの成分をベクトルとして見ると,それは元々の座標軸方向の 3 つの単位ベクトルがそれぞれどこへ移されるかを表している.つまり,縦の並びの 3 列のベクトルは,それぞれが直交する単位ベクトルになっているはずなのである.
そこで,第 1 列と第 2 列のベクトルどうしの外積を計算してやればそれらの両方に直交するベクトルが得られるのでそれを新たな第 3 列として採用し,次に,新たに作ったばかりの第 3 列と第 1 列との外積を作って新たな第 2 列として採用する.こうして 3 つの軸の直交性を復帰させられる.さらにそれぞれの列を単位ベクトルになるように調整してやればどこにも矛盾のない形に戻すことができる.
カメラは逆変換
ここまでの話はオブジェクトの座標をワールド座標に変換する方法であった.カメラの姿勢を上のような行列で表していたとしよう.これはカメラから見た座標をワールド座標へと変換するためのものである.しかしやりたいことは逆なのだ.ワールド座標に置かれたオブジェクトの座標をカメラの座標へと変換したい.そのためには逆行列を使う必要がある.
逆行列を作るのは大変なので,カメラのためには最初から逆行列を作っていけば良いだろう.角度の符号を逆にしただけではダメで,→→の順で元に戻してやる必要がある.次のような行列を使うことになるだろう. これを計算してやると次の形になる. 掛ける順序が違うから,先ほどとはかなり形が違って見える.
追記:……いや,よく見たらさっきの行列を転置しただけじゃないか.直交行列の逆行列は転置行列に等しいということをすっかり忘れていた.カメラ用の行列をわざわざ別に作る必要はなくて,この節の話はほとんど丸ごと要らなかったことになる.
もしカメラ視点を移動したければ次のように考えればいい.既にあるカメラのための逆行列によってワールド座標からカメラ座標へと変換し,その結果をさらに次のカメラの動きによって変換したいのである.つまり,どれだけ移動したいかを表すための差分行列をこの行列を使ってカメラ視点で作ってやり,それを現在のカメラのための逆行列に左から掛けてやればいいのである.先ほどとは掛けて行く方向も逆になっている.
しかしこれも理論上の話であって,誤差がどんどんたまるのでお勧めしない.
ここでちょっと思ったのだが,もし 2 軸だけを使って→→のようなやり方で姿勢を表すことにすれば,逆行列を作るときも→→とすればいいので,形式上は同じ形になっていたであろう.しかも軸周りの変換の行列は少し分かりにくい形をしているので,それを避けられるという利点もある.色んな人が色んな都合でそれぞれのやり方をしているというわけだ.
失敗談
自分が高校生の頃,まだコンピュータは非力であったが,とにかくコンピュータの中に世界を実現したかったのである.何よりも,宇宙旅行に憧れていた.都合のいいことに,宇宙は真っ黒の背景に星を描いて動かすだけで,かなり本物っぽくなるのだ.
自分はコンピュータの中に作った宇宙の中で自由にぐるぐる回ってみたいと思った.目標としては,現実の星座を背景にした宇宙戦闘ゲームを作りたかったのである.理科年表を片手に,星の位置のデータを根気よく打ち込んだ.出来れば星の色も再現したかったが,それは後回しだ.高校で学んだ数学を頼りに,座標変換の行列を作り出した.つまり,上に書いたような行列を計算して「俺はなんて賢いんだ!」と悦に入ったのである.
ちょっとした試行錯誤の後,それは動いた.感動だった.オリオンの三ツ星もあった.
しかしその喜びは30秒も続かなかった.
星はじわじわカクカクと移動し,次第におかしな方向に集まり始め,一斉にどこかへ吹っ飛んでいった.誤差の蓄積が止められなかった.次第にボロボロになっていく変換行列を解読して,再び辻褄の合う形に戻そうとしたが,いい方法は分からなかった.
数年後,マイクロソフトのゲーム用ライブラリの資料からクォータニオンの存在を知る.誤差が蓄積しない魔法のような計算方法が存在するというのだ.当時はまだ英語の文献しか手に入らなかったし,取り寄せて読んでみても全くの謎であった.私がこうしてクォータニオンの記事を楽しそうに書いているのは,その当時の感動と憧れが原動力である.
オイラー角が使える場面
誤差がどんどん蓄積してしまうので,物体やカメラをぐりぐりと回転させるような場面では使わない方がいいと先ほどから繰り返し話している.それではオイラー角の方法はどういう用途で使えばいいというのだろうか.
例えば,平らな地面を基準にして移動するような派手な動きのないゲームである.オブジェクトの向きが水平面内での方角と,地面を基準にした上下角と,水平面を基準とした傾きで決まるようなもの.そのようなものであれば,これらの情報からいつでも壊れていない行列を毎回作ることが出来る.
カメラの向きについても同様である.方角と,地面を基準にした上下角で決まるようなもの.左右に多少傾いても構わないが,常に水平な地面を基準に表せていないといけない.
これはこの世界に極座標を導入したようなものである.経度と緯度が表示された天球の中心にいるのと同じイメージである.カメラは真上を向いてはいけない.そこは多数の経線が集中する特異点になっている.視点が天頂付近に近付くほど,矛盾なくカメラ操作をするのが大変困難になってくる.
よく起こるバグ
昔のフライトシミュレーター系のゲームではこの問題が多く起きていた.カッコよく宙返りをキメてやろうとして操縦桿を引いてどんどん上を向いて上昇していき,天頂付近を向いたところで突如として空が勢いよく回転し,想像していたのとは全く違う方向へと引き倒されるのである.ひどい場合にはその後で操縦桿の操作と自機の動きが逆転してしまう.有名企業の市販のゲームソフトですら,こういうことはよく起こった.
何が起きたかはだいたい想像が付く.上下角がを超えることを許す作りになっていたのだろう.この状態では機体は背面飛行をしているはずである.そしてもともと北を向いていれば南を向いているはずである.画面でもそうなっている.ところが内部的にはまだ北を向いていることになっているから,加速して南へ行こうとするほど北へバックする.背面飛行をやめるために機体をロールさせてもこの状態は解消しない.東へ向かうために左旋回しようとすると,内部的はまだ北を向いていて,しかも先ほどのロールによって背面飛行をしていることになっているので西へ向かって右旋回することになる.
このような怪現象を防ぐために上下角がを超えることを禁止したらどうだろうか?もしも超えようとしたときには方位角を反転させて,さらに背面飛行をしていることにする.これで先ほどよりはかなりマシになる.しかしまだ抜け道が存在する.北を向いて水平飛行していた状態から上昇して天頂付近を向いたところで左へ約ほどロール.これで画面的には東を向く.ここから機首を下げて下降しようとすると何が起こるだろうか?内部的にはまだ北を向いていることになるから,北へ向かって倒れ始める.画面的には左へ向かって謎の力が働いて引き倒されることになる.
天頂付近を向いたときには機体をひねる動き(ロール角)と方位角がほとんど同じ動きになってしまい,両者を区別できなくなってしまうのである.このような現象を「ジンバル・ロック」と呼んだりする.
ジンバルロックという言葉は,昔の機械式のジャイロスコープで起きた現象に由来している.ジャイロスコープとは航空機や宇宙船の姿勢を知るための装置である.モーターで回転する円板が角運動量保存則によって一定の方向を向き続けることを利用している.
この円板は地球儀のような枠で支えられた中で回っており,複数の枠がそれぞれ異なる軸の周りに回転できるように組み合わされているので,機体がどんな方向を向いても中の円板の向きは変わらないでいられる.この枠のことをジンバルと呼ぶ.このジンバルのそれぞれの傾き加減を検出することで機体の姿勢を把握することが出来るわけだ.
ところが,機体が極端な方向を向いたときにはこの複数のジンバルの向きが揃ってしまい,中の円板が自由に動けなくなるという状況に陥ってしまう.文字通り,機械的に動きがロックされてしまう状態であって,これを「ジンバル・ロック」と呼んだのである.二つの回転軸が同じ方向を向いてしまって自由度が失われるという点が共通した本質である.
この状況になると中の円板が機体の動きに引っぱられて強制的に向きを変えられてしまう可能性が高く,計器の表示が狂ってしまう.これを防ぐためにはジンバルの数を増やして動きの自由度を増やしたものを作ればいいが,仕組みが複雑になってしまう.
ジンバルロックを防ぐための対策としては,あまり真上や真下を向くことの無いように,プレイヤーの操作範囲を制限してやることだ.そういうゲームに心当たりがある人も多いだろう.