「 スキニング 」一覧

スキニングメッシュのバウンディングボックス

久しぶりにスキニングメッシュ(エンベロープモデル)のネタです。
キャラクターのモデルでバウンディングボックスやスフィアを使って、あたり判定を行っていましたが、
結構無駄な部分が出来てしまい、結果厳密なチェックが出来ないという残念な結果になってしまいます。

かといって、頂点変形をCPUで全ての頂点で行って厳密なバウンディングボックスを作るのもこのご時世に何かあっていないようなそんな気がします。今だとシェーダーでスキニングロジック実装しちゃいますしね。

そんなわけでもう少しまともなバウンディングボックスを作れないかチャレンジしてみました。今回はこの中身に触れたいと思いますが、まずは処理結果の図を以下に示します。

割と各部位に従ってボックスが出来ているのがわかると思います。これが動きに合わせて追従してくれるので、使い勝手はよいかなと思っています。ボックス自体の頂点情報は固定で、単にボーンの行列に合わせて変形されるので処理も結構軽い物となっています。

処理手順

画像を見てもわかるようにDirectXサンプルの定番 tiny.x を使用しています。
そして、SkinnedMeshサンプルの改造です。

  1. 各ボーンの影響頂点群を取得して、各頂点で一番大きなボーンはどれなのかの情報を保持します。
    D3DXにはこの操作に便利な GetBoneInfluence, GetNumBoneInfluence というID3DXSkinInfoのメンバ関数が用意されているので楽に処理できます。
  2. 各頂点を走査して、自身が一番影響を受けるボーンのボーン行列と頂点位置を乗算します。
  3. 計算されたボーンローカルの位置で、そのボーンがもつバウンディングボックス情報を更新します。
    位置が最小、最大なら現在の値で置き換えるという処理。

これらの処理により、各ボーンが自分の影響する範囲でのバウンディングボックスを構築することが出来ます。

その他

動かしてみていると若干つなぎ目部分で隙間が出来ていたりします。
あたり判定に使うにはちょっとまずい場合があるのかもしれません。
あと、ConvertToIndexedBlendedMesh でメッシュを更新したあとだと、GetBoneInfluenceで返ってくる値と食い違いが出て悩みました。どうやらこれは変換前の頂点データ群についての結果を返してくるようです。
変換前後での頂点のマップを用意して処理してあげることが必要でした。


Vertex Texture Fetchによるスキニング

Vertex Texture Fetchを利用して、スキニングメッシュのサンプルを動かしてみました。
ひにけにxnaさんのところでは、XNAを用いての解説があったので、
ここでは、普通のDirectX SDKとC++による組み合わせでこれをやってみたいと思います。

使用するもの

  • SkinnedMeshのサンプルプログラム
  • ShaderModel 3.0に対応したグラフィックボード
    • Radeon X1000シリーズを除く。これはVTFに対応していないため。

ソースコード

各準備

まずは、ボーンのマトリックスを格納するためのテクスチャを準備します。
ここではグローバルにおいて、手抜きしました。
また、毎回書き換えるので、ダイナミックテクスチャにしています。

ボーン用テクスチャの概要

ボーン用のテクスチャは、横方向に使用したいボーンの数、縦方向に4というサイズで作成しました。
というのも、行列は4x4のfloat要素で、今回のテクスチャはRGBA32Fという形式のため、1テクセルに4要素格納できます。
そのため、4テクセル使えば、1つの行列が生成できるということを目的にしています。

横方向(U方向)はボーンのインデックスを示すことにして、
V方向には行列の行を格納することにしました。
このほうが配列っぽく見えるかなと思っています。

マトリックスパレット数調整のための変換部分

またついでに、ConvertToIndexedBlendedMesh を呼び出している部分も
これらの設定のボーン数をみるように修正が必要です。

マトリックスパレットの設定部分の修正

マトリックスパレット設定部分を次のように変更します。

テクスチャにボーンのマトリックスを格納していきます。
注意点としてはテクスチャのY方向へずらすときは、ロックしたときのピッチバイト数でずらさないといけない点です。
それと、シェーダー内ではマトリックスとしては取れず、
float4の4つの組で取れる感じになります。
そのためシェーダーに送る際に転置しておきます。
こうすることで、内積を利用して計算が可能になります。

プロジェクション用行列の設定修正

