スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

スキンメッシュアニメーションの解説その5 (最終回)

【リンク】 ◆解説系TOP◆

やっと書きます。
スキンメッシュアニメーションの解説 最終回です。

で、今回はスキンメッシュアニメーションが
どういう流れで実装されているかを順番通りに見て行く感じでいきます。

注意:個人的な実装方法なので、定石みたいなものと やり方が違うかもしれません。
     頂点ブレンディングとか使ってないですし。



まず、復習を兼ねて今回使用するXファイルの大まかな説明をします。

【今回使用するXファイル】 ⇒ SkinSample.txt
("RokDeBone2"で制作後、インデントを付ける等 分かりやすいように改変を加えました。)

※適宜 拡張子を.txtから.xに変更してください。


----【Xファイルの簡易説明】----------------------------------------------------------
始めの xof 0303txt 0032 はヘッダと呼ばれるものです。 無視してください。


次の AnimTicksPerSecond というのは、
1秒間に何コマのアニメーションをするか?の指定です。
(今回は1秒間に、60コマと指定。 1秒間に60回 画像が切り替わります。)

ついでに、このAnimTicksPerSecondを削除し、Xファイルビューアで見ると
物凄い高速のアニメーションをします。


その次は、Frameの階層構造が定義されてます。

~ Xファイルの全体像 簡易Ver ~

Frame TheSource {
FrameTransformMatrix { 行列 }
Frame Child {
FrameTransformMatrix { 行列 }
Mesh {
頂点情報
法線情報 (MeshNormals)
テクスチャ座標情報 (MeshTextureCoords)
マテリアル情報 (MeshMaterialList)
頂点重複情報 (VertexDuplicationIndices)
スキニング情報 (XSkinMeshHeader)
SkinWeights { "TheSource" }
SkinWeights { "JointA" }
SkinWeights { "JointB" }
}
}
Frame Root {
FrameTransformMatrix { 行列 }
Frame JointA {
FrameTransformMatrix { 行列 }
Frame JointB {
FrameTransformMatrix { 行列 }
}
}
}
}

Frame TheSource というのが、一番の親の階層になります。

この親の持つ行列を変更すれば、
3Dモデル全体が 回転・平行移動・拡大縮小などをする ことになります。
(親子関係が定義されている場合、親が変更されると子の階層も影響を受けるのです。)

注意!:Xファイルビューアで見る場合親の行列を 平行移動しても変化はありません。
    これは変化していないのではなく、
    Xファイルビューアが自動的に3Dモデルの全体像が見える様にカメラを設定する為
    です。

    (3Dモデルの座標自体はちゃんと変わってます。 同じように表示されたからといって、
    「あれ、平行移動(オフセット)してない?」と誤解しないように注意しましょう。)


で、Frameの "Root""JointA""JointB"がそれぞれボーンになります。
見て分かるように、ボーンもFrameの一種なのです。


で、このXファイルを図にすると、 下記の様になります。

【参考画像】
SkinSample.jpg
-----【補足説明】-------------------------------------------------------------------------
・頂点0~5の、合計6つの頂点で 横長い長方形が定義されている
・頂点1と頂点2は、影響度が設定されている。 (それ以外は、どちらかに100%影響する。)
・ボーンはJointA と JointBの2つ。

Rootが親、JointAが子、JointBが孫の関係。
               図にRootが載っていませんが、Rootの役割は後述します。)
----------------------------------------------------------------------------------------

最後に、Mesh情報の中にある頂点重複情報スキニング情報について。

頂点重複情報(VertexDuplicationIndices)は、どの頂点が重複しているかを示します。
3Dモデルの頂点数を減らして単純化する時などに使われる情報との事。

スキンメッシュアニメーションとは直接関係無いので 無視して結構です。
一応、参考URL ⇒ VertexDuplicationIndices について


スキニング情報(XSkinMeshHeader)は、頂点に影響を与えるボーンがいくつあるか?などを指定します。
「1つの頂点にボーンが10個も20個も影響すると座標の計算が面倒だから、最大4つまでね。
みたいな。

自分は、影響するボーン数は最大で4つと個人的に決めたので、
実は この情報も要りません。無視して結構です。
(注:Xファイルビューアで見る場合は必要です。 削除してはいけません。)
これも一応、参考URL ⇒ XSkinMeshHeader について

