アニメーションから描画までの一連の演算を GPU のみで行ってみます。
少々古いネタで恐縮ですが、実際に 1年くらい前に作成した描画パスです。
特徴はまじめに描画エンジンに組み込んだこと。
Maya 等のツールから出力したデータを、画面に描画するまでの一連の作業に対応できます。
やってる内容はたいしたものではないものの、実作業ではどんなデータが来るかわかりません。
さまざまな状況への対応を考えると、やることは意外に複雑です。
例えば Animation は任意の Model と bind 可能で、各 node と対応する順番も任意、
存在しない node は無視し、Animation しないノードと共存できる必要もあります。
Bone 構造を持ったデータでも Animation を Bind せずに描画する状況も考えられます。
欠点はレンダリング回数が増えたために遅くて実用にならなかったことです。
他にもいろいろ理由があるのですが、、今までお蔵入りでした。
●考え方
・Animation なし
(1) Buffer 上の Geometry を元に各ノードの world matrix を算出します。
(2) (1) の結果をもとに実際の Primitive 描画を行います。
・Animation あり
(1) 任意 frame の Animation 値を、node 対応付けしながら取り出します。
(2) (1) の出力を入力と見なして world matrix の算出します。
(3) node 毎に bone が参照している matrix と、前計算した逆行列を乗算します。
(4) 頂点毎に必要なノードを参照し、blend しながら描画します。
●Animation なしの場合
(1) TransformShader
あらかじめ各 node 毎の変換 Matrix を Buffer に格納しておき、
描画時に world matrix に変換します。
Matrix は stream 入力し、変換した結果もそのまま Stream Output します。
ただし各ノードは親の影響を受けるため、他の Matrix を参照する必要があります。
そのため全く同じ Geometry Matrix のバッファを
・Vertex (StreamInput)
・ShaderResourceView
の両方に入力します。
現在の node は Stream (Vertex) で参照し、階層をたどる場合に
ShaderResourceView を使います。
Matrix は 4×3 で、空いた 4要素に親の index 番号を格納しています。
// 各 node の matrix。あらかじめ用意しておく。書き換えない Buffer _LL_geometry00 Usage IMMUTABLE Cpu 0 Bind VERTEX_BUFFER SHADER_RESOURCE Format MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F INDEX:32x4UI // .x= 親の index 番号、 .yzw= 未使用 # node 0 f 1.0 0.0 0.0 0.0 f 0.0 1.0 0.0 0.0 f 0.0 0.0 1.0 0.0 i 99999 0 0 1 // 99999 = 親無し # node 1 f 1.0 0.0 0.0 0.0 f 0.0 1.0 0.0 0.0 f 0.0 0.0 1.0 0.0 i 0 0 0 1 ~
必要な node の数だけ matrix が並びます。
セットアップは下記の通り。
“GRoot” や “GTree” は Shader 内のシンボル名で、それぞれ _LL_rootWorld と
_LL_srv_geometry00 を割り当てています。
// 描画時の Shader Setup Vertex _LL_geometry00 CBuffer "GRoot"= _LL_rootWorld ShaderResourceView "GTree"= _LL_srv_geometry00 StreamOut _LL_geometryWorld Draw POINTLIST
その他のバッファ定義
// Root の Transform 値。アプリケーションから渡す描画位置 Buffer _LL_rootWorld Usage DEFAULT Cpu 0 Bind CONSTANT_BUFFER Format MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F // ResourceView ResourceView _LL_srv_geometry00 Link _LL_geometry00 Format 32x4F BUFFER Elemet 4 // 出力用 Buffer _LL_geometryWorld Usage DEFAULT Cpu 0 Bind VERTEX_BUFFER STREAM_OUTPUT SHADER_RESOURCE Format MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F
問題は、各ノードが親の影響を再帰的に受けること。
相互参照が発生するため 1pass では完了しません。
ここでは index を元に毎回 root までループして計算し尽くしています。
無駄が発生しますがとりあえず一回で終わります。
// Transform Shader struct VS_INPUT_TRANSFORM { // _LL_geometry00 float4 Mat0 : MATRIX0; float4 Mat1 : MATRIX1; float4 Mat2 : MATRIX2; uint4 Parent : INDEX; }; struct VS_OUTPUT_TRANSFORM { // _LL_geometryWorld float4 Mat0 : MATRIX0; float4 Mat1 : MATRIX1; float4 Mat2 : MATRIX2; }; VS_OUTPUT_TRANSFORM VS_Transform( VS_INPUT_TRANSFORM In ) { VS_OUTPUT_TRANSFORM Out; uint parent= In.Parent.x; float4 cmat0= In.Mat0; float4 cmat1= In.Mat1; float4 cmat2= In.Mat2; // 親まで計算する for(; parent < 0xffff ;){ M34_MATRIX mp; mp= mul34( GTree[ parent ].M[ 0 ], GTree[ parent ].M[ 1 ], GTree[ parent ].M[ 2 ], cmat0, cmat1, cmat2 ); cmat0= mp.mat[0]; cmat1= mp.mat[1]; cmat2= mp.mat[2]; parent= GTree[ parent ].Parent.x; } // 最後に root を乗算 M34_MATRIX mp2; mp2= mul34( GRoot[ 0 ], GRoot[ 1 ], GRoot[ 2 ], cmat0, cmat1, cmat2 ); Out.Mat0= mp2.mat[0]; Out.Mat1= mp2.mat[1]; Out.Mat2= mp2.mat[2]; return Out; }
(2) 描画
上で求めた _LL_geometryWorld を元にモデルデータの描画を行います。
頂点毎の index で _LL_geometryWorld を参照してもいいし、
オフセットを付けて空いている Stream に入れ、
1回だけのインスタンシングでパラメータを受け取ることも出来ます。
インスタンシング描画命令を使うとオフセットを渡せるので、
ジオメトリ番号を渡すためだけにバッファを書き換える必要がなくなります。