MCL(107)

インデックスに戻る

前のMCLへ  次のMCLへ

▼ My Computer Life (107)

107. 3D格闘ゲーム作成(後編)

 WindowsとDirectX9で、3D格闘ゲームを作ろうとした、という話の続き。
 Mayaが吐き出す、独立オイラー角の回転表現を、DirectX側で受け取るクォータニオン(四元数)に変換しないといけない。クォータニオンの方が、回転表現としては優秀なところがあるので、今回はどうしても使いたかった。

 そこでまず、変換公式を導いた。インターネットでも簡単な式は見つかったが、自分で導出しないと、プログラム化しづらい。なのでまず、紙とノートで長い数式を書き下した。その頃はもう学校を卒業して何年も経っていたので、長い数式を書くのなんか久々だった。ちょっと新鮮だった。
 考え方としては、X軸・Y軸・Z軸そのものをまずクォータニオンで表現し、そのそれぞれの回転を合成したものが、求めたい変換公式になるはず。こんなイメージで、確かノート3ページ分くらい数式の導出をしたなあ…。懐かしい。

 これでまず、オイラー角→クォータニオンの変換公式を導いたが、最初はX→Y→Z軸の順に回転させたケースのみ考えた。前回書いた通り、XYZ軸それぞれの回転を、どの順番でやったかで、結果は異なる。結果が異なるという事は、回転の順番によって異なるクォータニオンが得られるという事だ。
 そこで、それぞれの回転順の変換公式を求める事にした。これについては、当時のメモがPC側に残っていたから、それを転載してみる。別に読まなくてもいいけど。


 上記を踏まえて、クォータニオンQx Qy Qzを、YZXの順にかけてみる。
 まず、QyとQzをかけた、Qzyを作る。

 w部は全て、

	cos(Rx/2)*cos(Ry/2)*cos(Rz/2) + sin(Rx/2)*sin(Ry/2)*sin(Rz/2)

 になる事がわかったので、省略する。この式は、XYZの順番に関係しないらしい。

 一応w部としては、

	Qzy.w = Qz.w * Qy.w - Qz.v・Qy.v

 で出せる。後ろの内積の項は、直交しているのでゼロになる。よって、
	cos(Rz/2) * cos(Ry/2)

 という簡単な答えが出る。

 で、Qzyのベクトル部を出す。

	Qzy.v = Qz.v×Qy.v + Qz.w*Qy.v + Qy.w*Qz.v

 の3項の式になる。それぞれ計算する

*** Qz.v×Qy.v ***

 Qzはz、Qy.はy成分しか持たないので、Qz.zとQy.yが含まれている部分以外は全てゼロになる。
 それを踏まえて外積を出すと、

	X: Qz.y * Qy.z - Qz.z * Qy.y
	Y: Qz.z * Qy.x - Qz.x * Qy.z
	Z: Qz.x * Qy.y - Qz.y * Qy.x

 が…

	X: - Qz.z * Qy.y
	Y: 0
	Z: 0

 になる。外積なので、直交する軸が飛び出てくるわけだ。
 これを計算すると、

	-sin(Rz/2) * sin(Ry/2)

 が導かれる。

*** Qz.w*Qy.v ***

	Qz.w = cos(Rz/2)
	Qy.v = ( 0, sin(Ry/2), 0 )

 である。計算すると、

	(0, cos(Rz/2) * sin(Ry/2), 0 )

*** Qy.w*Qz.v ***

	Qy.w = cos(Ry/2)
	Qz.v = ( 0, 0, sin(Rz/2) )

 である。計算すると、

	( 0, 0, cos(Ry/2) * sin(Rz/2) )