--【Xファイルの簡易説明 終】---------------------------------------------------------------


Xファイルの復習が終わったので、次は処理する順番を書きます。

======================================================
【1】"Frame"が出てくる度に、階層構造を作っていく。
【2】Mesh情報があれば、 Mesh情報を読む。
【3】Mesh情報の中のSkinWeightsの情報で、ボーンコンビネーションテーブルを生成。

======================================================

まず、階層構造の作り方は、個人個人にお任せします。

自分は、構造体のvector配列で実装しました。
ChildとBrotherという変数を作って、それぞれ親子関係・兄弟関係のFrameがあれば
そのFrameの添え字を代入する、という方式です。
(無ければ-1を入れておく。)

【参考画像】
Frame階層



次に、Mesh情報の読み込みについてですが、
始めは、とにかくデータをvector配列に入れていきます。
一時的な情報を保持(バッファ)しておくのです。

一時的に保持(バッファ)しておくもの
--------------------------------------------------------
・頂点バッファ
・頂点インデックスバッファ
・法線バッファ
・頂点色バッファ
・UV座標バッファ
・マテリアル番号バッファ

--------------------------------------------------------

で、全ての法線や頂点情報などを読み込み終わったら、
----------------------------------------------------------------------------
頂点インデックスバッファに従って、 頂点・頂点色・UV座標 の情報をセット
法線インデックスバッファに従って、 法線 の情報をセット
マテリアル番号バッファに従って、 インデックスを分類

----------------------------------------------------------------------------
します。

※マテリアル番号バッファによるインデックスの分類 について補足。
これは、効率のよい描画をする為に利用します。

例えば、半透明不透明とか、赤色のテクスチャ青色のテクスチャがあった場合、

半透明 ⇒ 不透明 ⇒ 不透明 ⇒ 半透明 ⇒ 不透明
 ⇒  ⇒  ⇒  ⇒  ⇒ 
↑このように何度もマテリアル情報を切り替えて表示するよりも、


不透明 ⇒ 不透明 ⇒ 不透明 ⇒ 半透明 ⇒ 半透明
 ⇒  ⇒  ⇒  ⇒  ⇒ 
↑このようにマテリアルごとに纏めて表示して行った方が処理が早かったりします。

これは、マテリアルの頻繁な切り替えは、時間を食うためです。
その為、予め何番のマテリアルを使うかによって分類しておいた方が良いのです。


<おまけ> ヤケクソ状態で作った、インデックスを生成する部分のコード。
CreateIndex.jpg

変数の名前も適当になってしまってますし、
もし法線やUV座標を持たないXファイルを読み込んだら一発で壊れるコードです。
(vectorの要素数が0の状態になり、範囲外アクセスする為)


※真似しないで下さい。 そして、近々 修正しておこうと思う。

で、Meshの情報の最後にSkinweights(ボーンの影響度などの定義)があります。
コレをもとにボーンコンビネーションテーブルを生成します。

ボーンコンビネーションテーブルの作り方
-----------------------------------------------------------------------
【1】あらかじめ、ボーン・影響度の変数を持つ構造体を定義
【2】頂点の数だけ、vector配列を作る。 (今回は、配列の要素数は6。)
【3】一番の親の行列で初期化。 それ以外にはUINT_MAXを入れておく
【4】Skinweightsの情報によって、適宜 ボーンコンビネーションテーブルを修正。

-----------------------------------------------------------------------

図で見たほうが分かりやすいので、図も描きます。

~初期状態~

BoneTable2.jpg

「ボーン0番って何?」と思うかもしれませんが、これはFrame TheSourceの事です。
自分は階層構造をvectorの配列で実装したので、
階層構造の配列の添え字 = ボーン番号としてます。
(詳細は前述の、Xファイルの簡易説明の後にある図をご覧下さい)

最初は全ての頂点がFrame TheSourceに 100%影響されるようになってます。
つまり、3Dモデルが普通に動く状態です。
(90度回転させたら、モデルが変形せず そのまま まるごと90度回転します。)


