<p>W dalszej części będziemy zajmować się rozwojem trawy z użyciem równań różniczkowych. Dlatego zalecam zmniejszyć liczbę detali w ustawieniach terenu. Wybierz ustawienia terenu i ustaw <code>Detail Resolution per Patch</code> na 16 i <code>Detail Resolution</code> na 256 lub 128. <imgsrc="img\2.JPG"alt="details"/></p>
</blockquote>
<p>W trakcie tych zajęć skupimy się na dynamicznym modelu rozwoju trawy opartym od równaniach różniczkowych, który pozwoli na stworzenie rozmaitych wzorców wegetacji. Zanim przejdziemy do samego modelu, zapoznamy się z interfacem pozwalającym modyfikować trawę.</p>
<h2id="api-detali">API detali</h2>
<p>Trawa jest rozmieszczana na terenia na podstawie map detali. Są to dwuwymiarowe tablice typu <code>int</code>, której wartości określają zagęszczenie danego detalu w kolejnych polach polu. Tablicę ustawia się za pomocą funkcji <code>terrain.terrainData.SetDetailLayer(int xBase, int yBase, int layer, int[,] details)</code> gdzie <code>xBase</code> i <code>yBase</code> są początkowymi współrzędnymi (domyślnie 0,0), <code>layer</code> jest indeksem detalu a <code>details</code> to tablica opisująca wystąpienia detalu. Ostatnia może mieć maksymalnie wymiar <code>terrain.terrainData.detailWidth-xBase</code> na <code>terrain.terrainData.detailHeight-yBase</code>.</p>
<h3id="zadanie">Zadanie</h3>
<p>Uzupełnij funkcję <code>reset</code> o instrukcje, które ustawią trawę w krzyżyk idący po przekątnej terenu.</p>
<h2id="model-rozwoju-trawy">Model rozwoju trawy</h2>
<p>Do rozwoju trawy wykorzystamy model reakcji-dyfuzji wzorując się na <ahref="https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2007RG000256">pracy</a>. Wykorzystamy układ równań różniczkowych</p>
<p>gdzie <spanclass="math inline">\(r\)</span> i <spanclass="math inline">\(w\)</span> oznaczają odpowiednio wegetację (ilość roślin) i zawartość wody. Natomiast parametry <spanclass="math inline">\(D_w\)</span>, <spanclass="math inline">\(D_r\)</span>, kill i feed są parametrami, które będą kontrolować interakcję.</p>
<p>Analitycznie <spanclass="math inline">\(r\)</span> i <spanclass="math inline">\(w\)</span> są funkcjami od współrzędnych terenu <spanclass="math inline">\(x\)</span> i <spanclass="math inline">\(y\)</span> oraz czasu <spanclass="math inline">\(t\)</span>. Nas nie interesuje rozwiązanie przybliżone, więc współrzędne <spanclass="math inline">\(x\)</span> i <spanclass="math inline">\(y\)</span> będą realizowane jako tablica dwuwymiarowa, natomiast krok czasowy będzie dyskretny. Przy takich założeniach możemy zapisać następująco:</p>
<p>gdzie <spanclass="math inline">\(\text{newWater}[i,j]\)</span> oznacza poziom wody w następnym kroku we współrzędnych i,j, <spanclass="math inline">\(\text{oldWater}[i,j]\)</span> oznacza poziom wody w poprzednim kroku we współrzędnych i,j, analogicznie <spanclass="math inline">\(\text{newVegetation}[i,j]\)</span> i <spanclass="math inline">\(\text{oldVegetation}[i,j]\)</span>. Natomiast <spanclass="math inline">\(dt\)</span> to krok czasowy, jakaś mała wartość około 0.05.</p>
<p>Pozostaje symbol <spanclass="math inline">\(\Delta\)</span> oznacza on operator Laplace i oznacza on sumę drugich pochodnych po współrzędnych, czyli w naszym przypadku: <spanclass="math display">\[\Delta = \frac{\partial^2}{\partial x^2}+\frac{\partial^2}{\partial y^2}.\]</span></p>
<p>Opisuje on różnicę między wartością funkcji w danym punkcie a jego otoczeniem. Ponieważ nasza przestrzeń jest dyskretna musimy skorzystać z dyskretnego laplaciana, który wygląda następująco</p>
<p>Można je zobrazować za pomocą ilusracji: <imgsrc="img/kernel.jpg"alt="aa"/></p>
<h2id="implementacja">Implementacja</h2>
<p>Do implementacji wykorzystamy compute shader. Compute shader to program wykonywany na karcie graficznej. Służy do wykonywania dużej liczby obliczeń, które można wykonywać współbieżnie. Nasz przypadek nadaje się idealnie.</p>
<p>Utwórz nowy compute shader, nazwij go na przykład <code>GrassCalculation</code>. Otwórz go w edytorze, będzie on wyglądać tak</p>
<divclass="sourceCode"id="cb1"><preclass="sourceCode c"><codeclass="sourceCode c"><spanid="cb1-1"><ahref="#cb1-1"aria-hidden="true"tabindex="-1"></a><spanclass="co">// Each #kernel tells which function to compile; you can have many kernels</span></span>
<spanid="cb1-4"><ahref="#cb1-4"aria-hidden="true"tabindex="-1"></a><spanclass="co">// Create a RenderTexture with enableRandomWrite flag and set it</span></span>
<spanid="cb1-5"><ahref="#cb1-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">// with cs.SetTexture</span></span>
<spanid="cb1-6"><ahref="#cb1-6"aria-hidden="true"tabindex="-1"></a><spanclass="co">///// Miejsce na parametry przesyłane </span></span>
<spanid="cb1-10"><ahref="#cb1-10"aria-hidden="true"tabindex="-1"></a><spanclass="co">//Kernel czyli funkcja wywoływana przy aktywacji shadera</span></span>
<p>Ten kod zawiera definicję jednej zmiennej i funkcji, która jest kernelem. Kernel przyjmuje jeden argument typu <code>uint3</code>, który zawiera identyfikator wywołania. Shader uruchamiany jednocześnie w wielu instancjach z różnymi wartościami tego argumentu. Można je indeksować po 3 wymiarach. W naszym przypadku będziemy indeksować za ich pomocą po tablicach.</p>
<p>Zmienne definiowane globalnie, jak <code>RWTexture2D<float4> Result</code>, są parametrami, które przesyła się ze strony CPU. Typy, których nazwy zaczynają się od <code>RW</code>, mogą być modyfikowane przez shader. Dzięki temu mogą być wykorzystywane do zapisywania wyników.</p>
<p>Zaczniemy od napisania prostego shadera shadera, który będzie stopniowo obniżał wegetację o 0.01 całym na terenie. Zacznij od usunięcia definicji zmiennej <code>Result</code> i ciała funkcji <code>CSMain</code>. Tablicę z wegetacja prześlemy jako <code>RWStructuredBuffer<float></code>, zakładamy, że może ona osiągnąć wartości od 0 do 1. Zdefiniuj globalną zmienną <code>vegetation</code> typu <code>RWStructuredBuffer<float></code>.</p>
<p>Shadery nie obsługują 2-wymiarowych tablic, więc ją zastąpić tablicą 1-wymiarową o rozmiarze <spanclass="math inline">\(x*y\)</span> i ręcznie indeksować. Do tego potrzebujemy znać wymiary tablicy, dodaj zmienne <code>int sizeX</code> i <code>int sizeY</code>, których do tego użyjemy. Tablicę będziemy zapisywać wierszami, jak na obrazku.</p>
<p>Wartość o współrzędnych x,y znajduje się pod indeksem <spanclass="math inline">\(x+y*\text{sizeY}\)</span>. Napisz funkcję <code>int index(int x,int y)</code>, która będzie konwertować indeksy z jednego zapisu do drugiego.</p>
<p>Uzupełnij funkcję <code>CSMain</code> o instrukcje, które będzie obniżać wegetację we współrzędnych <code>id.x</code> i <code>id.y</code> wartość obetnij od dołu przez zero a przez jeden od góry z użyciem funkcji <code>clamp</code>. Całość powinna wyglądać tak:</p>
<divclass="sourceCode"id="cb2"><preclass="sourceCode c"><codeclass="sourceCode c"><spanid="cb2-1"><ahref="#cb2-1"aria-hidden="true"tabindex="-1"></a><spanclass="co">// Each #kernel tells which function to compile; you can have many kernels</span></span>
<spanid="cb2-4"><ahref="#cb2-4"aria-hidden="true"tabindex="-1"></a><spanclass="co">// Create a RenderTexture with enableRandomWrite flag and set it</span></span>
<spanid="cb2-5"><ahref="#cb2-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">// with cs.SetTexture</span></span>
<spanid="cb2-6"><ahref="#cb2-6"aria-hidden="true"tabindex="-1"></a><spanclass="co">///// Miejsce na parametry przesyłane </span></span>
<spanid="cb2-15"><ahref="#cb2-15"aria-hidden="true"tabindex="-1"></a><spanclass="co">//Kernel czyli funkcja wywoływana przy aktywacji shadera</span></span>
<p>Mamy napisany shader, teraz pozostaje przesłać dane,uruchomić go i odczytać wyniki. Skrypt <code>GrassController</code> Zawiera szkielet kodu potrzebnego do obsługi shadera. Zanim zaczniemy podepnij <code>GrassComputation</code> w inspektorze pod zmienną <code>computeShader</code>.</p>
<p>By zainicjalizować zwykłe zmienne wykorzystuje się metody <code>SetInt</code> lub <code>SetFloat</code> w zależności od typu. Pierwszym jej argumentem jest id danej zmiennej, pozyskuje się ją za pomocą funkcji <code>Shader.PropertyToID</code>, która przyjmuje nazwę zmiennej jako argument. Drugim jest wartość tej zmiennej.</p>
<p>Uzupełnij funkcję <code>initParameters</code> o inicjalizację zmiennych <code>sizeX</code> i <code>sizeY</code>. przypisz im wartość atrybutów <code>sizeX</code> i <code>sizeY</code> (wartości tych zmiennych pokrywają się z rozmiarem mapy detali).</p>
<p>Bufor trzeba najpierw stworzyć po stronie C#, klasa je obsługująca nazywa się <code>ComputeBuffer</code>. konstruktor przyjmuje 2 argumenty: liczbę komórek i rozmiar jednego pola. Pierwsza z nich w naszym przypadku wynosi <code>sizeX*sizeY</code> a druga <code>sizeof(float)</code>. Stworzony bufor zapisz w jako atrybut o nazwie <code>vegetation</code>, będzie on potrzebny później, żeby odzyskać dane. Bufor inicjalizuje się za pomocą metody <code>SetBuffer(int kernelIndex,str Name, ComputeBuffer buffer)</code> compute shadera. Pierwszy jej argument w naszym przypadku wynosi zero, drugi to nazwa bufora (czyli <code>vegetation</code>) a trzeci to bufor.</p>
<p>Uzupełnij funkcję <code>initComputeShader</code> o inicjalizację bufora wegetacji.</p>
<p>W metodzie <code>initBufferValues</code> stwórz tablicę o wymiarze <code>sizeX*sizeY</code> i zapełnij ją losowymi wartościami. Następnie zapisz ją do bufora <code>vegetation</code> za pomocą metody <code>SetData</code>. usuń Wszystko z metody <code>reset</code>.</p>
<p>Teraz należy wywołać shader i odczytać dane. Shader wywołuje się za pomocą metody <code>Dispatch(int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ)</code>, która jako pierwszy argument przyjmuje numer kernela (czyli 0). Natomiast pozostałe to liczba wątków jakie wywołą w danym wymiarze. Te liczby odpowiadają zakresowi jaki przyjmie wektor <code>id</code> w <code>CSMain</code>. W naszym przypadku te wartości mają odpowiadać wymiarom tablicy, czyli odpowiednio <code>sizeX</code>, <code>sizeY</code> i <code>1</code>.</p>
<p>Uzupełnij funkcję <code>doSimulationSteps(int steps)</code> o wywołanie shader <code>steps</code> razy w pętli.</p>
<p>Odczytanie danych wykonuje się za pomocą metody <code>GetData</code> bufora. Jako argument należy podać tablicę, w której dane zostaną umieszczone. W metodzie <code>recoverData</code> utwórz tablicę typu <code>float</code> o nazwie <code>result</code> o wymiarze <code>sizeX*sizeY</code> i pobierz do niej wartości wegetacji.</p>
<p>Pozostaje zinterpretować wyniki i wykorzystać je do rysowania trawy. Wciąż wewnątrz <code>recoverData</code> utwórz tablicę typu <code>int</code> o wymiarach <code>sizeX</code> na <code>sizeY</code>. W niej umieść podłogę z wyniku z <code>result</code> pomnożonego przez 10.</p>
<h3id="dyfuzja">Dyfuzja</h3>
<p>Teraz zmodyfikujemy compute shader tak, żeby zachodziłą dyfuzja trawy w terenie z użyciem operatora Laplace. Operator odwołuje się do sąsiednich komórek, to może powodować wyjście poza tablicę przy indeksowaniu. Musimy obsłużyć to w jakiś sposób. napisz funkcję <code>float get(StructuredBuffer<float> buffer, int x, int y)</code>, która zwróci wartość tablicy jeżeli <code>index(x,y)</code> jest pomiędzy 0 a <code>sizeX*sizeY</code> i zero w przeciwnym wypadku. Następnie wykorzystaj ją, żeby napisać funkcję <code>float laplace(StructuredBuffer<float> buffer, int x, int y)</code>, która obliczy laplacian dla punkty x,y.</p>
<p>Należy dodać jeszcze jeden <code>RWStructuredBuffer<float></code>, w którym będziemy przechowywać poprzedni krok, nazwij go <code>oldVegetation</code>. Obliczenia będziemy wykonywać na danych z niego i zapisywać w <code>vegetation</code>. to pozwoli uniknąć problemów z synchronizacją bufora. Dodaj też zmienną <code>dt</code> i zainicjalizuj ją atrybutem <code>dt</code>. Zaimplementuj powyższy wzór.</p>
<p>Zainicjalizuj go po stronie C# analogicznie co bufor <code>vegetation</code>. Zapisz go jako atrybut o nazwie . W <code>initBufferValues</code> przypisz mu te same wartości co buforowi <code>vegetation</code>.</p>
<p>Zaimplementuj funkcję <code>swapBuffers</code>, w której zamień ze sobą bufory <code>vegetation</code> i <code>oldVegetation</code> i wywołaj dla nich ponownie <code>computeShader.SetBuffer</code>. W <code>doSimulationSteps</code> dodaj wywołanie <code>swapBuffers</code> przed wywołaniem w pętli shadera.</p>
<li>Dodaj bufory, które będą przechowywać stan wody w obecnym i poprzednim kroku, zainicjalizuj je i obsłuż przy swapowaniu.</li>
<li>Dodaj pozostałe parametry, zainicjalizuj je wartościami atrybutów.</li>
<li>znajdź parametry dające ciekawe wzory, <code>dWater</code> i <code>dVegetation</code> ustaw na <code>0.3</code> i <code>0.1</code> odpowiednio.</li>
<h3id="interakcja-z-terenem">Interakcja z terenem</h3>
<p>W tej chwili trawa rośnie niezależnie od tekstury terenu i jego wysokości, powoduje to, że mamy trawę wyrastającą na drodze albo na szczycie góry. By zniwelować ten problem można uzależnić parametry <code>feed</code> i <code>kill</code> od terenu.</p>
<p>W tym celu stwórz dwa bufory typu <code>float</code> o nazwie <code>feedModifier</code> i <code>killModifier</code>. Następnie zapełnij je wartościami. Uzależnij <code>killModifier</code> od rodzaju tekstury. Pobierz wagi ze spaltmapy i jeżeli wartość śniegu będzie dodatnia ustaw <code>killModifier</code> na 2, w przeciwnym wypadku na 1. Teren ma inną rozdzielczość niż splatmapa, uwzględnij to przy pobieraniu danych.</p>
<p>Po stronie shadera odczytaj wartość wartości <code>feedModifier</code> i <code>killModifier</code> dla danej współrzędnej i przemnóż je przez parametry <code>feed</code> i <code>kill</code> w równaniach.</p>
<p>Uzależnij <code>feedModifier</code> od wysokości terenu. możesz do tego wykorzystać na przykład funkcję <code>transition</code> napisaną na poprzednich zajęciach .</p>