次の方法で共有


AdvancedParticles サンプル

これは、SIGGraph 2007 の Advanced Real-Time Rendering in 3D Graphics and Games コースで紹介された、3 つのサンプル アプリケーションのうちの 1 つです。この Direct3D 10 のサンプルは、環境と連携するパーティクル システムを示しています。システムは、GPU によって完全に管理されています。

Bb943954.d3d10_sample_AdvancedParticles(ja-jp,VS.85).jpg

Path

ソース : (SDK ルート)\Samples\C++\Direct3D10\AdvancedParticles
実行可能ファイル : (SDK ルート)\Samples\C++\Direct3D10\Bin\x86 or x64\AdvancedParticles.exe

サンプルが動作するしくみ

このサンプルでは、レンダーボリューム テクスチャー手法を使用して、パーティクルを環境と連携させることができます。この手法は、以下の 3 つのフェーズで構成されています。1 つ目では、パーティクルが反応できるような方法で、シーンがボリューム テクスチャーにレンダリングされます。2 つ目では、ボリューム テクスチャー内に格納されたシーンの説明と、GPU パーティクル システムを衝突させます。3 つ目では、シーンに影響を与えるパーティクルを持つために、一意にマッピングされたメッシュ テクスチャー上のパーティクルから描画した結果を収集します。

ボリューム テクスチャーの設定

ボリューム テクスチャーは、同時にスライスしたら、シーン ジオメトリと共に格納する必要があります。通常これは、ボリュームの各スライスに対して別個にレンダリング パイプラインを起動してから、レンダリングされる各オブジェクトに対しても別個にレンダリング パイプラインを起動する必要があります。ただし、グラフィック ハードウェアの最新の進歩では、ボリュームのすべてのスライスを同時にパイプラインにバインドでき、選択的にジオメトリを各スライスに出力できるため、このプロセスは、各オブジェクトに対してレンダリング パイプラインを 1 回呼び出すだけになります。グラフィック ハードウェアにおけるこの最新の進歩は、新たな追加分という形で、ジオメトリ シェーダーと呼ばれるレンダリング パイプラインにもたらされています。ボリューム レンダー ターゲットへの出力スライスを指定できることに加えて、ジオメトリ シェーダーでは、プリミティブ全体に操作を実行することもできます。

このプロセスは次のように機能します。シーン ジオメトリは、ハードウェアのインスタンス化が有効になっている状態で描画されます。ここでは、シーン ジオメトリの S インスタンスを描画します。この S は、ボリューム テクスチャーのスライス数です。シェーダーでは、各三角形プリミティブは、ジオメトリのインスタンス ID に基づいて、ボリュームの別のスライスに送られます。

Bb943954.d3d10_sample_AdvancedParticles1(ja-jp,VS.85).jpg

次のコードは、レンダリングしているボリューム スライスを SV_RENDERTARGETARRAYINDEX システム変数を使用して指定する方法を示しています。

struct GSSceneOut
{
    float4 PlaneEq        : NORMAL;
    float2 PlaneDist      : CLIPDISTANCE;
    float4 Pos        : SV_POSITION;
    uint   RTIndex    : SV_RENDERTARGETARRAYINDEX;
};

...

[maxvertexcount(3)]
void GSScene( triangle GSSceneIn input[3], inout TriangleStream<GSSceneOut> TriStream )
{
    GSSceneOut output;
    ...
    // send us to the right render target
    output.RTIndex = input[0].InstanceID;
    ...
}

前述のジオメトリ シェーダーを使用することにより、プリミティブに対して平面方程式が計算され、各頂点の速度と共にピクセル シェーダーに渡されます。特定のスライスを通じて渡されるジオメトリのみがそのスライスへラスター化されるように、ユーザーが指定したクリップ面が、その指定されたスライスから外れたすべてのジオメトリをクリッピングするために提供されます。その後ピクセル シェーダーによって、ボリューム テクスチャーへ平面方程式および補間された速度が出力されます。次のコードは、入力三角形に対する平面方程式の計算を示しています。

[maxvertexcount(3)]
void GSScene( triangle GSSceneIn input[3], inout TriangleStream<GSSceneOut> TriStream )
{
    GSSceneOut output;
     
                // calculate the face normal
    float3 faceEdgeA = input[1].wPos - input[0].wPos;
    float3 faceEdgeB = input[2].wPos - input[0].wPos;
    float3 faceNormal = normalize( cross( faceEdgeA, faceEdgeB ) );
    
    // find the plane equation
    float4 planeEq;
    planeEq.xyz = faceNormal;
    planeEq.w = -dot( input[0].wPos, faceNormal );

    ...
    // Output the triangle
    ...
}

Bb943954.d3d10_sample_AdvancedParticles2(ja-jp,VS.85).jpg

ボリューム テクスチャーのサンプリング

パーティクルの更新フェーズでは、パーティクルを含むパーティクル ボリューム テクセルが、その平面方程式と速度のためにサンプリングされます。その後、パーティクルは、平面方程式に対する衝突をチェックされます。衝突が発生する場合は、パーティクルはそれ自体の速度、平面方程式、および平面速度に従って、その衝突を回避するよう方向を変えられます。