サンプルのSkinnedMeshのサンプルではmViewProjというシェーダー定数に
実はプロジェクション用の行列しか送っていないです。
ビュー行列はマトリックスパレットに対して適用していました。

この部分を変更します。
mViewProjにg_matProjを設定している部分で、g_matViewとの演算結果を格納するようにしておきます。

シェーダーコード

ほとんどの部分はサンプルのそのままです。
変更点は以下の通り

  • tex2Dlodにて該当するマトリックスパレット相当のテクセルから値を取得
  • 浮動小数用にサンプラを設定
  • VTF使うために、vs_3_0を設定。あわせてピクセルシェーダーも

実行結果

こんな感じになります。
理由はピクセルシェーダーでディフューズ用のテクスチャ等を処理せず、
単純にライトの強度を出しているからです。

しかし、これがVTF利用して動いています。

今は、横方向128でやりましたが、これを256にすることで
ボーンの上限256個までを一度に使うことが出来るようになります。


GLSLスキニング

GLSLでスキニングシェーダー書いてみた

わりとGLSLでスキニングシェーダーをやったという記事を見かけないので
ここで書いてみます。

大変だった点はGLSLでは、Cのようなキャストを受け付けない点。
そのためブレンドインデックスをどうやってint型にするかで悩みました。
実際には、下記のソースコードに示すように int( )で囲ってやる程度で済む話なのですが、
知らないと結構悩みます。

備考

マトリックスを使わずにfloat4 (すなわちvec4)でやっているのは
単に自分のスタイルなだけです。
先輩のシェーダースタイルに影響された可能性が大です。
matrix型のほうが変換がmulで書ける分だけソースコードはすっきりします。
float4の4つという表現だと、シェーダー定数設定をするコードがすっきりします。
またMatrixをfloat4x3型として使っているような場合で効率よくシェーダー定数を使うことが出来ます。

他にはライティングが適当です。
平行光源と若干のアンビエント成分いれたつもりのコードです。

ソースコード(VertexProgram)

1頂点あたり4ボーンのスキニングシェーダーです。
1回のマトリックスパレットは36個です。

36個にした理由は、tiny.xがこの個数で描けるからです。


発想転換、再実装

新・ConvertToIndexedBlendedMesh相当品

アルゴリズムを見直して再作成しました。
前回は4つのマトリックスパレットで分割しようとした際に、
デバッグビルドだとうちの環境で5分くらいかかって、
かつ、描画バッチ数も200弱という悲惨なことになりましたが、
新しい方はそんな状況にはならず動くようになりました。

実行速度はデバッグで計っています。

パレット数 頂点数 描画バッチ数 変換時間
4 4881 24個 4.2秒
13 4578 4個 3.3秒
26 4474 2個 2.9秒

リリースビルドだと、2桁くらいは高速に動いているので
ようやく実用的になったと思います(13個のパレットのRelease時は15msで変換を完了)。
従来方式が複雑かつ長時間かかるという点で使い物にならなかっただけという話ですが。

プログラムコードは400行ほどに(従来コードは700行)。
STLのデータ構造とアルゴリズムを使いまくってます。
そのせいでデバッグでこの速度を出すのが難しかったです。
ただ気を配って書けばSTL使った場合デバッグで重くて使い物にならない!という状況は避けられると感じました。

ポイント

考えをあらためたポイントは、以下の点です

  • 頂点を共有することにがんばらない
  • 変換アルゴリズムはシンプルに

前回の足りなくなったら頂点を複製という点から、頂点は最初から複製前提で考える
という考えに切り替えたのが大きいです。


ConvertToIndexedBlendedMesh調査中

先日の状態よりはうまくいくようになったのですが、
使用できるボーンのマトリックスパレット数が少ないときの挙動でどうにも限界が。

問題点

パレット内でのインデックス割り当てのコンフリクトを解消できない。

頂点において、以前使用したときのマトリックスパレットのインデックスと同じにしなくてはならないのですが、
時たまそのインデックスがコンフリクトして3角形を描けない状況へ陥ってしまいます。

例:
頂点1は、インデックス1,3にボーンID 4,5を割り当てて使用
頂点2は、インデックス0,1にボーンID 6,5を割り当てて使用
このときに、インデックス1にボーンID4,5がコンフリクト

本家ConvertToIndexedBlendedMesh関数

どうがんばってみても自力実装での
ConvertToIndexedBlendedMesh関数がうまくできなかったので、
本家のほうを調べてみました。