*** 和を求める ***

 XYZがそれぞれ一つの項しか持たないので、和はたやすい。

	X:	-( sin((Rz/2) * sin(Ry/2) )
	Y:	cos(Rz/2) * sin(Ry/2)
	Z:	sin(Rz/2) * cos(Ry/2)
	W:	cos(Rz/2) * cos(Ry/2)

 これで、Qzyが求まった。

 途中から始まって途中で終わっているメモで、しかもこの後数式の間違いを自分で指摘しているので、もしかしたらここに書かれた導出のどこかにミスがあるかも知れない。もう15年も前に書いたので、今それを検算するのはさすがに面倒くさい。
 まあこんな感じで、結果的に、Mayaが出したオイラー角を、うまくクォータニオンに変換する公式が得られた。あとは、Mayaで吐き出すファイルをテキストファイルにした上で、この変換公式を用いてクォータニオン形式に直すコンバーターを作った。これで、Mayaで作ったモーションを、DirectXに適用できる。
 もちろん、ここに書いていない苦労はかなり多い。当時のメモを見ると、上記のコンバートだけではうまく動かず、色々調べた結果、Mayaのファイルには「関節のデフォルト角度、位置」があるが、それを一切考慮していなかったという事が判明した。他にも、そもそも、MayaとDirectXとで、座標系が右手系か左手系かが異なる(XYZ軸の、それぞれ+/-がどちらの向きなのかが2パターンある)ので、それを頭の中で変換するのも難しかった。

 さて、一番の難関だった、キャラのモーション関係が終わったら、後はわりとサクサク進んだ。
 まずカメラ制御がつき、それからクラスが諸々作られた。Characterクラスとか、それを継承したFighterクラスとか、なんか諸々。その上で入力を受け付けて、技を出せるようになった。ちなみに、モーションはわりと自分で作っている。パンチとかキックとか、立ちとかしゃがみとか、格ゲーっぽいのを作ったっけなあ。
 背景が無いのも寂しいので、CubeMapをとりあえず配置したっけ。何かのサンプルに入っていた、アメリカMicrosoftのロビーの360度画像をテクスチャ化したものだ。これをカメラと紐づけて表示すると、まるでMicrosoftで戦っているような画面になる。

 その後、もう一つの難関、影表示もした。
 この時代での影表示の方法はいくつかある。単純に丸影(物体の形に関わらず丸っこい影を床に表示する)であるとか、あるいはモデルを床方向に圧縮して板にしたポリゴンを表示するとか、あるいはシャドウマッピング(またはシャドウテクスチャ)と呼ばれる、光源方向からレンダリングした結果を影として床テクスチャに上書きする、という方法もある。
 私がまず挑んだのは、シャドウボリュームと呼ばれる手法である。これは今でもある程度一般的な手法ではある。説明は難しいが、光源からそのモデルを見た時の、モデルの輪郭線に沿った「型」を、そのまんま光源と反対方向に伸ばした立体を作る。つまりこの立体は、光の当たらない場所、影空間を示している。で、この影空間を、今度はカメラ方向からレンダリングして、その手前面と奥面の差分が、影が物体に当たっているところ、として、それを表示する。この目に見えない影空間のレンダリングに、ステンシルバッファという裏画面を使うので、ステンシルシャドウと呼んだりする事もある。
 このシャドウボリュームは結構高級な手法なので、床がデコボコでも正しく表示できるし、またシャドウマッピングとかの従来の手法では不可能な、セルフシャドウ(例えばキャラモデルの腕の影が、そのキャラモデルの体の上の表示されるようなケース)も実現できる。その代わり計算負荷が高い。画面レンダリング相当の処理を複数回行ったり、ポリゴン生成をしないといけないし。更に、最終的には影空間との交差を判定するので、形状によっては破綻するし、カメラがこの影空間の中に入った時にかなり特殊な処理をしないとおかしくなる。
 実際表示した時は感動したのだが、正直私が作っていた格闘ゲームの場合、床は平面だし、セルフシャドウはあると逆にうっとうしいので、シャドウボリュームである事の利点は特になかった。

 その後は普通に、格闘ゲームとしての機能を搭載していった。前後移動、ダッシュ、画面奥手前のいわゆる軸移動、立ちやしゃがみ状態、そしてパンチやキックも出せるようにした。
 パンチやキックのモーションに関しては、前回も書いたように、私が自分で作った。立ちやしゃがみの状態も作ったが、その途中、つまり立ち→しゃがみなどの移行に関しては、そのためのモーションを作らなかった。ここはあえて、モーションブレンディングに挑戦した。これもDirectXの機能だが、複数のモーションを、どれがどのくらいの比率、みたいなのを設定した上でブレンドできる。そうすると各関節の状態は、その比率に基づいて、その複数のモーションの中間の位置に表示される。これを使うと、立ちとしゃがみのそれぞれのポーズを決めておけば、その比率を変えながら表示する事により、移行途中の状態を計算で出せる。
 まあ、しゃがみ⇔立ちの移行だけなら、別にその専用モーションを作っても問題ないし、その方が早い。ただ、「しゃがみでパンチを出して、その戻りの時についでに立つ」みたいな、複数の動きの中間の状態があった方が良い、となった時に役に立つ。…気がしたので、あえて技術テストとして挑んでみた。

 それから、攻撃の当たり判定も作った。本気でやるなら、腕や足などのボーンの、各モーション間の移動空間の交差判定をすべきだろう。その場合、関節部の移動をフレーム毎に積分して、作られた平面あるいは立体を、敵味方相互に交差判定する形になる。
 が、それはあからさまに重いし、そこまで厳密にやらなきゃいけないわけでもないので、ものすごく簡易的にやった。当たり判定を持つ関節を定義し、その関節同士の距離で当たり判定を取った。つまり、パンチをしたときに、こちらの手首あたりの関節を中心にした球と、相手の胸なり頭部なりに仕込んだ関節中心の球とがぶつかったら、当たりという事にする。これで大体納得する結果が得られた。

 上記の、クォータニオン変換の導出メモを見ても分かるように、3Dゲームを作るのに、高校くらいまでの数学の知識はかなり必須だった。ベクトル・行列・三角関数・内積外積は、100%把握していないときつい。これらはどれも必須だが、自分が学生時代弱かった行列が、もう必須中の必須でねえ…。3D空間で何かを表現する時に、各点に対し、平行移動・回転・拡大縮小の計算処理をしないといけない。これらを別々にやるのは面倒だが、4x4の行列で、この「平行移動・回転・拡大縮小」を1セットにした数字の塊が作れる。この行列を掛けてやると、一発でこれらの計算ができる(と言っても、実際には行列計算自体が複数の計算処理を含んでいる。ただ、概念的に一発で処理が済むのと、行列計算に関してはハードウェア的な支援があったりするので、その方がやはり効率が良い)。更に、複数の移動だの回転だのも、行列同士を掛け算する事で一個にまとめられるので、計算量の削減に繋がる。なので、行列の概念があやふやだと、そもそも3Dゲームは作れないと思っていい。
 そういうわけで、まだ取ってあった高校の時の数学の教科書だか問題集だかを引っ張り出して、もう一度読み直した。そうしたら、高校の時あんまり理解できていなかった部分が、するするっと頭に入ってきた。色々周辺知識が増えたというのもあるけど、実際「3Dゲームを作る」という目標に沿って勉強すると、非常に分かりやすかった。行列も、ベクトルも、なぜそれを使った方がいいのかが分かった上なので、頭にすんなり入った。逆に言うと、学生の頃は、「これがどこの役に立つのだろう」と思いながら勉強していたから、頭に入らなかったのかも知れない。

 この後、いわゆるコマンド技(下・右下・右+パンチで波動拳みたいな)を実現するための、キー入力システムを作る予定だった。ただ、このあたりで、この格闘ゲームの開発は終わってしまった。恐らく、普通に仕事が忙しくなる時期に差し掛かったのだろう。細かい事は忘れたが。
 この3Dゲーム開発を通して、色々な事が分かった。Windowsの作法、DirectXの作法、3D表示の作法、ベクトル・行列・三角関数・内積外積やクォータニオンなどの数学的知識、影生成の方法、カメラ制御、光源制御、その他諸々だ。その事は、趣味でも仕事でものちのち役に立った。
 やはり、学ぶ事って重要だな…と思った。


前のMCLへ  次のMCLへ

戻る

TOM 2019