2012年

3月

22日

XNAでMMDを読み込むコンテンツパイプライン(一年前のネタ)

ほぼ一年位前、MMDfromColladaやGTAViewerの開発を始める前に作ってたXNAのコンテンツパイプラインを色々と修正してみた。

 

これは元々『独自クラスを作らずにXNAの基本クラスである"Modelクラス"にモデルをインポートする』という目的で始めたものです。XNAでMMDを読み込むコンテンツパイプラインだと有名どころでMMDXがありますが、確かあれは独自クラスを使ってましたね。趣旨に反するので使用していません。

 

XNAでのアニメーション実装には「ひにけにXNA」様のTexSkinningSampleライブラリをちょっとだけ改造して使用してます。

で、修正内容。以前はモデルファイル名とモーションファイル名を書き込んだ以下のような独自フォーマットを作ってパイプラインに読み込ませてました。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="shift-jis" ?>
<root>
  <model source="初音ミク-1052C-Re ノーマルEHs1.pmd" />
  <motions>
    <motion name="踊り">nyanyanya.vmd</motion>
  </motions>
</root>

このスタイルだと、複数体のモデルで同じアニメーションをさせるためには同じvmdを何回も読み込む必要があります。キーフレーム数が多いデータだ とビルドだけで10数分異常掛かっていました。なのでこのスタイルは廃止して、pmd・vmdを別途パイプラインで読み込んでゲーム側で両者をくっつける ようにしました。まぁMMDXと同じ形式です。

 

これでそれぞれをパイプラインへ送ると、

.vmd→SkinnedModel.AnimationClipクラス

.pmd→Microsoft.Xna.Framework.Graphics.Modelクラス

※Model.TagはSkinnedModel.SkinnedDataを付属 SkinningData.AnimationClips要素は無し

 

と、このように変換される。vmdを読み込んで出来たAnimationClipをSkinningDataに追加すればアニメーション出来るようになる。

 

他にも無駄な処理を色々と省いたおかげで結構ビルド速度が上がってます。

あとはGTAViewerやMMDfromColladaの開発経験を活かして読み込み方やシェーダーをちょっと変えて、最終的にこんな感じになりました。

実行環境:

OS:Windows XP SP3

GPU:ATI Radeon4670

バックバッファ 1024×768 32bit

深度バッファ 24bit ステンシルバッファ 8bit

アンチエイリアス マルチサンプル 8個

ミップマップなし

モーション情報
モーション名 再生時間 総キーフレーム数
sweetmagic-left 3分33.267秒 26,975
sweetmagic-right 3分33.267秒 27,110

 

モデル情報
モデル名 総頂点数 総ポリゴン数 総材質数 総ボーン数
ワイルドタイガー ver091 23,394 23,117 23 121
バーナビー・ブルックスJr 21,887 23,899 21 112
レアさんステージ 33,004 29,770 51 3

 

 

MMDfromColladaとGTAViewerにも実装してない機能、『地面影』『エッジ』の処理を入れました。エッジは少し太めにしてあります。有ると無しとじゃ、見栄えが違いますね。

 

地面影とエッジはそれぞれシェーダーを組みました。地面影はステンシルシャドウ、エッジは座標を法線方向に少し伸ばしてカリングを反転させただけです。

 

昔深度でエッジを判断するシェーダーを書いたことあるんですけど、こっちのほうが簡単で綺麗に見えますね。ステンシルシャドウも綺麗ですね。地面がデコボコでなければデプスバッファシャドウより使えます。

 

しかし、同じモデルを1フレーム辺り3回描画しているのにもかかわらず普通に60FPSで動いてたので驚きました。意外に速いですね。

 

どちらも結構簡単に出来るので、GTAViewerやMMDfromColladaにも処理を加えてみようかな?

 

そういえば昔、プロパティ欄に独自項目の設定方法をどのようにやればいいのかわからずに困っていたのですが、

これ→

どうやらプロセッサでインスタンスプロパティを宣言するだけでいいようです。簡単ですね。

 

私はトゥーンマップのフォルダパスを個別に設定できるようにしてみました。他にも材質名リストのファイルを指定したり、あらかじめコチラでモーションを指定しておくと便利かもしれません。ちなみにこんな感じになります↓

1
2
3
4
5
6
7
/// <summary>
/// トゥーンファイルのフォルダ
/// </summary>
[DisplayName("トゥーンマップのフォルダ名")]
[Description("フォルダ名を指定しない場合、共通トゥーンは反映されません。")]
[DefaultValue(typeof(string), @"")]
public virtual string UnionToonFolder { get; set; }

