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

アニメーションから描画までの一連の演算を 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回だけのインスタンシングでパラメータを受け取ることも出来ます。

インスタンシング描画命令を使うとオフセットを渡せるので、
ジオメトリ番号を渡すためだけにバッファを書き換える必要がなくなります。