2017-01-31 12 views
0

Я хочу реализовать жидкостное моделирование. Что-то вроде this. Алгоритм не важен. Важная проблема заключается в том, что если бы мы реализовали ее в пиксельном шейдере, это должно быть сделано в несколько проходов.Любая технология для преобразования многопроходного пиксельного шейдера для вычисления шейдера?

Проблема с техникой, которую я использовал, это производительность очень плохо. Я объясню обзор происходящего и метод, используемый для решения расчета за один проход, а затем информацию о времени.

Обзор:

У нас есть местность, и мы хотим, чтобы дождь над ним и увидеть поток воды. У нас есть данные по текстурам 1024x1024. У нас есть высота местности и количество воды в каждой точке. Это итеративное моделирование. Итерация 1 получает рельеф местности и текстуры воды в качестве входных данных, вычисляет и затем записывает результаты на рельеф местности и текстуры воды. Итерация 2 затем запускается и снова меняет текстуры немного больше. После сотен итераций мы имеем что-то вроде этого:

enter image description here

В каждой итерации эти этапы происходят:

  1. Fetch местности и высоту воды.
  2. Рассчитать расход.
  3. Запись значения расхода в память групп.
  4. Sync Group Memory
  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 секунд. Очевидно, что результаты ошибочны.
  • Если я не использую память групп и не загружаю второй расчет с помощью фиктивных значений, время будет 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

OK Я сделал ужасную ошибку. Для проверки работоспособности памяти групп я сделал следующее:

// -------------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 раза быстрее, чем оригинальная реализация, и техника, описанная в вопросах, работает изящно в моем случае.

Смежные вопросы