これまでは、カメラを動かすのに位置と回転角度による制御を行っていました。エルミート補間でスムーズに動くカメラは見ていて気持ちいいものです。位置と回転角度で制御できるので、レースや3Dシューティング等の一人称視点ゲームには大変好都合でした。
それでは、たとえばレースにしてもコクピットを離れ車体を後ろから眺める視点ならどうでしょうか。サインコサインで何とかなりそうです。では3D格闘の2人がいる辺りを見る視点等はどうですか?うーん、頑張ればなんとかカメラの回転角度を計算できるかも……です。どうもすっきりしません。見たい場所を指定して、そこを見るような制御は出来ないのでしょうか?
「簡単じゃん。見たい場所と視点位置から角度を逆算すればいいんじゃ?」そう思われるかもしれません。でも、果たしてそうでしょうか?というのも、ある点からある点への回転変形方法は無数に考えられるのです。モスクワ経由でいっても、オーストラリア経由でいってもフランスには辿りつくのです。さぁ困りました。
DirectXRMには便利なメソッドがありますね。IDirect3DRMFrame::LookAtがそうです。目標フレームと、参照するフレーム(属する座標系の指定)と、回転挙動指定構造体を引数にとると、希望どおりの方向を向きます。こいつはカメラに限らずフレームに使えるので、ちょっと引数がややこしい気もしますが便利なことには違いありません。
OpenGLにもほぼ同名の関数が存在します。gluLookAtです。これは視点位置、目標位置、Upベクトルの3つのベクトルを引数にする関数です。DirectXのLookAtと名前こそ似ているもののやる事は全然違います。この関数はなんと、ビュー行列つまりカメラ行列を作ってしまうのです。
見たいところを見るカメラの秘密はこの辺りにありそうです。そんな気がしませんか?
先程gluLookAtが得ていた引数で、見慣れないベクトルが1つありました。Upベクトルです。例えばあなたがテレビを見ているとします。頭のてっぺんはどの方向を向いていますか?天井向きですか?えっ、壁向きですか?じゃぁ寝転がっているんですね。当たりでしょう。そうです、あなたの頭のてっぺんの向きを知れば、あなたの頭がどういう状態にあるか知る事ができるのです。テレビの位置と、あなたの頭の位置と、てっぺんの方向を知ればあなたの頭の姿勢が一意に決まるのです。もちろん、「てっぺんの方向がUpベクトル」ということになります。
さて、こうしてカメラ行列を作成しているのがgluLookAtでした。姿勢が制御できるのはわかりました。でもここで疑問が浮かびます。何故カメラ行列まで決める事が出来るのでしょうか。ここまでの説明で、回転や角度という言葉は一切出てこなかったにもかかわらず、それでもカメラ行列が作成できるのでしょうか。
結論から先にいうともちろん可能です。方法の説明に入る前に、カメラ行列とは何をしている行列か思い出して頭の整理をしてみましょう。
あなたを見ている女の子視点がカメラ座標系でした。女の子が頭を回すと反対向きに世界が回る、そうして作った行列がカメラ行列でした。オブジェクト座標系からワールド座標系への変換行列があり、ワールド座標系からカメラ座標系への変換を行う行列が、カメラ行列でした。
最後の文章に注目します。ワールド座標系からカメラ座標系へ変換する行列がカメラ行列なんです。回転という概念を経由しなくても、ワールド座標系からカメラ座標系へと変換する行列が手に入れば、それはまぎれもなくれっきとしたカメラ行列なのです。
カメラ座標系って何でしょう。思い出しましょう。視点の位置を0として、視線方向をz軸とした座標系のことです。先程のテレビの喩えでいう頭のてっぺん方向がy軸で、右耳にあたる方向がx軸です。なんのことはない。ちゃんとカメラ座標系が定義されているじゃないですか。あなたの頭がワールド座標系で何度傾いているか、何度首を回しているかを計測しなくても、見ているテレビの方向とてっぺんの向きでカメラ座標系が決まっています。
少し話は前後しますが、ここでてっぺんの向きが何をつかさどっているか見てみましょう。視点の位置と注視点(テレビ)の位置によってあなたは動く事が出来なくなりますね。眼はテレビに釘付けです。それと同時に、こうべを垂れる事も後ろを振り返ることも出来なくなります。そのような事をすればテレビは注視点から外れてしまいます。あなたに許されている残る唯一の行動は何でしょうか。
そうです。視点とテレビで構成される軸を用いて、視点を中心にあなた全体がz軸回転することだけが許されているんです。あなたが空中で逆立ちしてテレビを見てようが右耳を右肩にくっつける不自然な姿勢でいようが、視点の位置と注視点の位置とには変化がなく、かつテレビを見続けていられるのです。この右耳を右肩につけるような動き、z軸回転のことをバンク角(bank)と呼びます。てっぺんはバンクをつかさどっているといえます。
シンプルな言葉で要点を整理してみましょう。視点と注視点により、ワールド空間上でのカメラz軸の方向が決定されます。Upベクトルもしくはバンク角によってカメラy軸ベクトルやカメラx軸ベクトルが決定されます。こうして決まったカメラ座標系にワールド座標系での位置を変換してやる方法が見つけられれば、見たいところを見るカメラ変換は実装できます。
長い前振りでした。本題にはいりましょうか。興味の対象は2点、「カメラ座標系の決定」と「カメラ行列の作成」ということになります。
われわれの目標は簡単に見たいところをみるカメラの制御ですね。これをもう一度踏まえて話を進めたいと思います。
バンク角0のカメラ、真正直に物を見るカメラとでもいいましょうか。この傾ぐことなく対象を見詰めるカメラを考えます。ここで注意するべきことが一つ存在します。バンク角が0であっても、Upベクトルは天井方向、ワールド座標系での<0,1,0>を指している訳ではないということです。足元を見る際に、てっぺんは前方に倒れていますよね。このようなUpベクトルを引数にとるgluLookAtは扱いにくそうじゃないですか?カメラ制御の際にいちいちUpベクトルを算出して呼び出さなきゃいけない。それよりは、バンク角制御の方が楽そうです。方針を決めましょう。視点、注視点、バンク角、この3つでカメラ座標系を決めることにします。いけそうです。
再びバンク角0のカメラから始めます。この時、視点から注視点へのベクトルは、そのままz軸方向になるんでしたね。これでカメラ座標系の一軸が決定しました。残るは二つ。
次に決めるのはx軸、右耳方向が良さそうです。なぜならバンク角0なので、右耳方向は常に床に平行に伸びているからです。信じられなかったらやってみましょう。首を傾けることなく、上下を見たり、左右を見たりしてみてください。家族にみつからなければなお良いでしょう。納得がいきましたか。つまり右耳方向は「天井方向と視線方向の成す角に直交するベクトル」ということになります。外積の出番でしょうね。
CX = <0,1,0>×CZ CX,CZはベクトル
という感じでしょうか。ベクトルの長さが気になる人は慧眼です。視線方向CZも正規化しておく必要がありますし、出てきた右耳方向Cxも正規化しましょう。さらに一軸が確定しました。
最後のy軸は自動的に決まるでしょう。
CY = CZ×CX
さらっとこんな感じでしょうか。これで3軸全てが決定しました。どれだけ回転しようと、あなたの視線と右耳と頭のてっぺんが成す角90度に変化はありません。だからこんな芸当ができるのです。
ただし、上記の式は視線ベクトルがキッチリ天井、床方向(CZ.xとCZ.zがともに0)の場合には用いることができません。詳しい説明は省きますが、<0,1,0>の代わりに<-CZ.y,0,0>を用いれば良いみたいです。考えてみてください。
もうこれでカメラ座標系は確定できました。軸方向は、CX,CY,CZ、その原点位置は視点位置そのものです。では、この座標系へ変換する方法を考えましょう。
以前に「ポリゴンの原点からの法線方向にそった距離」を内積でシンプルに算出したことがあったかと思います。それと同じ方法が今回も使えることにお気づきでしょうか?
例えばカメラz軸方向にそった距離を求めるには、
V・CZ = |V|cosθ (CZは正規化ベクトル)
で済んでしまうんです。しかも嬉しいことにここで出てくる答えスカラーは、カメラz軸でのz座標そのものにるんですね。他もあわせて書いてみましょうか。
V' = < V・CX V・CY V・CZ >
ここで行列表記に変換してみましょう。ちょっと不思議なことが起こります。
| V'.x | = | V.x | | CX.x CY.x CZ.x | | V'.y | = | V.y | * | CX.y CY.y CZ.y | | V'.z | = | V.z | | CX.z CY.z CZ.z |
表記のしかたがややこしくなってしまいましたが、右側の3x3行列をワールド座標の位置ベクトルに掛けてやると、カメラ座標系での位置ベクトルに変換されます。これは、今まで私がカメラ行列と呼んできた物の回転部分そのままではありませんか。
回転等と一言も言っていないのに、同じ物が作れてしまいました。では平行移動部、同次行列部はどうなるのでしょうか。これも考え方はここまでと同じです。ワールド座標系での視点の位置をカメラ座標系に変換して、逆向けに移動させれば良いのです。あわせて4x4行列にしてみましょうか。
| CX.x CY.x CZ.x 0 | | CX.y CY.y CZ.y 0 | | CX.z CY.z CZ.z 0 | | -(CX・E) -(CY・E) -(CZ・E) 1 | Eは視点位置ベクトル
ちょっとわかりにくいですね。すいません。
とにかく今まで女の子視点を例に出して、世界ごと移動させたり世界ごと回転させたりして作ってきたカメラ行列と同じものが作れてしまいました。視点と注視点とを使うだけでカメラ行列が作れる。そしてこれは本項の主題であった「見たいところを見るカメラ」なのです。
ところで、ほったらかしにしてきたバンク角の問題はどうなったのでしょう。「あぁ、やだなまた一からやりなおしか、めんどうだな。」いえ、そんなことありません。上記の新カメラ行列に以下の行列を掛けてみましょう。
| cosB sinB 0 0 | | -sinB cosB 0 0 | | 0 0 1 0 | | 0 0 0 1 | Bはバンク角
もうお分かりですね。z軸回転行列です。変に考え直すより潔いと思いませんか(笑)
こうして出来たカメラ行列を今までのカメラ行列のかわりに用いることで見事にワールド座標系からオブジェクト座標系の変換が行えます。また、3x3部は正規化されており、しかも(というか当たり前というか)直交しているので、転置による逆行列も用いることが出来、いままでの裏面消去やライティングに支障はきたしません。素晴らしいことです。
もちろん、位置と向きによるカメラの制御はこれからも重要ですが、加えて視点と注視点とバンク角で制御できるカメラもあれば有用ですよね。余談ですが、そう言われてみると、QuakeやDoomのカメラにしてもバンクしませんよね。3D格闘ゲームのカメラの中にもバンクしないカメラが多くないですか?
いや、3D格闘ゲームのカメラがバンクしないのには実は訳があるんです。次の項はそのあたりになりそうですかね。