●Animation なしのまとめ
■ Transform Shader
+ in VertexStream 0 ( _LL_geometry00 )
+ in ShaderResourceView ( _LL_geometry00 )
+ in ConstantBuffer ( _LL_rootWorld ) 描画時に CPU が与える
+ out StreamOutput ( _LL_geometryWorld )
●Animation ありの場合
準備
1. 頂点に Geometry の node 番号と weight 値を埋め込んであります。
2. Animation データは plot した値をバッファにつめて格納します。
3. 描画単位のノード毎に、参照する bone の逆行列をあらかじめ求めておいて
GeometryMatrix の後ろに格納しています。
4. データを読み込み、モデルに Animation データを Bind します。
ノードとの対応表を作っておきます。
(1) Aniamtion の取り出し
単純化するため plot した値をそのままバッファに格納しています。
外部から描画すべき frame を与えて、そのフレームの Animation ノードを取り出します。
フレーム補間は容易に実現できますがここでは行っていません。
単に frame 値による値の選択なので、独立した描画パスに分ける必然性はありません。
ただし、モデルのノードとアニメーションのノードをマッピングする必要があります。
・アニメーションデータの n 番が、実モデルの何番の node に対応するのか
・対応するノードがモデルに存在しているかどうか
これらのノード対応表をデータを読み込んだあと、Animation を bind するときに
作成しておきます。
アニメーションデータのバッファ定義は下記の通り。
// 実際のアニメーションデータ Buffer _LL_Anim00 Usage IMMUTABLE Cpu 0 Bind SHADER_RESOURCE Format MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F ~
ノード毎の frame 単位の値を、単一バッファにパックしています。
ノード対応表にはただの番号ではなく、バッファ内のオフセット値を入れておきます。
例えば index0~31 が node 3 のデータ、index 32~63 が node 1 に bind され
たとします。ノード→Animation の対応表は次のようになります。
0: 無し
1: 32
2: 無し
3: 0
ここでのセットアップ情報
// Shader Setup Vertex _LL_geometry00 CBuffer "GBind"= マップデータ ShaderResourceView "GAnimation"= _LL_srv_Anim00 StreamOut _LL_geometryAnimation Draw POINTLIST
その他のバッファは下記の通りです。
// アニメーション参照用 ResourceView _LL_srv_Anim00 Link _LL_Anim00 Format 32x4F BUFFER Element 151110 // 出力バッファ Buffer _LL_geometryAnimation Usage DEFAULT Cpu 0 Bind STREAM_OUTPUT SHADER_RESOURCE VERTEX_BUFFER Format MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F INDEX:32x4UI
出力は、アニメーションを適用した _LL_geometry00 の置き換えとなります。
// Animation Shader struct TBindMap { uint Remap; uint reserved0; uint reserved1; uint reserved2; }; cbuffer cb_GBind { TBindMap GBind[MAX_TRANSFORM_BUFSIZE_]; }; struct VS_INPUT_ANIMATION { // _LL_geometry00 float4 Mat0 : MATRIX0; float4 Mat1 : MATRIX1; float4 Mat2 : MATRIX2; uint4 Parent : INDEX; uint VertexID : SV_VertexID; }; struct VS_OUTPUT_ANIMATION { // _LL_geometryAnimation float4 Mat0 : MATRIX0; float4 Mat1 : MATRIX1; float4 Mat2 : MATRIX2; uint4 Parent : INDEX; }; VS_OUTPUT_ANIMATION VS_Animation( VS_INPUT_ANIMATION In ) { VS_OUTPUT_ANIMATION Out; float4 cmat0= In.Mat0; float4 cmat1= In.Mat1; float4 cmat2= In.Mat2; uint id= GBind[ In.VertexID ].Remap; if( id != 0xffffffff ){ // animation uint offset= id * 3 + cFrame.x*3; Out.Mat0= GAnimation[ offset + 0 ] * cBlend.x; Out.Mat1= GAnimation[ offset + 1 ] * cBlend.x; Out.Mat2= GAnimation[ offset + 2 ] * cBlend.x; }else{ // through Out.Mat0= In.Mat0; Out.Mat1= In.Mat1; Out.Mat2= In.Mat2; } Out.Parent= In.Parent; return Out; }
(2) 階層構造の解決
(1) の出力は _LL_geometry00 と同じものなので、これを入力として
Animation なしの場合と同じ Transform Shader に与えます。
Vertex _LL_geometryAnimation CBuffer "GRoot"= _LL_rootWorld ShaderResourceView "GTree"= _LL_srv_geometryAnimation StreamOut _LL_geometryWorld Draw POINTLIST
(3) スキニングのためのマトリクス計算
それぞれの頂点が参照するノード個数分、静止状態で前計算した Bone の逆行列を
あらかじめ格納しておきます。これは BasePose に相当します。
この Matrix には Bone Matrix の参照用 id と、描画するオブジェクト自体が
存在する node の id を持たせておきます。
バッファ数を減らすためと管理しやすくするために、非 Animation データでも
使っていた GeometryMatrix ( _LL_geometry00 ) を流用します。
この node 用 Geometry Matrix の後ろに、追加で Bone 用 Matrix 領域を
確保しておきます。
_LL_geometry00 は index 部の .x に parent の node Geometry 番号を
格納していました。
Bone 用マトリクスは、.x に Bone Matrix の番号、.y に描画するオブジェクトの
node 番号を格納しています。
Buffer _LL_geometry00 Usage IMMUTABLE Cpu 0 Bind VERTEX_BUFFER SHADER_RESOURCE Format MATRIX:32x4F MATRIX:32x4F MATRIX:32x4F INDEX:32x4UI // .x= parent/bone id, .y= world id # node 0 ~ # Primitive0 - Bone0 f -0.187506 -0.875848 0.444671 -7.197771 f -0.011216 0.454579 0.890635 1.248997 f -0.982199 0.162011 -0.095059 1.356605 i 6 26 0 1 ~
ここで必要なのは Bone Matrix の部分だけです。
この値を Vertex (Stream) として入力し、アニメーション変換済みの
Bone Matrix に乗算します。
StreamOut _LL_geometryWorldB Vertex _LL_geometry00 // offset 付き (Bone Matrix のみ入力) ShaderResourceView "GOutT"= _LL_srv_geometryWorld Draw POINTLIST
struct VS_INPUT_BONE { float4 Mat0 : MATRIX0; float4 Mat1 : MATRIX1; float4 Mat2 : MATRIX2; uint4 Parent : INDEX; }; struct VS_OUTPUT_BONE { float4 Mat0 : MATRIX0; float4 Mat1 : MATRIX1; float4 Mat2 : MATRIX2; }; VS_OUTPUT_BONE VS_Bone( VS_INPUT_BONE In ) { VS_OUTPUT_BONE Out; uint boneid= In.Parent.x; uint worldid= In.Parent.y; float4 rmat0= In.Mat0; float4 rmat1= In.Mat1; float4 rmat2= In.Mat2; M34_MATRIX mp; mp= mul34( GOutT[ boneid*3 + 0 ], GOutT[ boneid*3 + 1 ], GOutT[ boneid*3 + 2 ], rmat0, rmat1, rmat2 ); // worldid の乗算を行うと動的な変化に対応できるけどしていない Out.Mat0= mp.mat[0]; Out.Mat1= mp.mat[1]; Out.Mat2= mp.mat[2]; return Out; }
今見ると、無理に Transform Shader を共有しようと思わなければ
もっと簡略化できそうですね。
関連エントリ
・Direct3D10 GPU だけでジオメトリ計算 (1)