2017-01-31 2 views
0

私は流体シミュレーションを実装したいと思います。何かthisのようなものです。アルゴリズムは重要ではありません。重要な問題は、それをピクセルシェーダに実装する場合は、複数のパスで実行する必要があるということです。マルチパスピクセルシェーダをシェーダを計算するために変換する技術はありますか?

私が使っていた技術の問題は、パフォーマンスが非常に悪いことです。私は何が起こっているのか、そして1回のパスで計算を解決するために使用されたテクニック、そしてタイミング情報について説明します。

概要:

私たちは地形を持っているし、我々はそれの上に雨や水の流れを見てみたいです。 1024x1024テクスチャに関するデータがあります。私たちは地形の高さと各地点の水の量を持っています。これは反復シミュレーションです。反復1は、地形と水のテクスチャを入力として取得し、結果を計算し、地形と水のテクスチャに結果を書き込みます。反復2が実行され、再度テクスチャが変更されます。これらの段階が起こるの各反復で

enter image description here

  1. は地形や水の高さを取得する反復の何百もの後、私たちはこのようなものを持っています。
  2. フローを計算します。
  3. フローの値をグループ共有メモリに書き込みます。
  4. 同期グループメモリ
  5. このスレッドのスレッド共有メモリからのフロー値と、現在のスレッドの左、右、上、下のスレッドを読み込みます。
  6. 前の手順で読み込んだFlow値に基づいて水の新しい値を計算します。
  7. 地形と水のテクスチャに結果を書き込みます。

だから基本的に私たちは、データをフェッチcalculate1は、共有メモリ、同期にcalculate1結果を入れてください、現在のスレッドや隣人のための共有メモリからフェッチcalculate2を行い、そして結果を書き込みます。

これは、非常に広い範囲の画像処理の問題で発生する明確なパターンです。従来のソリューションはマルチパスシェーダでしたが、帯域幅を節約するために1パスの計算シェーダで行いました。

テクニック:私は技術を使用し

Practical Rendering and Computation with Direct3D 11第12章で説明したが、我々は、各スレッドグループは16x16x1スレッドになりたいとします。しかし、2番目の計算には近隣も必要なので、各方向にピクセルを塗りつぶします。つまり、18x18x1のスレッドグループを持つことになります。このパディングのために、2番目の計算では有効な近傍があります。ここにパディングを示す画像があります。黄色のスレッドは、計算が必要なスレッドで、赤のスレッドはパディングです。それらはスレッドグループの一部ですが、中間処理に使用して、テクスチャに保存しません。この図では、パディング付きのグループは10x10x1ですが、スレッドグループは18x18x1です。

enter image description here

処理が実行され、正しい結果を返します。唯一の問題はパフォーマンスです。

タイミング: Geforce GT 710のシステムでは、10000回の繰り返しでシミュレーションを実行します。

  • 完全で正確なシミュレーションを実行するには60秒かかります。
  • ボーダーをパディングせずに16x16x1スレッドグループを使用する場合、時間は40秒になります。明らかに結果は間違っています。
  • 私はgroupsharedメモリを使用せず、2番目の計算にダミー値を入力すると、時間は19秒になります。結果はもちろん間違っています。

質問:

  1. これは、この問題を解決するための最良の手法ですか?代わりに、2つの異なるカーネルで計算すると、より高速になります。 2x19 < 60.
  2. なぜグループ共有メモリが遅いのですか?

ここに計算シェーダコードがあります。それは60秒かかる正しいバージョンです:

#pragma kernel CSMain 


Texture2D<float> _waterAddTex; 
Texture2D<float4> _flowTex; 

RWTexture2D<float4> _watNormTex; 
RWTexture2D<float4> _flowOutTex; 

RWTexture2D<float> terrainFieldX; 
RWTexture2D<float> terrainFieldY; 
RWTexture2D<float> waterField; 


SamplerState _LinearClamp; 
SamplerState _LinearRepeat; 

#define _gpSize 16 
#define _padGPSize 18 

groupshared float4 f4shared[_padGPSize * _padGPSize]; 

float _timeStep, _resolution, _groupCount, _pixelMeter, _watAddStrength, watDamping, watOutConstantParam, _evaporation; 
int _addWater, _computeWaterNormals; 
float2 _rainUV; 
bool _usePrevOutflow,_useStava; 

float terrHeight(float2 texData) { 
    return dot(texData, identity2); 
} 

