2017-01-31 31 views
0

我想實現流體模擬。類似於this。該算法並不重要。重要的問題是,如果我們要在像素着色器中實現它,則應該多次完成。任何將多通道像素着色器轉換爲計算着色器的技術?

我用來做這個技術的問題是性能非常差。我將解釋發生了什麼事情的概述,以及用於一次性計算計算的技術,然後解釋計時信息。

概述:

我們有一個地形,我們要下雨了,看看水流。我們有關於1024x1024紋理的數據。我們有地形的高度和每個點的水量。這是一個迭代模擬。迭代1將地形和水紋理作爲輸入,計算並將結果寫入地形和水紋理。迭代2然後運行並再次改變紋理。數百名迭代後,我們有這樣的事情:

enter image description here

在每次迭代這些階段發生:

  1. 獲取地形和水面高度。
  2. 計算流量。
  3. 將流量值寫入組共享內存。
  4. 同步組內存
  5. 從該羣組共享內存中讀取該線程的流量值以及當前線程左側,右側,頂部和底部的線程。
  6. 根據上一步驟中讀取的流量值計算水的新值。
  7. 將結果寫入地形和水紋理。

所以基本上我們獲取數據,做calculate1,把calculate1結果共享存儲,同步,從當前線程和鄰居共享內存取,做calculate2作,並寫出結果。

這是一個清晰的模式,發生在一個非常廣泛的圖像處理問題。經典的解決方案將是一個多通道着色器,但是我通過一次通過計算着色器來實現,以節省帶寬。

技術:

我所使用的技術在Practical Rendering and Computation with Direct3D 11第12章解釋假設我們希望每個線程組是16x16x1線程。但因爲第二次計算也需要鄰居,所以我們在每個方向上填充像素。這意味着我們將擁有18x18x1線程組。由於這種填充,我們將在第二次計算中使用有效的鄰居。這是一張顯示填充的圖片。黃線是需要計算的,而紅線是填充的。它們是線程組的一部分,但我們只是將它們用於中間處理,並不會將它們保存爲紋理。請注意,在這幅圖中,填充組爲10x10x1,但我們的線程組爲18x18x1。

enter image description here

進程中運行,並返回正確的結果。唯一的問題是性能。

時間: 在系統與Geforce GT 710我運行與10000次迭代的模擬。

  • 運行完整和正確的模擬需要60秒。
  • 如果我沒有填充邊框並使用16x16x1線程組,則時間將是40秒。顯然結果是錯誤的。
  • 如果我不使用groupshared內存,並用虛擬值進行第二次計算,則時間爲19秒。結果當然是錯誤的。

問題:

  1. 這是解決這個問題的最好的技術?如果我們用兩個不同的內核來計算,它會更快。 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; 

您看到同步後有一些變量。我測試了前五個將變量初始化爲正確值的語句。然後在沒有團體共享的情況下進行測試,我對五條語句進行了評論,並取消了將下面五條語句初始化爲零的註釋。這是它如何變成:

// -------------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遍的算法。它比原來的執行速度快2.5倍,並且在我的案例中,問題中描述的技術工作得很好。