●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)