ParticlesGS サンプル
このサンプルでは、Direct3D 10 のシェーダー、ストリーム出力、および DrawAuto を使用して、GPU 上で完全なパーティクル システムを実装します。
Path
ソース : | (SDK ルート)\Samples\C++\Direct3D10\ParticlesGS |
実行可能ファイル : | (SDK ルート)\Samples\C++\Direct3D10\Bin\x86 or x64\ParticlesGS.exe |
サンプルが動作するしくみ
パーティクル システムの計算は、従来、GPU を使用して CPU 上で実行されており、視覚効果を得るために、パーティクルをポイント スプライトとしてレンダリングしていました。ジオメトリ シェーダーを使用すると、任意の量のデータをストリームに出力できるため、GPU で新しいジオメトリを作成したり、既存のジオメトリの計算結果を格納することが可能になります。
このサンプルでは、ジオメトリ シェーダーのストリーム出力機能を使用して、パーティクルの計算結果をバッファーに格納します。また、ジオメトリ シェーダーで新しいジオメトリをストリームに出力したり、既存のジオメトリをストリームに書き込まないようにしたりすることで、パーティクルの出現と消滅 (存続期間) を制御します。バッファーに出力をストリームするジオメトリ シェーダーは、通常のジオメトリ シェーダーとは違う方法で作成する必要があります。
FX ファイル内で使用する場合
//--------------------------------------------------------------------------------------
// Construct StreamOut Geometry Shader
//--------------------------------------------------------------------------------------
geometryshader gsStreamOut = ConstructGSWithSO(compile gs_4_0 GSAdvanceParticlesMain(),
"POSITION.xyz;
NORMAL.xyz;
TIMER.x;
TYPE.x" );
FX なしで使用する場合
//--------------------------------------------------------------------------------------
// Construct StreamOut Geometry Shader
//--------------------------------------------------------------------------------------
D3D10_STREAM_OUTPUT_DECLARATION_ENTRY pDecl[] =
{
// semantic name, semantic index, start component, component count, output slot
{ L"POSITION", 0, 0, 3, 0 }, // output first 3 components of "POSITION"
{ L"NORMAL", 0, 0, 3, 0 }, // output the first 3 components of "NORMAL"
{ L"TIMER", 0, 0, 1, 0 }, // output the first component of "TIMER"
{ L"TYPE", 0, 0, 1, 0 }, // output the first component of "TYPE"
};
CreateGeometryShaderWithStreamOut( pShaderData, pDecl, 4, sizeof(PARTICLE_VERTEX), &pGS );
パーティクルのタイプ
このパーティクル システムは、さまざまなプロパティを持つ 5 つのパーティクル タイプで構成されています。どのパーティクル タイプも固有の速度と動作が定義されており、他のパーティクルを生成するタイプもあれば、生成しないタイプもあります。
発射筒 (Launcher) パーティクル
発射筒パーティクルは、移動することも、消滅することもありません。単純に、花火弾 (Shell) パーティクルを生成できるようになるまでカウントダウンを続けます。花火弾パーティクルを生成すると、自身のタイマーをリセットします。
花火弾 (Shell) パーティクル
花火弾パーティクルは、発射筒パーティクルによって不定の速度で夜空に発射される、単一のパーティクルです。これらは、破裂前の花火の砲弾を表すように作成されています。花火弾パーティクルは、ライフスパンがきても、再びシステム内で自己生成することはありません。代わりに、複数の残り火 1 (Ember1) および残り火 2 (Ember2) タイプのパーティクルを生成します。
残り火 1 (Ember1) パーティクル
残り火 1 パーティクルは、花火弾パーティクルが破裂したときに花火弾パーティクルから生成されます。残り火 1 パーティクルのライフスパンは短く、タイマーのカウントダウンにともなってフェード アウトします。タイマーがゼロになっても、システム内で自己生成することはなく、事実上 "消滅" します。
残り火 2 (Ember2) パーティクル
残り火 2 パーティクルも、花火弾パーティクルが破裂したときに花火弾パーティクルから生成されます。残り火 1 パーティクルと異なり、残り火 2 パーティクルはライフスパンがくると残り火 3 パーティクルを生成します。残り火 2 パーティクルは、システム内で 2 次的な破裂を引き起こします。
残り火 3 (Ember3) パーティクル
残り火 3 パーティクルは、残り火 1 パーティクルと似ていますが、残り火 1 パーティクルよりライフスパンが短く、色も異なります。
ジオメトリ シェーダーでのパーティクルの処理
パーティクルは、ジオメトリ シェーダーによって GPU 上で完全に処理されます。ラスタライザーに転送される代わりに、ジオメトリ シェーダーに渡された頂点データは、別の頂点バッファーに出力されます。頂点バッファーが発射筒タイプのパーティクルで初期シードされると、システムは、CPU から送信されるフレーム単位のタイミング情報のみを使用して GPU に留まることができます。
サンプルでは、頂点データで構成される 3 つのバッファーを使用して、花火のパーティクル システムを円滑に処理しています。最初のストリームには、システムの "シード" に必要な初期パーティクルが含まれます。このストリームは、パーティクルシステムがアクティブな間、最初のフレームが一度だけ使用されます。2 番目と 3 番目のバッファーはピンポン バッファーであり、交互に他のすべてのフレームにストリームされ、これらのフレームからレンダリングされます。
パーティクル システムは、次のように動作します。
1. シード バッファーに初期発射筒パーティクルが格納されます。
2. ジオメトリ シェーダー (GS) の 1 回目の処理では、発射筒のタイマーは 0 であり、発射筒の位置で花火弾が生成されます。注:パーティクル システムは GS 全体を通してすべてのパスでリビルドされるため、新しいパーティクルだけでなく、次のフレームに必要なパーティクルもすべて生成する必要があります。
3. 2 回目の GS の処理では、発射筒と花火弾のタイマー カウントが減らされ、花火弾が新しい位置に移動します。
4. 花火弾のタイマー カウントが 0 になります。つまり、これがこの花火弾の最後のフレームになります。
5. タイマーが 0 になった花火弾は再び生成されません。代わりに、4 つの残り火パーティクルが生成されます。
6. 発射筒はタイマーが 0 になったので、別の花火弾パーティクルを生成する必要があります。残り火パーティクルはそれぞれ新しい位置に移動し、タイマー カウントも減らされます。発射筒のタイマーはリセットされます。
出力されたパーティクルの数
ジオメトリ シェーダーは、フレームごとにさまざまな量のデータを生成できます。そのため、このサンプルには、特定の時点でバッファーに存在するパーティクルの数を知る方法がありません。標準の Draw 呼び出しを使用する場合は、GPU に描画させるパーティクルの数を推測する必要があります。幸いなことに、DrawAuto はこのような状況を処理するために設計されています。DrawAuto を使用すると、ストリーム出力バッファーに書き込まれた動的なデータ量を、描画呼び出しの入力データ量として使用できます。これは GPU で実行されるため、CPU ではパーティクル システムを構成する実際のパーティクル数がわからなくても、処理を進めてパーティクル システムを描画することができます。
パーティクルのレンダリング
gsStreamOut ジオメトリ シェーダーによるパーティクルの処理が終了すると、2 番目のパスでは、その出力をちょうど受け取ったバッファーを使用して、パーティクルがポイント スプライトとしてレンダリングされます。VSSceneMain 頂点シェーダーが、パーティクルのタイプと存続時間に基づいて、サイズと色を割り当てます。次に、GSSceneMain が、渡されたすべての点に対する 2 つのトライアングル ストリップを生成することによって、各点からポイント スプライトを作成します。このパスでは、ジオメトリ シェーダーの出力はラスタライザーに渡され、バッファーにはストリーム出力されません。