今回のレッスンは、物理演算ライブラリ Bullet の導入です。
スキンメッシュアニメーションの実装に比べると格段に難易度は下がるので
プログラマでない方も本レッスンで遊んでみてはいかがでしょうか?
今回はこのイラストのように、机に球?をパラパラ落とすデモを作ります。
シミュレーションと言えば、イメージがわくのかな?
ゲームにおいて、それは次のフレームにおけるオブジェクトの姿勢を計算するということ。
なるべく正確に、そして高速にシミュレーション結果のオブジェクトの姿勢を取得したい。
そんな夢を叶えてくれる無料かつ商用利用可能なライブラリはあるのでしょうか?
探してみると結構あります。
そこでみなさんは Bullet Physics の存在を知ることになるでしょう。
ライセンスは Zlib License なので、ソースコードの公開義務なく販売することができます。
では簡単に導入テストをやってみたいと思います。
今回の成果物がこちら↓
起動に失敗する方は DirectX End-User Runtimes (June 2010) をダウンロードして、セットアップを行ってください。
展開するだけではダメで、展開したあと DXSETUP.exe を実行してください。
アプリの遊び方:マウスのドラッグで視点の移動 SPACE キーで球体を中央から落下させます。
まず Bullet Physics から ver 2.78 の zip ファイルを落としてきます。(左上の Download から)
解凍したら msvc フォルダの 2008_dx11_directcompute にある.slnを開いてビルドし、.lib ファイルを作成します。
ここでプロジェクトプロパティのC++→コード生成「ランタイムライブラリ」を
使用したいプロジェクトのものと合っているか確認し、違っていたら同じにします。
このとき作成される .lib ファイルがこちら
あとはインクルードパスを通して、作成したライブラリを静的リンクして
次のコードがビルドできるかテストしてみます。
ビルドを確認できましたでしょうか?
ということで、ドキュメントを使って詳細を確認しながら使い方を学んでいきましょう。
Bullet User Manual and API documentation
読み進める通り、最初は HelloWorld プロジェクトをやってみてください、とのこと。
さっそく次のコードを実行して、結果をリダイレクトし、ボールの軌跡とやらを追ってみました。
HelloWorld のコード
ボールの軌跡がこちら
ドキュメントを読まずとも、HelloWorld のコードを読んでいけばこの辺は問題ないですね。
続いてドキュメントで紹介されている、Debug Drawing を実践してみましょう。
Bullet には元々、btIDebugDraw クラスが用意されていて、こちらを継承して
純粋仮想関数をオーバーライドすると、簡単にシミュレーションの描画結果が得られるというのです。
さっそく試してみましょう、ということで
SimpleDX10 に必要な機能を追加します。
具体的には次の関数にて呼び出す処理を追加するのですが↓
…ということで SimpleDX10 を ver 1.04r1 に更新しました。
ここで、SimpleDX10 の導入を丁寧にまとめたいと思います。
入手場所はいつもの Download ページより SimpleDX10 の ver1.04r1 を落とします。
展開したら、include フォルダを追加のインクルードディレクトリに設定し、
lib フォルダを追加のライブラリディレクトリに設定します。
あとは、プロジェクトごとに当てはまる.libファイルを静的リンクすれば準備は完了です。
静的リンクについてよくわからない方は 33話 の「外部ライブラリを使用する Windows アプリケーション」
を参考にするとわかると思います。(同じ SimpleDX10 の静的リンクの方法なので)
ではテスト用に
先ほどの HelloWorld プロジェクトに「プリコンパイル済みヘッダーファイル」を加え
さらに SimpleDX10 も静的リンクして環境を用意します。
加えたプリコンパイル済みヘッダーがこちら↓
何も難しいことなんてないです。
では、正しくリンクできているか試してみましょう。
ということで、次の確認用コードを Do some simulation のコメントの下に追加します。
リンクエラーが起きると思うので、winmm.lib を静的リンクして対処します。
これは、timeGetTime 関数などに必要です。
実行結果の様子がこちら↓です。
ウィンドウを作成して、メインループにて
画面のクリア → FPSの描画 → 更新処理を行っています。
正しく動けば図のように、コンソール画面とウィンドウが表示されることでしょう。
さて、ここまで確認できましたら線の描画を行ってみましょう。
2点の3次元位置と色を指定すると線が描かれるようにしたいところですが
SimpleDX10 にはそのような関数は用意されていません。
基本図形描画関数を追加しようと考えてはみたのですが、やめました。
描画方法が一つの方がシンプルでわかりやすいからです。
いくつか準備が必要になりますが、順に作業を追って説明していきましょう。
みなさんは HLSL をご存じでしょうか?(なんですかそれって方は一緒に勉強していきましょう)
HLSLとはシェーダ言語のことです。
以下に今回のライン描画用のシェーダ言語で書かれたエフェクトファイルを示します。
描画にはまずこのエフェクトファイルが必要です。
エフェクトファイルの書式に困ったら HLSLの使い方 に行けば問題が解決します。
これだけの情報があれば、入門書は不要ですね。
また、一番最初にエフェクトファイルに触った時のレッスン 033話 エフェクトファイル入門 があるので参考にしてみてください。
さて、このエフェクトファイルを使うのですが
当然コードの中で、このエフェクトファイルを読み込むわけです。
まず最初は原点を通る縦一本のラインを描画して、
-Z方向の位置からその線を見た結果を確認したいと思います。
コードはこんな感じになります。
このコードの実行結果は次のようになります。
では解説していきます。
ハイライトしている部分を追っていくのですが、最初の16行目
エフェクトファイルを登録する部分はOKでしょうか?
SMDX10.AppendEffect 関数にて、先ほど作成したエフェクトファイルを指定します。
引数の説明等は SimpleDX10 の doc フォルダにある SimplestarDX10_Reference.chm を参照してください。
API リファレンスなので、関数名で検索をかけることで一発で詳細情報に飛べると思います。
最初の関門ですが、エフェクトファイルの記述が正しくないと読み込み、解析に失敗します。
SimpleDX10 で正しく解析するためには、ファイルヘッダ部に "SimplestarFX" のマジックコードが無いといけません。
次に頂点シェーダの入力情報構造体が "struct VS_INPUT" で宣言されていないといけません。
また、セマンティクスにはインデックスを必ず振らなければなりません。(一番最初なら0を付加しなければなりません。)
以上のルールに従ってエフェクトファイルを書くことにより SimpleDX10 は自動で頂点レイアウトをデバイスに設定します。
ただし、解析に失敗しても SimpleDX10::CreateVertexLayout 関数で指定のエフェクトに対して
頂点レイアウトを設定できるので、後でどうにでもできます。
(頂点レイアウトの設定をスキップできる便利な方法を先に紹介した感じです。)
続いてのハイライト行 24行目のエフェクト変数のアクセス子の取得について説明します。
エフェクトファイルで "cbuffer cbChangesEveryFrame" で囲った毎フレーム更新する変数を宣言したと思いますが
こちらの変数へのアクセス子を取得しておきます。それだけです。
描画ごとに毎回取得するのはかしこくないので、あらかじめローカルで持っておきます。
次のハイライト行 40行目、48行目、56行目ではバッファの作成と描画での必要事項の入力を行っています。
頂点位置はエフェクト変数で設定するので、開始点と終端を判断するインデックスを入れるだけです。
ちなみに 0 が開始点、1 が終端を意味します。
(エフェクトファイルの分岐ですが、今回のトポロジはラインリストなので順番は関係ありません)
最後に描画処理にて、ハイライトしている 104 行目から 152 行目までに注目してください。
エフェクト変数に値を設定してから、描画関数を呼びます。
エフェクト変数は毎フレーム変更しても結果が反映されるので、試しに時間ごとに色が変わったり
位置が移動するアニメを試してみるのも面白いかもしれません。
いかがでしたでしょうか?
面倒な処理が多いですが、これが SimpleDX10 で線を描画する最小のコードです。
シンプルとは程遠いかも知れませんが、DirectX10 の煩雑な処理を相当スキップしているので
一応簡単にはなっています。(たぶん)
はい、では SimpleDX10 のライン描画方法が明らかになったところで
さっそく Bullet の DebugDrawing を行ってみましょう。
まずは btIDebugDraw クラスを継承する DebugDraw クラスを作成します。
単純に継承したクラスだけだと「抽象クラスをインスタンス化できません。」と怒られるので
純粋仮想関数をオーバーライドしましょう。
…で、作成した DebugDraw クラスがこちら↓
気を付ける部分は線の描画の部分だけです。
線の描画の部分は先ほど詳しく説明したので、わからないことがあったらそちらを参照してください。
視点やパースを変更したい場合は、ビューとプロジェクション行列を設定してください。
実行結果はこんな感じになります↓
クリックで拡大できます。
デバッグ描画を使用する側は次のように記述して使います。
球一個を地面に落とすだけならば、1000 fps 出るようですね。
もう少し遊んでみましょうか…
まずはキー入力を処理する機構を設置しないといけませんね。
今回は DirectInput を使ってキーボードのキー押下イベントを取得したいと思います。
DirectInput については以下のページに詳細がまとめられています。
こちら↑のページを見てあっさり導入できる方は以下の説明を読む必要はありません。
準備としてプリコンパイル済みヘッダに Dinput.h のインクルードの行を追加します。
プロジェクトプロパティから追加の依存ファイルに Dinput8.lib, DXGUID.LIB を加えます。
そして以下のコードを参考に DirectInput のオブジェクトとキーボードのデバイスを作成し
いろいろ設定を行って、デバイスを取得(Acquire()に成功)できることを確認します。
あとはメインループにて、指定のキーが押されていることを確認した場合に
DynamicsWorld に剛体を追加するコードを書き加えます。
(直前に追加した剛体がある程度落ちたところで次を追加するように書きました。)
下図は球体を上からどんどん降らせたときの結果画像です。
(クリックで原寸拡大)
Debug 版で球が 14個程度ならば 200fps 出ました。
どんどん球を足していくと 50個くらいで 60fps に落ちてしまいました。
マシンにもよると思いますが、なるほど Bullet の速度はこんなものですか…
リリース版にすると、もっと速度は出ます。詳細は次を参照してください。
キーボード入力を参考にマウス入力も用意してみましょう。
ということで、まずは DirectInput
のドキュメントの通りマウスデバイスの準備コードをメインループの前に記述します。
あとはメインループにマウスの状態を逐一報告するコードを追加するだけです。
これだけじゃ味気ないので
マウスで DebugDraw の視点を変更できるようにしてみましょうか。
カメラ操作できるようにした HelloWorld.cpp
どうやって実現したかが気になる方は、上記のソースコードを参考にしてください。
重要な部分をハイライトしてあります。
これ、少し遊べるものなので成果物を公開しておこうと思います。
冒頭と同じものですが、遊んでみたい方はこちらで試してみて下さい。
遊び方:マウスのドラッグで視点の移動 SPACE キーで球体を中央から落下させます。
球が転がる様子を観察するのが結構楽しかったりします。
パソコンの性能とか気になる方は球の数と FPS の関係を確認してみてください。
リリース版だと結構速度出ました。
100 個の球を計算しても、100fps 以上出ていました。
さて、今回はこれくらいにしておきます。
続いて Bullet のマニュアルを読み進めると今度は Collision Detection (衝突検出)とあります。
次も今回同様 Bullet の入門編を続けたいと思いますので、お楽しみに!
2011/08/07 初記.
2011/08/20 最終更新。