使用したプログラムはSDK付属のSkinnedMeshです。データはtiny.xを使用。

パレット数 面数 頂点数
26 6841 4474
13 6841 4578
8 6841 4646
4 6841 4903

ちなみにConvertToIndexedBlendedMesh関数を呼び出す前はこんな状態でした。

面数 頂点数
6841 4432

まとめ

どうやら入力時の頂点の数と、変換後の頂点の数は一致しなくてもよいらしい。
当たり前といえばあたりまえだけど、面の数が変わらないという点で、
インデックスデータの長さも変換前後で変化しないということみたい。

自前変換の手順

自前でやっていた内容をメモしておきます。意外と面倒だったりするので…。

  1. そのマテリアルで描けるデータ列(面データ)の頂点を随時処理
  2. このデータ列全て処理が完了したらループを抜けて、次のマテリアルへ。
  3. ある程度パレットが埋まるまで面単位で処理をしてみる。-(A)
    1. 以前に使用されていた場合の、パレット内インデックスを今回に予約。
    2. (このとき以前未使用なら、自由に再割り当てしてOKということで今スキップ)
    3. 今回のパレットがある程度うまったら、再度(A)ループをやり直す
    4. このとき前回はスキップした部分を埋めていく
    5. 未使用のデータについて現在のパレット内での空きを利用して再割り当てを行っていく

という状態で割り当てていったのですが。
自由に割り当てという部分でもうちょっと調停が必要だったのかもしれません。

まとめ

自分のやっていた方法では、変換前後で頂点の数は変化しません。
一方ConvertToIndexedBlendedMesh関数では、前後で数が変化します。

おそらくコンフリクトしたときに、頂点を再生成し現在の状況にあうように変換しているのではないかと考えられます。
この機構をいれれば、変換はうまくいくのではないかと思います。

もうちょっとがんばらないとダメそうですね。


ConvertToIndexedBlendedMesh相当品

自力で ID3DXSkinInfo::ConvertToIndexedBlendedMesh相当品を作ってみてます。…が、結構難しい…。

設定したマトリックスパレット数で、モデル全体を描画できるように
ボーンコンビネーションテーブル相当品準備するところまでは出来たのだが、
まだまだバグが入ってる。
とりわけ、複数のサブセット描画で共有される境界の頂点がおかしい。

D3DXの機能では頂点キャッシュへの乗り具合や、
ボーン影響度の設定とかも出来てかなり高機能です。
これと同様の機能を自力実装はかなり骨が折れそうです。そこまではきっとやらないです。

また、現状のものでもデバッグ実行状態でtiny.xの変換で20秒くらいかかってしまうので、
全くダメダメですね。


SkinnedMeshで複数描画したい

DirectXのサンプルSkinnedMeshをいじって、
リソースは1つ、描画用インスタンスは複数なんてものを作成してみました。 

色々と、DirectXの仕様で躓く点が多かったように思います。

実現の為のポイント

  • AnimationControllerは単純にCloneで作成しても、ボーン構造(FRAME)は共有されてしまう
    • 独立させたいのは、マトリックスパレット.
    • 一緒にボーンオフセット行列もインスタンスごとに保持させてしまうと、楽にはなる。
  • マトリックスパレットは、MeshContainerが保持するのではなく、一段外に追いやってみる。
    • 自分では FRAMEの中に追い出しました。MeshContainerと並列して存在する感じに。

このような点から、
描画用のオブジェクト生成時には、

  • FRAME構造をコピー(別インスタンス作成)
  • AnimationControllerをcloneする
  • 上記でそれぞれ描画用のインスタンス用としてのデータができたので、関連づける.
    • D3DXFrameRegisterNamedMatrices()を使用する

これで画像のようにインスタンスごとにアニメーションデータを持った感じで
描画ができるようになりました。

面倒といえば面倒なので、要望があったらlib化してしまうかも。


スキニング問題

前からやっていたスキニング、前回までの結果ではうまくできないことが判明し、もがいてます。

出来ないのは下記の理由により。

  •  SkinedMeshサンプルではマトリックスパレットに設定している値そのものがワールドになっている。
  •  結果、ライト位置をモデル空間に変換しただけではNG (全てをワールドでやればOKでしょうが…)

というわけで、スキンメッシュの表示方法そのものを変更しようかと。
点光源にチャレンジしていなかったら、上記の問題点に気付かずに放置していただろうなーと思うと運が良かったのかな。