で、初期化後は、JointAに●%の影響を受けるという風に適宜 修正すればいいだけなのですが、
これが 案外 厄介(´д`)

かなり上の方に記述した、Xファイル全体像の図を見て下さい。
"TheSource"のSkinWeightsの後は"JointA"、"JointB"のSkinWeightsがありますね。

"TheSource"は一番の親なので、階層構造配列の添え字が0だと分かってます。

しかし、SkinWeightsを読み込んでいる時点では、
"JointA"、"JointB"の階層構造は不明です。
Frame JointAFrame JointBは、SkinWeightsの後に記述されているため。)


なので、他のMesh情報 同様に一時的な情報の保存(バッファ)をしておく必要があります。
階層構造を全て読み終わった時に、「"JointA"って添え字番号 何番ですか?」と言う風に、
階層構造のvector配列を 線形探索しなければなりません。

・ボーンの名前を覚えておく
・名前で線形探索

こんな感じ。

あと、ボーン名・影響度の他に、
もう1つボーンオフセット行列も適当な変数や配列に覚えさせておいてください。
※ボーンオフセット行列については、解説2を参照してください。
 頂点を変換する際に絶対に必要なのです。

で、ボーンの名前で線形探索し、添え字番号を取得した後の
完成したコンビネーションテーブルは下記の様になります。

【参考画像】
BoneTable3.jpg

で、あとはコレを同じボーンの組み合わせから影響を受ける頂点ごとにソートします。
(つまり、 頂点0と3頂点1と2頂点4と5でそれぞれ纏めます。)

そして、その後
頂点座標をこのボーンコンビネーションテーブルに従って変換するだけです!
※変換の仕方は、解説4を参照してください。
※補間の仕方は、キーフレームアニメーションの解説を参照してください。

以上です。


あ・あと最後にボーンの1つである"Root"の解説を。
実はこれ、無くても大丈夫です。 Frame Rootを削除しても動きます。

「何で存在するか?」というと、階層構造を作るためです。

例えば赤い箱青い箱の3Dモデルがあったとします。
バラバラに定義されている場合、いっぺんに動かす事は出来ません。
Frame RedBox {

}
Frame BlueBox {

}


しかし、親を定義すれば いっぺんに動かせるようになります。

Frame Parent {
Frame RedBox {

}
Frame BlueBox {

}
}

(親の座標変換行列は、子にも影響を与えるため。)

要するに、今回のRootというのは ボーンがどんなに複雑に枝分かれしていても、
Rootを通じて、子ボーン全てをいっぺんにまとめて移動させられるという
ボーンにとっての一番の親という役割を果たしているのです。

(まぁ、形式上あるだけで 前述したように削除しても動きます
スポンサーサイト

テーマ : ゲーム製作 関連 - ジャンル : ゲーム

コメント

No title

いつもお世話になっています^^

ついにここまで来た気がします。
スキンメッシュの読み込みは成功しているようです^^
ここのサイトはやはりとてもわかりやすいです。
1回読んだだけでここまでたどり着けるとは思いませんでした^^

だけどアニメーションなしで描画してみると・・・ここのサンプルは問題なく表示できているみたいですが、tiny.x(DirectXのサンプルについてくるやつ)は指や足の部分から他の部分へ向かって変な面が出来てしまっている状態です><;

私の理解では、逆Offset行列を先にそれぞれのボーンのフレームに対して計算を行っておく→計算を行ったボーンのフレームを影響度とあわせて、頂点に演算処理していくのだと思っています。
座標変換行列は、アニメーションで逆Offset行列による変化に加えて「自分で」変化させるときに必要なんですよね?
私は簡単のために座標変換行列は単位行列としました。

今のところインデックスの問題なのかなと思っていますが、インデックスバッファを使うとやはり結構異なってくるのでしょうか?

後もう少しなのに・・・悔しいです。

conioさんはコンビネーションテーブルでインデックス番号を指定していますが、私は、前の設計でインデックスで追えないようになってしまっているため、ボーンをフレームの1つとみなしそのポインタを保持することにしました^^

No title

おぉ読み込みまでいけましたか!
いやー本当にスキンメッシュはややこしいですよねv-394
(原理が分かれば、意外とすんなりと仕組みが理解できると思いますが、 分かるまでが大変)

「アニメーションなしでの描画がおかしい」との事ですが、
原因は特定できませんが、 思いつく所は3つあります。
----------------------------------------------------
・アニメーションタイム = 0 で計算してない
・AnimationKey 4 の補間方法がおかしい
・行列の読み込みがおかしい

----------------------------------------------------
まず一つ目ですが、アニメーションの時間を0として行列の更新を行ってないというパターンです。


で、2つ目が、AnimationKey 4
の更新でバグッてるというパターンです。
tiny.xってAnimationKey 4 が頻繁に使われてるんですよね。
ですから、それの更新がうまく行っていない可能性があります。

※このブログに掲載してるXファイルサンプルは、
実は AnimationKey 4 を使ったサンプルがありません。

何でかというと、 自分はいつもRokDeBone2というモーションを付けるフリーソフトを使ってるんですが、
そのソフト、 AnimationKey 4をXファイルとして吐き出してくれないのですよ。

・・・というか、「4」って何なんでしょうかね。
msdnをみても、「3」までしかないのに。
http://msdn.microsoft.com/ja-jp/library/bb172260%28VS.85%29.aspx

まぁ、ともかく要素数が16なので行列でいと思います。
で、そのAnimationKey4の、読み込み・変換がうまく行っているか確認してみてください。


最後に3つ目ですが、DirectXとOpenGLじゃ行列のかけかたが違うので、
DirectX仕様の行列をそのまま読み込んでバグっている可能性があります。


更新の順番に関しては、下記のようになります。

AnimationSetで定義されている通りに座標変換行列を
更新して、
更新された座標変換行列用いて、Skin行列を算出。
(Skin行列・・・スキンメッシュで頂点を移動させるための行列とする。)

Skin行列と影響度の組み合わせで、頂点を動かす。
-----------------------------------------------------------
【1】AnimationKeyなどで指定されている情報に従って、
全てのアニメーションが定義されている行列を更新。

・"Root"の座標変換行列を更新
・"JointA"の座標変換行列を更新
・"JointB"の座標変換行列を更新

【2】全ての座標変換行列を更新し終わった後、
階層構造に従って行列を更新。

”Root”
SkinR = Bof行列 × 座標変換行列 × 逆Bof行列

"JointA"
SkinA = Bof行列 × 座標変換行列 × 逆Bof行列 × 座標変換行列 × 逆BOf行列

"JointB"
SkinB = Bof行列 × 座標変換行列 × 逆Bof行列 × 座標変換行列 × 逆BOf行列 × 座標変換行列 × 逆BOf行列

【3】更新したSkin行列と、頂点の影響度に基づいて頂点を変換。
頂点×(SkinA× 0.5 + SkinB × 0.5) = 変換後の頂点
-----------------------------------------------------------

最後にインデックス関連ですが、特にインデックスじゃなくても問題ないですv-290
要は、 どの行列の影響度がいくらなのか?が分かればOKなのです。

No title

>全ての座標変換行列を更新し終わった後、階層構造に従って行列を更新。

あれ、もしかして私の理解は間違っているんでしょうか?
私は逆Bof行列がAnimation内で指定されていると思い、Animationに書いてある内容を行列にしたものを逆Bof行列にすればよいのかなと思っていました。

あれから、アニメーション部分を付け加えて動かしてみると描画速度が結構きついんです。
(tiny.xですとFPS70が限度)
最終的に描画される頂点の座標は毎フレーム計算する必要ありますよね?

Animationkey 4は調べてみた感じですと変換行列そのものみたいですね^^;
実際そのまま使ってみて動かしてみると、相変わらず表示はおかしいままですがきちんと動いていそうでした。
OpenGLとDirectXの行列の掛け方は、自前の行列やベクトルクラスを用意してあるのであればどちらの方法でも問題ないのではと思ったのですが、この認識は間違っているんでしょうかね。。。

うーん、まだまだ先は長そうです^^;

No title

混乱させてしまってすいませんでしたv-292
認識に関しては、どちらも正しいです。

逆Bof行列というのは、 基本 平行移動しかしないんですよね。
(移動させた頂点を元の場所に戻すだけなので)

【参考URL】 http://rudora7.blog81.fc2.com/blog-entry-290.html

ですから、座標変換行列と逆Bof行列はひとつにまとめる事が出来ます。
まとめた場合は 座標変換 & 逆Bof 行列となります。

とりあえず詳しい解説を。

http://rudora7.blog81.fc2.com/blog-entry-290.html
↑こちらのXファイルでは、

Bof行列 =
1 0 0 0
0 1 0 0
0 0 1 0
-0.02181 -2.476220  //省略

逆Bof行列 =
1 0 0 0
0 1 0 0
0 0 1 0
0.02181 2.476220  //省略

となっているので、90度回転させる場合は

Bof行列 × 90度座標変換する行列 × 逆Bof行列
になります。
ですが、
----------------------------------------
0 1 0 0
-1 0 0 0
0 0 1 0
0.02181 2.476220  //省略

----------------------------------------
赤字・・・逆Bof行列で必要な要素
青字・・・90度回転させる行列で必要な要素

上記のようにひとつにまとめる事が出来ます。


>私は逆Bof行列がAnimation内で指定されていると思い、
>Animationに書いてある内容を行列にしたものを
>逆Bof行列にすればよいのかなと思っていました。

Animation内では、逆Bof行列も座標変換行列も両方定義されています。
強いて言えば、 平行移動のAnimationが逆Bof行列
回転・拡大縮小が、座標変換行列になります。

上記で述べたように、わざわざ2つの行列に分けなくても大丈夫なので、
-------------------------------------------------------
D3DXMATRIX TransMat; //座標変換 + 逆Bof
D3DXMATRIX  BofMat; //ボーンオフセット用
D3DXMATRIX SkinMat; //最終的に頂点を動かす行列用
-------------------------------------------------------

AnimationSetで定義されてるアニメーションは全部
TransMatに入れてしまって、
--------------------------
Frame A{
  Frame B {

  }

--------------------------
階層構造がこうなっている場合は、
・FrameA
SkinMat = BofMat × TransMat

・FrameB
SkinMat = BofMat × TransMat ×(FrameAの)TransMat

このようにSkinmatを更新すればOKです。
---------------------------------------------
【1】TransMatを更新 (AnimationSetに基づいて)
【2】SkinMatを更新
【3】SkinMatと影響度で、頂点を移動
---------------------------------------------
なので、おおまかな流れは上記のようになります。↑

要は、AnimationSetの情報から代入する為の行列
Skin用とBof用とは 別に用意しておけば大丈夫です。
で、その行列にアニメーション情報を全部代入してもらって構いません。

>最終的に描画される頂点の座標は毎フレーム計算する必要ありますよね?
そうです。
アニメーションの時間が変化 → Animation定義に従って補間してTransMatを更新・・・//以下略
という風になります。

ですから、FF14みたいな画像が凄いゲームなんかは
PCの性能によってはまともに動かなかったりします。
(頂点が更新されるのが間に合わなくて、 3Dモデルの顔面が無茶苦茶な状態で描画されたりします。)

No title

なるほど・・・いろいろと頭の中が整理付きました。
私が行っていることが正しいみたいで良かったです^^

さらにあれから調べてみたところ描画処理を早くするためにはそのボーンから頂点の座標を計算するのにもシェーダを使うと良いらしいということがわかりました。
ということはDirectX Viewerもシェーダを使って描画してそうですね。
しかし・・・、シェーダって勉強したこと無いんですよね^^;
これは、スキンメッシュが正しく表示されたらやってみようかと思います。

ともかく、私のために丁寧に説明してくださってありがとうございますm(--)m

No title

逆Bof 「行列」と呼んでいたから、紛らわしいんですよね。
行列というより、ただの平行移動の差分です。
(x、y、z の合計3つ。)

確かに。
シェーダは良いらしいんですけど、
自分もまだ勉強不足の分野です v-292

慣れてくると、行列の変換とかだけでなく、
----------
・残像
・霧
・モザイク
・光の反射
----------
といった特殊効果を作り出すことも出来るらしいです。

とりあえずシェーダに関してはこの本が分かりやすいですよv-411  ↓
http://www.amazon.co.jp/DirectX-9-%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%80%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%83%96%E3%83%83%E3%82%AF-%E4%BB%8A%E7%B5%A6%E9%BB%8E-%E9%9A%86/dp/4839912475/ref=sr_1_1?ie=UTF8&s=books&qid=1293205691&sr=8-1-catcorr

スキンメッシュアニメーションの実装まで頑張って下さいv-291
(自分も、より分かりやすい解説&ソースコードを公開できるように なりたいと思います。)
コメントの投稿
管理者にだけ表示を許可する



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。