DirectXの話 第29回
「ボボンボンボン」
ラリパッポラリパッポ、ラーリラリラリ、ラリパッポ。
この歌(?)っていったい何なんでしょう?
よくわからないのですが、誰もが知ってるんですよねぇ。不思議不思議。
ボーンとかけてる以外に意味はありませんよ。
さて、今回は前回予告の通りにインデックス付き頂点ブレンディングをやっていきます。
頂点ブレンディングは前回説明したとおりですが、インデックスが付くことでより高度なことが出来るようになります。
たとえば、前回の方法では関連づけられる行列は4つまでです。
しかし、人体モデルなどの場合、ボーンは数十本存在します。
それらを4つにまとめることなど不可能ですし、足のボーンの影響が頭にある頂点に働いてもらっても困ります。
そこで、このインデックス付き頂点ブレンディングを使用します。
これを使用すれば好きなボーン(と言うか行列)を各頂点に好きなように関連づけることが出来るようになります。
っていうか、普通の頂点ブレンディングは使い道が薄いんですけどね。
では、プログラムの方から見ていきましょう。
まずはビデオボードの性能をチェックするところから始めます。
インデックス付き頂点ブレンディングを使う場合、前回のチェックは役に立ちません。
| if(caps.MaxVertexBlendMatrixIndex < 1){ behavior = D3DCREATE_SOFTWARE_VERTEXPROCESSING; } |
D3DCAPS8 の MaxVertexBlendMatrixIndex は使用可能なインデックス数を示しています。
正確には、MaxVertexBlendMatrixIndex + 1 が使用可能なインデックス数になります。
今回のサンプルでは最大2つのインデックスを使用しているので、この値が
1 以上でないならハードウェアTnLは使用できません。
ちなみに、うちの環境では使用不可です。GeForce3以上が必要かな?
次に頂点データ形式を指定します。
これは前回と似ているんですが、けっこう違うので注意が必要です。
| typedef struct _D3DBLDVERTEX{ float x, y, z; float blend; DWORD index; DWORD color; } D3DBLDVERTEX; #define D3DFVF_BLDVERTEX (D3DFVF_XYZB2 | D3DFVF_LASTBETA_UBYTE4 | D3DFVF_DIFFUSE) |
まず目に付くのは構造体のメンバ index と D3DFVF_LASTBETA_UBYTE4 と言うフラグでしょう。
これらがブレンドインデックスに関わることは予想できると思いますが、もう一つ前回と違うところがあります。
D3DFVF_XYZB2 と言うフラグがありますが、何か変だと思いませんか?
これは構造体中のブレンド値の数だったはずです。前回はブレンド値が1つなので
D3DFVF_XYZB1 でしたね?
今回もブレンド値は1つしかありません。なのに、なぜ2つ分指定されているのか?
ここに D3DFVF_LASTBETA_UBYTE4 が関わってきます。
このフラグの意味は、「ブレンド値のうち、最後の一つはBYTEオーダ4つのDWORD値である」というものです。
わかりにくい? 私もそう思います。
つまり今回の場合、「ブレンド値は2つあるけど、最後の1つはインデックス値なんです、テヘ♪」とDirectXに通達してるんですよ。
なぜ、わざわざこんな面倒なやり方をしているのかは私にもわかりませんが、こうしなければいけないのでこうしてください。
で、描画前のレンダリングステートの設定ですが、ここでも追加があります。
| g_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE); |
これはインデックス付き頂点ブレンディングを有効にしています。それだけ。
当然、この命令を実行しないとインデックス付き頂点ブレンディングは使えません。
前回設定したウェイト値の数ですが、これはそのままでいいです。
頂点データではウェイト値は2つと指定していますが、実際に使用されるウェイト値は1つだけなので問題ありません。
これで描画すれば問題なく表示されるはずです。簡単でしょ?
とはいえ、さすがにこれだけじゃ寂しいなぁ、と言うことで、ボーンの基礎的な部分も少し説明しておきます。
ちなみに、msystem の方でも同じようなことをやっているので、実装方法などはそちらを参照した方がいいと思います。
サンプルプログラムでは3つのボーンが存在するものとしていますが、説明の方では簡単に2つのボーンの親子関係をやります。
親子関係についての説明はdevilmanさんの宇治社中を参考にしてください。私の説明より数倍わかりやすいです。
まず、ボーンAとBというものを仮定します。ここで、AはBの親であり、Bの根本はAの先端にくっついているものとします。
図で描くとこんな感じ。