しかし未だにIKボーン処理が導入されていない・・・セルフシャドウも入れてみたい。

これで物理演算も入れればMMDと同じように動くのかな・・・?

髪とか足とかちゃんと動くようになったら面白そうですよね。がんばろう。

追記:

乗算アルファ、lat式ミク関係でブレンディングを色々といじって気がついたけど、どうやらXNA4のBlendState.AlphaBlendはDirectXのアルファブレンドと設定が異なるようです。知らずにそのまま使ってどれだけlat式ミクに苦戦させられたか・・・

 

DirectXだとアルファブレンドの設定はこうです。

1
2
3
graphicsDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); // ブレンディング処理方法
graphicsDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); // ソース側
graphicsDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); // 対象側

XNA4のBlendState.AlphaBlendの設定はこうです。

1
2
3
graphicsDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); // ブレンディング処理方法
graphicsDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); // ソース側
graphicsDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); // 対象側

ちなみにDirectXのアルファブレンドと同じ設定になるのはBlendState.AlphaBlendではなくBlendState.NonPremultipliedのようです。

コメントをお書きください

コメント: 5
  • #1

    a (金曜日, 23 3月 2012 10:27)

    頑張っていらっしゃいますね。結構軽そうでよさげ。
    MMDXはver2になって動的頂点バッファをベースにした
    クラス構成にかわってるようです。独自Modelというのを
    問題にされてますが、独自にすることでCPUスキニング・モーフィングができたり様々な利点があるので見直されてもいいかも。乗算アルファの件もMMDXのほうでも言われてたので、物理やIKではあっちを参考に独自実装・改良するってのもアリでは?あっちはver2でセルフシャドウをオミットしているので、
    そういうところでも差別化図れるかと。

  • #2

    ze10 (金曜日, 23 3月 2012 20:08)

    アドバイスありがとうございます。

    CPUスキニングやモーフィングですが、Model.Meshes.ModelMeshPart.VertexBufferで頂点情報の読み込み・書き出しが出来るので今のままでも実装できると思います。モーフィング情報はSkinndeModel.Keyframeのようなモーフ用キーフレームクラスを新たに作るつもりです。

    物理やIKも同じようにSkinnedDataクラスに付け加えるつもりです。SkinnedDataには元々ボーンの形状データが入ってるので一緒にしてしまいます。

    物理エンジンはXNA用のBullet Physicsライブラリがあるようなのでそれを使うつもりです。MMDXは独自で移植されてるようですが、私にはそれを行う知識と技術がないですからね・・・

    もし実行速度に問題があったりModelクラス、SkinnedModelライブラリだけで限界を感じたら独自クラス化するつもりです。

    でも独自クラス化するなら既にMMDXがあるからなぁ・・・

    MMDXはセルフシャドウないんですか。ちょっと実装してみようと思います。

  • #3

    a (金曜日, 23 3月 2012 21:55)

    CPUスキニングの場合、VertexBufferのままだと速度的に問題が生じるはずです。MMDXではDynamicVertexBufferを使ってるようだし、それをGPU処理が衝突しないようWritable~という独自クラスでラップしてるみたいです。Modelでもできればそれでいいと思いますが。
    独自クラうスだのなんだのはこだわる必要ないかと。Modelクラスを使うメリットって他ライブラリとの整合くらいしか思い当たらなけど、本格的なライブラリやエンジンって、どこも独自モデルクラス定義してる印象が…。もち、別にze10さんのポリシーに口だすつもりはないですよ。

    BulletはBulletSharpがいい感じですね。だけどMMDXの
    BulletXもMITライセンスなんでそれでもいいんじゃないかと。WPやXBOXでも動かすならこっちかBulletXNA(やや低速な感じ)かな。あるいはBulletじゃないけどBEPUが相当イケてるっぽいです。MMDアダプタつくってもらえると私が嬉しいですw

    MMDXver2は全体的に手抜きの印象ぽくて(就職されたためか、
    CPU重視方針のためか?あるいはUnity移行のためか)、
    GPU処理がかなり省かれてるようですね。GPU重視すれば全く別方向のライブラリになるといえるかと。

  • #4

    a (金曜日, 23 3月 2012 22:23)

    すんません。ブログよく読んでたら、ModelやBulletについては
    愚かなレスしてるっぽいす。
    上の内容はお忘れになってくださいなw

  • #5

    ze10 (金曜日, 23 3月 2012 22:35)

    いえいえ、いつも為になる情報をありがとうございます。とても勉強になります。
    DynamicVertexBufferクラスとかあるんだ・・・知らなかったです。

    MMDXについても知らないことが多そうなので、MMDXのソースを今一度見直してみようと思います。