[numthreads(_padGPSize, _padGPSize, 1)] 
void CSMain(int2 groupID : SV_GroupID, uint2 dispatchIdx : SV_DispatchThreadID, uint2 padThreadID : SV_GroupThreadID) 
{ 

    int2 id = groupID * _gpSize + padThreadID - 1; 
    int gsmID = padThreadID.x + _padGPSize * padThreadID.y; 
    float2 uv = (id + 0.5)/_resolution; 

    bool outOfGroupBound = (padThreadID.x == 0 || padThreadID.y == 0 || padThreadID.x == _padGPSize - 1 
     || padThreadID.y == _padGPSize - 1) ? true : false; 

    // -------------FETCH------------- 

    float2 cenTer, lTer, rTer, tTer, bTer; 
    sampleUavNei(terrainFieldX,terrainFieldY, id, cenTer, lTer, rTer, tTer, bTer); 

    float cenWat, lWat, rWat, tWat, bWat; 
    sampleUavNei(waterField, id, cenWat, lWat, rWat, tWat, bWat); 

    // -------------Calculate 1------------- 

    float cenTerHei = terrHeight(cenTer); 
    float cenTotHei = cenWat + cenTerHei; 
    float4 neisTerHei = float4(terrHeight(lTer), terrHeight(rTer), terrHeight(tTer), terrHeight(bTer)); 
    float4 neisWat = float4(lWat, rWat, tWat, bWat); 
    float4 neisTotHei = neisWat + neisTerHei; 
    float4 neisTotHeiDiff = cenTotHei - neisTotHei; 


    float4 prevOutflow = _usePrevOutflow? _flowTex.SampleLevel(_LinearClamp, uv, 0):float4(0,0,0,0); 
    float4 watOutflow; 
      float4 flowFac = min(abs(neisTotHeiDiff), (cenWat + neisWat) * 0.5f); 
      flowFac = min(1, flowFac); 
      watOutflow = max(watDamping* prevOutflow + watOutConstantParam * neisTotHeiDiff * flowFac, 0); 
      float outWatFac = cenWat/max(dot(watOutflow, identity4) * _timeStep, 0.001f); 
      outWatFac = min(outWatFac, 1); 
      watOutflow *= outWatFac; 

    // -------------groupshared memory------------- 
    f4shared[gsmID] = watOutflow; 

    GroupMemoryBarrierWithGroupSync(); 

    float4 cenFlow = f4shared[gsmID]; 
    float4 lFlow = f4shared[gsmID - 1]; 
    float4 rFlow = f4shared[gsmID + 1]; 
    float4 tFlow = f4shared[gsmID + _padGPSize]; 
    float4 bFlow = f4shared[gsmID - _padGPSize]; 

    //float4 cenFlow = 0; 
    //float4 lFlow = 0; 
    //float4 rFlow = 0; 
    //float4 tFlow = 0; 
    //float4 bFlow = 0; 

    // -------------Calculate 2------------- 

    if (!outOfGroupBound) { 
      float watDiff = _timeStep *((lFlow.y + rFlow.x + tFlow.w + bFlow.z) - dot(cenFlow, identity4)); 
      cenWat = cenWat + watDiff - _evaporation; 
      cenWat = max(cenWat, 0); 
    } 

    // -------------End of calculation------------- 

    //Water Addition 
    if (_addWater) 
     cenWat += _timeStep * _watAddStrength * _waterAddTex.SampleLevel(_LinearRepeat, uv + _rainUV, 0); 

    if (_computeWaterNormals) 
     _watNormTex[id] = float4(0, 1, 0, 0); 

    // -------------Write results------------- 

    if (!outOfGroupBound) { 
     _flowOutTex[id] = cenFlow; 
     waterField[id] = cenWat; 
    } 

} 

答えて

0

私はひどい間違いを犯しました。グループ共有メモリのパフォーマンスをテストするには、次のようにしました:

// -------------groupshared memory------------- 
f4shared[gsmID] = watOutflow; 

GroupMemoryBarrierWithGroupSync(); 

float4 cenFlow = f4shared[gsmID]; 
float4 lFlow = f4shared[gsmID - 1]; 
float4 rFlow = f4shared[gsmID + 1]; 
float4 tFlow = f4shared[gsmID + _padGPSize]; 
float4 bFlow = f4shared[gsmID - _padGPSize]; 

//float4 cenFlow = 0; 
//float4 lFlow = 0; 
//float4 rFlow = 0; 
//float4 tFlow = 0; 
//float4 bFlow = 0; 

同期後にいくつかの変数があります。私は、値を修正するために変数を初期化する上位5つのステートメントでテストしました。次に、グループを使用せずにテストするために、私は5つのステートメントをコメントし、変数をゼロに初期化する5つのステートメントのコメントを外しました。これは、次のようになりました。

// -------------groupshared memory------------- 
//f4shared[gsmID] = watOutflow; 

//GroupMemoryBarrierWithGroupSync(); 

//float4 cenFlow = f4shared[gsmID]; 
//float4 lFlow = f4shared[gsmID - 1]; 
//float4 rFlow = f4shared[gsmID + 1]; 
//float4 tFlow = f4shared[gsmID + _padGPSize]; 
//float4 bFlow = f4shared[gsmID - _padGPSize]; 

float4 cenFlow = 0; 
float4 lFlow = 0; 
float4 rFlow = 0; 
float4 tFlow = 0; 
float4 bFlow = 0; 

そして、処理がはるかに高速になりました。私が気づかなかったことの1つは、もはや以前の計算を使用せず、コンパイラがそれらを最適化したことでした。より多くのアルゴリズムを実装した後、私は、グループ共有メモリが実際にうまくいっていることに気づき、テクスチャを読み込むのに比べてマイナーな時間を要しました。

結局のところ、1回のパスでアルゴリズムを実行することができました。それは元の実装よりも2.5倍高速で、質問に記載されている技術は私の場合は正常に機能します。

関連する問題