図が汚いとかわかりにくいとかいうのは気にしない方向で。
このような親子関係のあるボーンはちょっと面倒で、たとえばボーンAが回転すると、その先端に付いているボーンBも回転することになります。
しかし、ボーンBが回転したとしてもボーンAは動きません。親の特権です。
また、ボーンBが回転した場合、ボーンBに関連づけられている頂点はボーンBの根本の点を中心に回転します。
このような親子関係はボーンだけではなく、オブジェクトに対してもつけることが出来ます。
では、まずボーンAに関連づけられている頂点を考えてみましょう。
ボーンAの根本が原点にあるとするなら、この頂点座標の変更は極めて簡単です。
ボーンAの平行移動・回転に会わせて頂点座標の変換行列を求めるだけでいいわけですから。
問題はボーンBに関連づけられている頂点の場合です。
ボーンBの根本は原点にはありませんが、そのまま回転させてしまえば原点中心で回転してしまいます。
この場合どうすればいいかと言えば、まずボーンBの根本の点を原点まで平行移動すればいいと言うことになります。
これでまず平行移動行列が出来ますね。
ボーンBの根本が原点にきたなら、回転させて問題ありません。これで回転行列が出来ます。
しかし、このままではボーンBの根本が原点にきたままになってしまいます。
そこで、ボーンBの根本を元に戻すように平行移動し直します。
このようにして作成された行列を順序よく頂点に積算していけばボーンBに関連づけられた頂点を変換することが出来ます。
最初の平行移動行列を matOffset、回転行列を matTrans、最後の平行移動行列を matInvOffset とすると、変換後の頂点 Vout は以下のようになります。
Vout = Vin * matOffset * matTrans * matInvOffset
matOffset と matInvOffset はそれぞれ逆行列の関係になります。
また、これらの行列はすべて平行移動とは限らず、親が回転している場合は回転要素も加えないとローカル座標が回転した状態になってしまいます。もちろん、スケーリングも同様です。
これはボーンAが原点にあり、ボーンBがボーンAの子である場合の計算式です。
実際のデータでは、あるボーンの親が別のボーンの子である場合もあり、こんなに単純な式にはなりません。
一般的な式は以下のようになります。
Vout = Vin * matOffset0 * matOffset1 * ・・・
* matOffsetn * matTransn * matInvOffsetn * matTrans(n-1) * matInvOffset(n-1) * ・・・ * matTrans0 * matInvOffset0
複雑そうに見えますが、規則的に並んでいるのでそれほど難しくないでしょう。
また、親ボーンの matTrans * ・・・ 以降の計算が終了しているのであれば計算量を減らすことも出来ますし、頂点データをあらかじめボーンのローカル座標値に変換しておくという手もあります。
そのあたりは自分で使用するデータの形式などを基準として各自工夫してみてください。
サンプルプログラムの方の詳しい説明はしませんが、上記の計算を行っているだけなので難しいことはないと思います。
では、そのサンプルプログラムです。
操作はテンキーの 1〜3 で変換行列0の回転、4〜6
で変換行列1の回転、7〜9 で変換行列2の回転となります。
また、カーソルキーの上下左右で全体を平行移動させることも出来ます。
どのように親子関係が付いているのかわかりやすいと思います。
さて、今回はこれで終了。
次回は・・・予定なし。
DirectXの話より msystem の方をもう少し改良して、簡単なビューアを作ろうと思っています。