姿勢情報はクォータニオンだけでいい
前回は3Dプログラムがやるべきことをざっくり話しただけで終わってしまったのだった.今度こそ,ちゃんとクォータニオンを使う話をしよう.
まず最初に,オブジェクトが作成された時のままの座標データをうまく回転させてワールド座標系に配置することが必要になるのだった.回転後のどんな姿勢であっても,一つの軸とその周りの回転で表せるという数学の定理があるので,回転軸の方向を表す単位ベクトルと回転角を表すデータを保持していれば,姿勢が表せるのである.このデータは次のような形で一つの四元数(クォータニオン)として表しておける. つまり,一つのオブジェクトごとに一つのクォータニオンを姿勢データとして持っていればいい.
とは言うものの,望むような姿勢にするためにどの軸を選んでどの程度回転させればいいのかが分からないということがほとんどだろう.全く心配は要らない.3つの軸の周りに順番に回転させていけばいいのである.
説明のために仮に座標軸を定めた方が伝わりやすいかもしれない.画面の右側を軸,画面の下向きを軸,画面の奥行きを軸としてみよう.
そして,例えばそこに飛行機などの模型があるとイメージしてもらいたい.これが何も変換しない状態ではワールド座標系での軸方向を向いて水平姿勢になっているとする.まずこれを軸の周りにある程度回転させて機体を左右どちらかへ傾ける.この回転のためのクォータニオンをと表すことにしよう.
次にこれを傾いたまま,ワールド座標系での軸の周りに回転させてやる.この回転のためのクォータニオンをと表すことにしよう.
さらにこれをワールド座標系での軸の周りに回転させてやる.この回転のためのクォータニオンをと表すことにしよう.
このような回転を順次行った場合の結果を表すクォータニオンは という計算で得ることが出来る.
このような考えでプログラムの最初のところで計算をしておけば,オブジェクトの初期位置を直観的に設定しておけるわけだ.変更も実に簡単である.
オブジェクトを転がしまくれ!
これは初期設定に限った話ではない.オブジェクトに力が加わって回転する様子を表したければ,ワールド座標系でのどの軸の周りにどの程度回転したのかを表すクォータニオンを作ってやって,それを姿勢データを表すクォータニオンに左から次々と掛けて行ってやればいいのである.
一コマ表示するごとに掛けて行って,その結果を新しい姿勢データとして保持してやればいい.毎秒30コマの動画を作りたければ毎秒30回だ.もちろん,計算力に余裕があればもっと滑らかにするために,もっと高い頻度で掛けて行ってやってもいい.
毎秒30コマの場合には,10秒もあれば300回もクォータニオンどうしを掛け合わせることになる.誤差が蓄積してしまうことが心配だ.しかし,クォータニオンの絶対値を計算してやって全体を割ってやれば,ほぼ同じ姿勢を表すデータとして,矛盾のない形に修正してやることができる.データが大きく壊れる前に頻繁に補正をしてやればいいだろう.
座標変換もクォータニオンで
この姿勢データを表すクォータニオンをどう使ったら良いかだが,オブジェクト内の点を表す座標データを のような四元数として表して, という計算をしてやれば,ワールド座標系の座標値は四元数として取り出せるというわけである.
しかしこれはあくまでも理論的な話である.いちいちこのような四元数の積を実行させるのでは効率が悪いので,このときの計算内容を調べて同じ結果を返すような式に書き直してやれば,無駄を削ぎ落とせてもっとずっと楽に計算できる.
例えば,次の公式を使って計算すれば計算量がかなり減らせる. この式は座標点をへと変換するもので,クォータニオンの 4 つの実数成分をのようにスカラーとベクトルに分けて表している.この式は,クォータニオンの虚部どうしの積がベクトルの外積と同じになることを利用して慎重に検討すれば導くことができる.共通部分をなるべく括り出すようにして計算の二度手間をなるべく減らす工夫することが重要である.
ベクトルの外積では 6 回の乗算が必要で,ベクトルを定数倍するのに 3 回の乗算が必要であることを考えると,この計算では合計 15 回の乗算を行うことになる.もし普通に四元数の積を計算するとどうだろうか?A の実部が 0 であることを考慮しても,最初の四元数どうしの積で 4 × 3 = 12 回,次に 4 × 4 = 16 回の合計 28 回の乗算を行うことになる.いや,最終的に実部が 0 になることは確定しているのでそれに関わる計算を省くことができて,2 番目の計算も 12 回でいい.合計 24 回ということになるが,それでも無駄が多い.
ビュー座標系への変換
さて,このワールド座標系を眺めるカメラもまたこのワールド座標系の中に置かれているという設定だ.このカメラの向きもまたクォータニオンで表しておくだけでいい.
このクォータニオンはワールド座標系に対してカメラがどういう回転をしているかを表しているのである.ということは,ワールド座標系を逆方向へ回転させてやれば,カメラから見た座標系へと変換できる.回転角の符号を反転させればいいだけなので,はそのまま,の符号が反転する.つまり成分の符号をひっくり返したクォータニオンを使って計算すればいいだろう.この操作は回転軸を正反対の方向に向けたと解釈しても同じことである.この操作は「四元数の共役」でもある.
このように,何もかもが実に簡単な操作で実現できる.
ところで,カメラを動かすときには,ワールド座標系の中でどう動かすかというよりも,カメラ視点で上下や左右に動かしたいということの方が多いだろう.例えばゲームの場合には,プレイヤーの視点でカメラを動かすことになるからである.
その場合の計算も簡単である.現在のカメラの姿勢がだとして,そこからビュー座標系の立場で作っただけ動かした結果としてカメラの姿勢がになるようにしたい.そのためには仮にビュー座標系とワールド座標系が一致している状況を初期状態と考えて,そこからだけ動かして,その後にだけ動かせばが得られるだろう.つまり,次のように計算すればいい.
姿勢制御
今はカメラの視点でカメラの姿勢を変更する方法について話したが,この考え方は他にも応用可能である.つまり,オブジェクト側の視点でオブジェクトを動かすときに,ワールド座標系ではどう動かしたら良くて,その結果としてどんな姿勢になるかという計算は全く同じことである.
カメラをオブジェクトに置き換えただけのことだ.
例えばフライトシミュレーターを作るとき,航空機に働く力の全てをワールド座標系の中での力学で考える必要もない.航空機内の視点で航空機に働く力を計算して,それを元にしてワールド座標系での姿勢を変更してもいいだろう.
人工衛星の姿勢制御にも同様の考え方が使えるだろう.人工衛星に装備されているスラスターやリアクションホイールなどによる動きは,人工衛星からの視点で考えたほうが楽である.その結果をワールド座標系へと反映させてやれば,現在の客観的な姿勢を把握することができる.
姿勢制御の話が出たついでに話しておくと,現在の姿勢から,望みの姿勢へと動かしたいということがあるだろう.そのような計算もクォータニオンは得意なのだった.現在のワールド座標系での姿勢がで表されていて,目標としたい未来の姿勢がであるとき,そこへ至るための回転を表すクォータニオンは次の計算をすればいいのだった. このから回転軸と角度を取り出すことは簡単なので,その回転軸を再びビュー座標系へと変換してやれば,オブジェクト視点でどのように回転してやればいいかが分かるというわけだ.
しかしわざわざから回転軸を抽出したあとに座標変換するのは余計なひと手間である気がする.一発で情報を得られる方法は無いだろうか?
そのために少し考えてみよう.欲しいのはビュー座標系から見てこれからどう動かせばいいかを表すクォータニオンである.ということは,最初にビュー座標系とワールド座標系が一致している状況をイメージしてによる回転を行い,その後,オブジェクトの姿勢であるによってそれをワールド座標系内で回転させた結果,それがオブジェクトの新しい姿勢になっているはずである.式で表すとである.この両辺に左からを掛ければ次の式が得られる. このから回転軸と角度の情報を取り出すようにすれば,座標変換を施すひと手間が省けるというわけだ.
回転情報の取り出し
たった今,簡単だと書いたけれども,クォータニオンから回転軸と回転角度を正しく取り出せるだろうか?少し考えてみよう.クォータニオンには次のような形で情報が入っているのだった. 角度は第 1 項だけを見て取り出せそうだが,の範囲を限定してやらないと一意に決定できないような気がする.例えばの範囲だと考えれば,の範囲で動き,一意に決まるだろう.しかしの範囲だと考えれば,の範囲で動く.は原点を中心にして左右対称なので,これでは角度が正なのか負なのかが決められない.クォータニオンの角度指定は正の値だけが許されるのだっただろうか?いや,そんなことはない.
この疑問を解決するために,上の式ののところにを代入してやると状況がはっきりする.全ての三角関数の位相がだけずれることになるので,すべての項の符号が反転する.つまり,クォータニオンの全体に -1 を掛けてやったものも,全く同じ姿勢を表しているのである.
クォータニオンの実部は正であることも負であることもあり,積を繰り返すうちにたまたまどちらかになったりするわけである.この正負だけでは姿勢についての判断は出来ない.
そこで,回転角の範囲をだと仮定してしまって,もしクォータニオンの実部が負だったら全体に -1 を掛けて正にしてしまい,つじつまを合わせてしまおう.そして次にの値からの値を算出しよう.このの値が正か負かが分からないという問題が生じるが気にしなくてもいい.正であると仮定してしまおう.この時点で回転角の範囲がだと仮定されたことになる.そしてクォータニオンの実部以外の3つの項をこので割れば回転軸の方向を表す 3 成分が得られる.もし符号の仮定が間違っていたとしても,そのときは回転軸の方向が逆のものとして得られるだけであり,回転後の姿勢については全く同じ状況を表している.
何よりも,最小の正の角度で目的の姿勢に到達できる方法を得ることができるのでありがたい.
この方法ではθが0に近いところでは誤差が出やすいという御指摘を頂いた.cos(θ/2) はその付近でほとんど平坦になるからθの変化に対して敏感ではないからだ.代わりに sin(θ/2) を使えば少しはマシになるかもしれない.この sin(θ/2) の絶対値は cos(θ/2) から得られるが,虚部の方から得ることもできる.回転軸は単位ベクトルなので,虚部の 3 成分をそれぞれ 2 乗して和を取れば sin(θ/2) の2乗が得られることになる.どちらの値を使うのがより誤差が少ないのかについてはよく分からない.理論上はどちらも同じであって,同じになるように調整されているはずだからである.
本文中では cos(θ/2) から sin(θ/2) を算出してから虚部を sin(θ/2) で割れば回転軸が得られると書いたが,θ=0 のときには 0 による除算が発生してしまって危険である.わざわざそのような危険な計算をする必要もないのだった.この虚部には共通の sin(θ/2) が掛かっていて同じ比率で値が小さくなっているだけなので,長さが 1 になるように再調整してやるだけで済む.とは言うものの,虚部の 3 成分の長さというのは sin(θ/2) の絶対値に相当するのでどちらにせよ sin(θ/2) を計算したことになるし,この値が 0 かどうかのチェックは必須になる.
θ が 0 の場合には虚部の 3 成分は全て 0 になっているので,その場合には回転軸の情報が得られないことになるが,そもそも回転していないのだから回転軸の方向には意味がない.諦めよう.
補間も簡単
このように,回転軸や角度の情報を取り出すことが簡単にできるので,初期姿勢から目的の姿勢までの途中の姿勢を計算することも簡単にできる.
クォータニオンから取り出した回転角を幾つかに割ってやって,角度を一定ずつ増加させながら,新しいクォータニオンを作り直してやればいいのである.ゲームのライブラリではそういう計算をしてくれる機能が用意されているはずだ.
しかし丁寧に毎回角度情報を取り出して新しいクォータニオンを作るのは面倒である.一回の計算はそれほどでもないのだが,オブジェクトの数が増えるとコンピュータの負担も多い.そこで,あまり正確な動きを必要としていない場合にはもっと単純な方法で実現されることもある.初期姿勢のクォータニオンから,目的の姿勢のクォータニオンに近づくように,各成分の値を直線的に変化させていくのである.
多少のおかしな動きはあるが,この方法でもかなり目的を果たすことが出来たりする.ゲームのライブラリではこういう単純計算を実現してくれる機能も用意されているはずだ.
ここで注意が必要なのだが,無理やり各成分を直線的に変化させると絶対値が 1 から外れてしまって,整合性が保てなくなってしまう.座標変換などがうまく使えないようになる.それで,毎回ちゃんと絶対値で割って正規化をしてやることが必要になるだろう.この辺りはライブラリが内部的にちゃんとしてくれているはずだ.
各成分を直線的に変化させるときにはもう一つの注意がある.目標とする姿勢を表すクォータニオンは,全体を -1 倍してもやはり同じ姿勢を表しているのだった.どちらを目標とするかによって,変化途中の値が違ってくる.つまり,目標に至るまでの姿勢変化に二通りのコースがあることを意味している.最短の回転角で目標に向かってくれるという保証がないのである.
クォータニオンの内積
現在姿勢から目標姿勢までの回転角をあらかじめ知る方法があれば助かるだろう.「実はクォータニオンの内積」という,素晴らしい方法があるのだ.
現状の姿勢が,目標の姿勢がであるとき,それぞれの成分を実数の 4 成分ベクトルのように考えて内積を計算してやる.その計算結果は,次のようなクォータニオンの実部と等しくなる. これはやる気を出せばすぐに確かめられると思う.今話したことを分かりやすく表すと次のようになる. というのは,をどう動かしたらになるかを表すクォータニオンだったので,このが二つの姿勢の間の角度を表していることになるのである.
内積を計算してみた値が負になっていたらとにかくよりも遠いということになる.すなわちまでは最短コースではないことが分かる.その場合にはを -1 倍してやれば,そこへ至るまでの姿勢の方が逆回りの最短コースだということになるだろう.途中の姿勢を補間する前にはこのような方法で確認して,望む方を使えばいいのである.
クォータニオンを使わないほうが速いこともある
ここまでクォータニオンを使って何もかも解決できるという感じに書いてきたが,ゲームやリアルタイムシミュレーターでは計算速度が命である.速度が稼げるのなら,クォータニオン以外の方法を使ったほうがいい.
姿勢の計算にはクォータニオンがとても便利だが,一度姿勢が決まってしまえば,あとは座標変換という単純作業が待っている.座標変換は 3 × 3 の回転行列を使ったほうがずっと楽である.
クォータニオンによる座標変換をクソ真面目にやると 24 回の乗算が必要だが,行列を使えば 9 回で終わる.
この行列を作るのに少し計算の手間が掛かるが,場合によっては何万個ものオブジェクトを座標変換することになるので,最初に行列を一つ作っておくくらいはどうということはない.
この行列を作るために前にも紹介した「ロドリゲスの回転公式」というものを使ってもいいだろう. しかしコンピュータでは三角関数の値を算出するためにテイラー展開を利用した近似式を使ったりしていて,計算量が多くなってしまうことがある.もっと楽をしよう.わざわざ回転角度や回転軸の情報を抜き出してきて使わずとも,クォータニオンの成分をそのまま使えばいいのである.
クォータニオンの 4 つの成分をとして,クォータニオンによる座標変換の計算内容を展開して整理すれば次のような公式が出来上がる. このように直接的にクォータニオンから変換行列が生成できるのである.計算内容が少し面倒に見えるかもしれないが,この程度ならコンピュータは文句も言わずに即座に結果を返してくれる.
このような行列を使った大量の座標変換は,一つの計算結果を待たなくても次の計算をするのに差し支えないので,同時に計算してやれば効率がいいだろう.しかしCPUというのは基本的にプログラムに従って流れ作業をこなしていくものなので,同時に複数の計算を進めて行くのは苦手である.そこで並列計算に特化した専用ハードウェアの出番である.
グラフィック機能に特化したGPUと呼ばれる専用チップがある.このGPUの他に映像出力端子やその信号制御までの機能を積み込んだ拡張基板は「グラフィックボード」「グラボ」などと呼ばれる.最近ではごく簡単なGPUはCPUの中に内蔵されていたりもする.
GPUはどんどん進化しており,座標変換だけでなく,光の反射なども計算してくれるようになっている.オブジェクトのデータと変換行列を渡してしまえば,あとは画像を表示するところまで全てお任せというわけである.