エイリアスの解決

詳細なジオメトリまたは粗いボリューム テクスチャー表現では、複数のプリミティブが同じボリューム セルへラスター化される場合があります。そのグリッド セルと交差するすべての平面方程式と速度を格納すると、取得するビデオ メモリーが多くなりすぎ、サンプリング フェーズで複数のフェッチが必要になります。そのため、最も重要な平面方程式と速度のみを、計算で使用するために保持します。これは、大多数のパーティクルが進む方向から、ボリューム テクスチャーにシーン ジオメトリをレンダリングすることによって行います。多くの場合、これはエミッタの視点となります。その後、ボリュームにシーンをレンダリングするときに使用されるカメラ位置に最も近いプリミティブが保持されるように、ハードウェアで深度テストを使用します。大多数のパーティクルはカメラから離れる方向に移動しているため、理想的な状況においては、ほとんどのパーティクルは、この特定セルも占有する別の任意の平面に到達する前に、この平面に到達できます。ただし、平均の方向から大きく離れた方向に進むパーティクルに対しては、間違った結果になる可能性があります。このエラーは、より高密度なボリューム テクスチャーを使用することでも回避できます。

Bb943954.d3d10_sample_AdvancedParticles3(ja-jp,VS.85).jpg

Bb943954.d3d10_sample_AdvancedParticles4(ja-jp,VS.85).jpg

位置バッファーのレンダリング

次は、シーン ジオメトリの外観を、パーティクルとの連携に基づいて変更する方法を示します。特に、パーティクルは、それが検出したオブジェクトの任意の部分に描画を適用します。

まず、シーン内の各オブジェクトのために位置バッファーを作成する必要があります。位置バッファーは、オブジェクト UV 空間の各テクセルのワールド空間座標を含む浮動小数点テクスチャーです。これは事実上、UV からワールド空間へのマッピングです。位置バッファーに入力するには、位置座標としてテクスチャー UV 座標を使用してメッシュをレンダリングします。これにより、UV 空間にメッシュ ジオメトリがレンダリングされます。その後、ピクセル シェーダーによって、補間された位置データが位置テクスチャーに出力されます。使用されている UV 要素がメッシュの固有のパラメーター表示であることを確認してください。それ以外の場合は結果が間違っているということを覚えておいてください。

Bb943954.d3d10_sample_AdvancedParticles5(ja-jp,VS.85).jpg

描画した結果の収集

入力された位置バッファーでは、パーティクル バッファーまたはテクスチャーからパーティクルを収集し、それらがメッシュと交差するかどうかを判別する必要があります。メッシュと交差する場合は、描画テクスチャーにそれらの描画を追加します。レンダー ターゲットとして描画テクスチャーを設定し、レンダリング時にレンダー ターゲットを正確にカバーするクワッドをラスター化することにより、これを処理します。ラスター化の間に、現在のテクセルの位置テクスチャーからワールド空間座標をサンプリングします。次に、パーティクル バッファーまたはテクスチャーにおいてパーティクルを繰り返し処理します。各パーティクルに対して、描画をそのまま残すために、それが位置バッファーにおいてワールド空間座標の十分近くにあるかどうかを判断します。十分近くにある場合は、このピクセル シェーダー呼び出しの描画出力全体に描画を反映させます。

Bb943954.d3d10_sample_AdvancedParticles6(ja-jp,VS.85).jpg

次のコードは、パーティクル頂点バッファーから直接コードを読み込むプロセスを示しています。

float4 PSPaint(PSQuadIn input) : SV_Target
{       
    // sample the position buffer
    float4 meshPos = g_txDiffuse.Sample( g_samPoint, input.Tex );
    
    // loop through the particles
    float3 color = float3(0,0,0); float alpha = 0;
    for( int i=g_ParticleStart; i>g_NumParticles; i+=g_ParticleStep )
    {
    // load the particle data from the buffer
    float4 particlePos = g_ParticleBuffer.Load( i*4 );
    float4 particleColor = g_ParticleBuffer.Load( (i*4) + 2 );
        
    float3 delta = particlePos.xyz - meshPos.xyz;
    float distSq = dot( delta, delta );
    if( distSq > g_fParticleRadiusSq )
    {
           color = color.xyz*(1-particleColor.a) + particleColor.xyz * particleColor.a;
           alpha += particleColor.a;
    }
    }
   
    return saturate( float4(color,alpha) );
}

時間の経過に合わせた収集の償却

何千ものパーティクルを含むシステムで、収集時にすべてのパーティクルを繰り返し処理する場合は、最適なフレーム レートが提供されない場合があります。命令数が固定されているハードウェアの場合は、すべてのパーティクルに対してループを実行できない可能性があります。収集するパーティクルの固定数を決定することによって、複数フレーム上での収集コストを償却します。たとえば、最初のフレームに対しては、最初の G パーティクルを収集します。次のフレームに対しては、次の G パーティクルを収集し、パーティクル バッファーの先頭へループ バックするまでこの動作を続けます。これにより、ほとんどエフェクトの質を損なうことなく、さらにパフォーマンスが向上します。