Direct3D10 GPU だけでジオメトリ計算 (2)

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