MWS_2021/Treść zadań/cw7.html
2021-05-28 23:21:02 +02:00

184 lines
22 KiB
HTML

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>cw7</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
</style>
<link rel="stylesheet" href="style.css" />
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<h1 id="dynamiczne-rozmieszczenie-traw">Dynamiczne rozmieszczenie traw</h1>
<blockquote>
<h4 id="ustawienie-gęstości-detali">Ustawienie gęstości detali</h4>
<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. <img src="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>
<h2 id="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>
<h3 id="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>
<h2 id="model-rozwoju-trawy">Model rozwoju trawy</h2>
<p>Do rozwoju trawy wykorzystamy model reakcji-dyfuzji wzorując się na <a href="https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2007RG000256">pracy</a>. Wykorzystamy układ równań różniczkowych</p>
<p><span class="math display">\[ \frac{\partial w}{\partial t} = D_w\Delta w-w^2r^2+\text{feed}(0.5+w)\]</span> <span class="math display">\[ \frac{\partial r}{\partial t} = D_r\Delta r+r(wr-\text{kill})\]</span></p>
<p>gdzie <span class="math inline">\(r\)</span> i <span class="math inline">\(w\)</span> oznaczają odpowiednio wegetację (ilość roślin) i zawartość wody. Natomiast parametry <span class="math inline">\(D_w\)</span>, <span class="math inline">\(D_r\)</span>, kill i feed są parametrami, które będą kontrolować interakcję.</p>
<p>Analitycznie <span class="math inline">\(r\)</span> i <span class="math inline">\(w\)</span> są funkcjami od współrzędnych terenu <span class="math inline">\(x\)</span> i <span class="math inline">\(y\)</span> oraz czasu <span class="math inline">\(t\)</span>. Nas nie interesuje rozwiązanie przybliżone, więc współrzędne <span class="math inline">\(x\)</span> i <span class="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><span class="math display">\[ \text{newWater}[i,j] = {oldWater}[i,j]+dt*(D_w\Delta(\text{oldWater},i,j)-\text{oldWater}[i,j]^2*\text{oldVegetation}[i,j]^2+\text{feed}(0.5+\text{oldWater}[i,j]))\]</span> <span class="math display">\[ \text{newVegetation}[i,j] = \text{oldVegetation}[i,j]+dt*(D_r\Delta(\text{oldVegetation},i,j)+\text{oldVegetation}[i,j]*(\text{oldWater}[i,j]*\text{oldVegetation}[i,j]-\text{kill}) )\]</span></p>
<p>gdzie <span class="math inline">\(\text{newWater}[i,j]\)</span> oznacza poziom wody w następnym kroku we współrzędnych i,j, <span class="math inline">\(\text{oldWater}[i,j]\)</span> oznacza poziom wody w poprzednim kroku we współrzędnych i,j, analogicznie <span class="math inline">\(\text{newVegetation}[i,j]\)</span> i <span class="math inline">\(\text{oldVegetation}[i,j]\)</span>. Natomiast <span class="math inline">\(dt\)</span> to krok czasowy, jakaś mała wartość około 0.05.</p>
<p>Pozostaje symbol <span class="math inline">\(\Delta\)</span> oznacza on operator Laplace i oznacza on sumę drugich pochodnych po współrzędnych, czyli w naszym przypadku: <span class="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><span class="math display">\[ T[i,j] = 0.2*(T[i+1,j]+T[i-1,j]+T[i,j+1]+T[i,j-1])+\\0.05*(T[i+1,j+1]+T[i+1,j-1]+T[i-1,j-1]+T[i-1,j+1])-T[i,j].\]</span></p>
<p>Można je zobrazować za pomocą ilusracji: <img src="img/kernel.jpg" alt="aa" /></p>
<h2 id="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>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">// Each #kernel tells which function to compile; you can have many kernels</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#pragma kernel CSMain</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co">// Create a RenderTexture with enableRandomWrite flag and set it</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co">// with cs.SetTexture</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co">///// Miejsce na parametry przesyłane </span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>RWTexture2D&lt;float4&gt; Result;</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">//Kernel czyli funkcja wywoływana przy aktywacji shadera</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>[numthreads(<span class="dv">8</span>,<span class="dv">8</span>,<span class="dv">1</span>)]</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> CSMain (uint3 id : SV_DispatchThreadID)</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>{</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="co">// </span><span class="al">TODO</span><span class="co">: insert actual code here!</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> Result[id.xy] = float4(id.x &amp; id.y, (id.x &amp; <span class="dv">15</span>)/<span class="fl">15.0</span>, (id.y &amp; <span class="dv">15</span>)/<span class="fl">15.0</span>, <span class="fl">0.0</span>);</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<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&lt;float4&gt; 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&lt;float&gt;</code>, zakładamy, że może ona osiągnąć wartości od 0 do 1. Zdefiniuj globalną zmienną <code>vegetation</code> typu <code>RWStructuredBuffer&lt;float&gt;</code>.</p>
<p>Shadery nie obsługują 2-wymiarowych tablic, więc ją zastąpić tablicą 1-wymiarową o rozmiarze <span class="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>
<figure>
<img src="img/index.jpg" alt="index" /><figcaption aria-hidden="true">index</figcaption>
</figure>
<p>Wartość o współrzędnych x,y znajduje się pod indeksem <span class="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>
<div class="sourceCode" id="cb2"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">// Each #kernel tells which function to compile; you can have many kernels</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#pragma kernel CSMain</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="co">// Create a RenderTexture with enableRandomWrite flag and set it</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="co">// with cs.SetTexture</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="co">///// Miejsce na parametry przesyłane </span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>RWStructuredBuffer&lt;<span class="dt">float</span>&gt; vegetation;</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> sizeX;</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> sizeY;</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> index(<span class="dt">int</span> x, <span class="dt">int</span> y){</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> x+y*sizeX;</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="co">//Kernel czyli funkcja wywoływana przy aktywacji shadera</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>[numthreads(<span class="dv">8</span>,<span class="dv">8</span>,<span class="dv">1</span>)]</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> CSMain (uint3 id : SV_DispatchThreadID)</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>{</span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a> vegetation[index(id.x, id.y)] = clamp(vegetation[index(id.x, id.y)] - <span class="fl">0.01</span>,<span class="dv">0</span>,<span class="dv">1</span>);</span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<h3 id="obsługa-shadera">Obsługa shadera</h3>
<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>
<h3 id="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&lt;float&gt; 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&lt;float&gt; buffer, int x, int y)</code>, która obliczy laplacian dla punkty x,y.</p>
<p>Dyfuzje można opisać wzorem</p>
<p><span class="math display">\[\frac{\partial v}{\partial t}=\Delta v.\]</span></p>
<p>W wersji dyskretnej będzie to:</p>
<p><span class="math display">\[\text{newV[i,j]} = \text{oldV}[i,j] + \Delta(\text{oldV},i,j).\]</span></p>
<p>Należy dodać jeszcze jeden <code>RWStructuredBuffer&lt;float&gt;</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. 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>
<h3 id="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>
<h3 id="zadanie-1">Zadanie</h3>
<p>W tym celu stwórz dwa bufory typu <code>float</code> o nazwie <code>feedModifier</code> i <code>killModifier</code> i rozmiarze <code>sizeX*sizeY</code>. Następnie zapełnij je wartościami. Uzależnić <code>killModifier</code> od rodzaju tekstury.</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>
<h3 id="zadanie-domowe">Zadanie domowe</h3>
<p>Uzależnij <code>feedModifier</code> od wysokości terenu. możesz do tego wykorzystać na przykład funkcję <code>transition</code> napisaną wcześniej.</p>
</body>
</html>