diff --git a/Treść zadań/.vscode/settings.json b/Treść zadań/.vscode/settings.json new file mode 100644 index 0000000..39d574a --- /dev/null +++ b/Treść zadań/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.language": ",pl" +} \ No newline at end of file diff --git a/Treść zadań/cw6.html b/Treść zadań/cw6.html new file mode 100644 index 0000000..3aca45f --- /dev/null +++ b/Treść zadań/cw6.html @@ -0,0 +1,287 @@ + + + + + + + cw6 + + + + + + +

Tworzenie krajobrazu

+

W trakcie tych zajęć skupimy się na tworzeniu krajobrazów z wykorzystaniem pakietu Terrain Tools i darmowych assetów dostępnych w sklepie unity. W pierwszej części skupimy się na ręcznym kształtowaniu terenu. Z tematem można sie zapoznać także korzystając z licznych tutoriali np tym lub tym.

+

Przygotowanie

+

Do odtworzenia wyników z zajęć w nowym projekcie wymagany jest pakietu Terrain Tools, który jest w wersji preview, żeby go pobrać najpierw trzeba ustawić wyświetlanie tych pakietów w menadżerze. Poza tym potrzebne będą assety terenu, można wykorzystać te, które są dostępne jako przykładowe albo znaleźć inne (można na przykład pobrać jakieś darmowe sceny i ukraść z nich assety do celów edukacyjnych) lub zrobić je samemu.

+

Inicjalizacja terenu

+

Obok zakładki Inspector powinna być zakładka Terrain Toolbox, która służy do zarządzania obiektami terenu. Jeżeli jej tam nie ma, możesz ją znaleźć pod opcją Window > Terrain > Terrain Toolbox. Zaznacz Create New Terrain, powinno się pojawić poniższe okno:

+
+size +
+

ustaw wymiary (szerokość wysokość i długość) terenu wedle własnego uznania, domyślnie jest to 500 na 500 na 600, ale dla oszczędzenia mocy obliczeniowej systemu w dalszych punktach zalecam ograniczyć się do 100 na 100 na 200. (Te opcje można też później zmienić w ustawieniach terenu lub globalnie dla wszystkich terenów w Terrain Toolbox > Terrain Settings.) Pozostaje stworzyć teren za pomocą przycisku Create na dole okna

+

Opcje

+

Stworzony teren pojawi się w postaci płaskiej szachownicy w widoku sceny i w hierarchii wewnątrz grupy terenów

+
+stworzony teren +
+

Teraz skupimy się na opcjach jakie możliwości oferuje teren jako komponent terenu. Przełącz się na zakładkę Inspector i zaznacz teren. W inspektorze możesz zobaczyć szereg opcji jakie teren oferuje pogrupowanych w zakładki.

+
+stworzony teren +
+

Interesujące nas części to:

+ +

Formowanie Powierzchni

+

Kształt terenu jest opisywany przez heightmapę. Czyli maskę, której jasność oznacza wysokość terenu. Ta jest interpretowana przez unity i przetwarzana na mesh. Dzięki temu podejściu projektant nie musi martwić się o geometrię terenu, która jest generowana przez silnik. Wadą jest, że wszelkie wypukłości i lub jaskinie muszą być obsługiwane w inny sposób.

+

Tworzenie sceny zaczniemy od wyrzeźbienia powierzchni terenu. Przejdź do zakładki Malowanie terenu. Pod paskiem z zakładkami jest lista rozwijana, w której można wybrać tryby malowania. Poniżej listy jest pole tekstowe, które opisuje co robi i dana opcja. Pod nią opcje pędzla, przede wszystkim można wybrać jakiego pędzla chce się używać, poza tym można modyfikować jego parametry, czyli rozmiar siłę, nacisku i spacing. Można dodawać swoje własne pędzle. Jeszcze niżej są opcje poszczególnych trybów - istotniejsze są omówione poniżej.

+

Do formowania terenu interesuje nas pierwsze pięć i ostatnie cztery opcji, są to:

+ +

Poza tym można dodawać filtry na maski modyfikując ich możliwości.

+

Przydatne skróty:

+ +
+

Nim zaczniesz modelować teren dodaj jakiś obiekt, który będzie służył za punkt odniesienia, na przykład model postaci albo jakąś figurę geometryczną. Model wykorzystany w trakcie zajęć pochodzi stąd.

+
+

Zadanie

+

wybierz jeden z krajobrazów znajdujących się w folderze landscapes i wymodeluj teren nimi inspirowany. Zdjęcia nie będą punktem odniesienia w ocenianiu. służą jedynie jako inspiracja do zrobienia terenu.

+

Gdy skończysz, przenieś kamerę w miejsce docelowe. Możesz założyć, że scena jest statyczna, to znaczy, kamera się nie porusza. Zaznacz kamerę i użyj Ctrl+Shift+F by ustawić kamerę z widokiem sceny.

+

Teksturowanie terenu

+
+

Tekstury terenu są jak cebula, mają warstwy

+
+

Na teren nakłada różne tekstury odpowiadające za rożne rodzaje powierzchni, takie podejście pozwala uniknąć monotonii i czyni teren bardziej atrakcyjnym. Osiąga się to za pomocą palety warstw oraz splatmapy, która opisuje z jakimi parametrami silnik ma mieszać warstwy.

+

By przejść do teksturowania, należy wybrać opcję Texture Paint dostępną w rozwijanym menu.

+

Paleta Tekstur

+

Pierwszym krokiem jest stworzenie lub wczytanie palety tekstur, które będą nakładane na teren. Ustawienia palety znajdziemy w zakładce Layers.

+
+details +
+

Gotowe warstwy można dodać za pomocą przycisku Add Layer a nowe za pomocą Create. Jednak tutaj trzeba wziąć pod uwagę, że dodaje się tylko teksturę, która odpowiada za kolor, pozostałe trzeba dodać do warstwy ręcznie.

+

Warstwa terenu składa się z 3 tekstur:

+ +

Nie ma potrzeby, żeby wszystkie tekstury znalazły sie w warstwie, zalecane jest, żeby były przynajmniej dwie pierwsze.

+

Kolejność tekstur w palecie można swobodnie zmieniać, ale najlepiej ustalić ją na początku. Tekstury są indeksowane w splatmapie po ich kolejności w palecie, więc zmiana jej w trakcie malowania spowoduje nieprzewidziane w efekcie końcowym.

+

Warstwy podobnie jak materiały można skalować za pomocą opcji tilling, z jednej storny pozwoli to na dopasowanie ich do sceny, z drugiej, stworzenie kilku warstw różniących się tylko tym parametrem może być dobrym sposobem na szybkie urozmaicenie sceny

+

Rysowanie terenu

+

Pierwsza warstwa od góry jest tą domyślną, więc umieść ją na szczycie. Kolejność pozostałych nie ma znaczenia, póki nie zaczniesz nakładać ich na teren. Tekstury nakładasz wybierając teksturę i pędzel, następnie nakładasz ją na teren kliknięciami i pociągnięciami myszki. Spróbuj różnych ustawień pędzla. Przykładowo zwiększenie wartości Brush Scatter, Brush Spacing i Jitter i użycie nieregularnego pędzla da efekt rozrzucenia plam w losowych miejscach.

+

Zadanie

+

Utwórz paletę korzystając z dostępnych warstw i tekstur. Następnie wykorzystaj ją do oteksturowania swojego terenu. Dodaj do swojego krajobrazu jakąś ścieżkę i nadaj jej odpowiednie tekstury.

+

Rozmieszczenie drzew

+
+

Zanim zaczniemy dodaj do kamery skrypt Simple Camera Controller, ułatwi on poruszanie się w scenie

+
+

Kolejne narzędzie służy do rozmieszczania drzew i innych większych elementów krajobrazu.

+
+trees +
+

Nowy rodzaj drzew dodaje się za pomocą Edit Trees > Add Trees, opcje w Edit Trees służy także do zmiany modelu drzewa czy usuwaniu rodzaju drzewa.

+

Poniżej są suwaki odpowiadające za opcje ustawiania drzew. Dwa pierwsze modyfikują rozmiar i gęstość pędzla. kolejne odpowiadają za dostosowanie wielkości drzew. Opcja Lock Width to Height uzależnia szerokość drzewa od wysokości. Jej wyłączenie zwiększy różnorodność drzew, ale może prowadzić do niepożądanych efektów, przy źle dobranych parametrach.

+

Można też umieścić drzewa losowo za pomocą Mass Place Trees, gdzie użytkownik może wybrać ile drzew ma się pojawić, a te zostaną umieszone losowo w scenie.

+

Umieszczanie detali

+

Kolejna zakładka służy do dodawania detali, jak trawa i rośliny polne. Jest bardzo podobna do tej od drzew. Podobnie za pomocą Edit Details można dodać edytować lub usuwać detale. Dodatkowo jest opcja Add Grass Texture, która pozwala dodać teksturę jako bilboard. Działanie pierwszego suwaka odpowiada za wielkość pędzla. Natomiast trzeciego za maksymalną ma być na tym terenie, a drugi za tępo w jakim tą wielkość się osiągnie. Jakby to porównać do malowania, to Target Strength by odpowiadało za siłę nasycenia, ale Opacity za siłę krycia.

+
+details +
+

Dodawanie Detali

+

Poniżej jest okno jakie się pojawia przy dodawaniu/edytowaniu detalu. Można w nim dostosować rozmiar, żeby zgadzał się z rozmiarem reszty obiektów w scenie. Wartości kolorów odpowiadają za modyfikacje odcienia detali w zależności od położenia, by nadać im większą różnorodność. Warto dopasować wartości kolorów, Healthy Color z reguły powinien być biały, natomiast Dry Color nie powinnien zbytnio odbiegać od oryginalnego koloru obiektu.

+
+details +
+

Dodatkowe informacje

+

Dodawanie Skał i innych obiektów

+

Narzędzia terenu same w sobie się nie nadają na tworzenie ostrych skał czy klifów, by je dodać do sceny lepiej dodać je jako osobne obiekty. Można to zrobić za pomocą systemu drzew, co jednak może dać nierealistyczne efekty lub umieścić je samemu ręcznie.

+

Druga opcja jest preferowana, gdy chcemy wystającą skałę lub klif umieścić blisko kamery lub w centralnym punkcie naszej sceny. Słabym punktem takiego obiektu jest miejsce połączenia z terenem. Można je zamaskować poprzez dodanie w tym miejscu detali, jak trawa i/lub małe kamyki.

+
+details +
+

Post-processing

+

Post processing to zbiór technik wykorzystywanych wykonywanych na wyrenderowanej klatce. Dzięki nim można poprawić wygląd sceny lub dodać efekty jak mgła, bloom czy flary.

+

Unity oferuje pakiet, który udostępnia podstawowe efekty postprocessingu o nazwie PostProcessing. By go zainicjalizować należy dodać do kamery komponenty Post-Proces Layer i Post-Proces Volume. Następnie dodać dodać nową warstwę w Layer > Add Layer..., nazwij ją Post Processing. Na koniec ustawić warstwę na Post Processing w kamerze i w Post-Proces Layer.

+
+details +
+

Teraz wystarczy stworzyć profil w Post-Proces Volume i dodać do niego komponenty.

+
+

Ustaw opcję Is Global żeby efekty były widoczne także w widoku sceny

+
+

Niektóre dostępne obiekty efekty

+

Color Grading - pozwala dostosować kolorystykę sceny, zmodyfikować ekspozycję i kontrast. Do prawidłowego funkcjonowania należy zmienić przestrzeń kolorów na liniową można to zrobić w Edit > Project Settings > Player > Rendering > Color Space.

+

Ambient Occlusion - dodaje okluzję otoczenia, czyli zacienienie dla światła rozproszonego wynikającego z geometrii. Zalecane jest wybrać Scalable Ambient Obscurance jako typ.

+

Vigniette, Chromatic Aberration, Bloom - efekty symulujące działanie rzeczywistych kamer zwiększają filmowość obrazu.

+

Motion Blur - rozmycie w ruchu, nada więcej realizmu ruchom trawy i gałęzi.

+
+details +
+

Zadanie domowe

+

Znajdź w internecie model domku lub innego budynku i zbuduj wokół niego scenę przedstawiającą go na odludziu w otoczeniu przyrody. Wymodeluj teren, dodaj drzewa, trawę i inne detale, wykorzystaj post-processing. Możesz wykorzystać assety znajdujące się w projekcie lub ściągnąć własne. Postaraj się, żeby teren wyglądał estetycznie. Efekt końcowy może być statycznym ujęciem, w takim przypadku umieść kamerę w odpowiednim miejscu.

+

Teskturowanie w oparciu o ukształtowanie terenu

+

Teksturowanie można częściowo zautomatyzować uzależniając występowanie warstwę od własności terenu, takich jak wysokość czy kąt nachylenia. Przypomnijmy, że to jaka i w jakim stopniu dana warstwa zostanie narysowana jest dyktowane przez splatmapę. Po stronie skryptu jest to tablica trójwymiarowa, której 2 pierwsze współrzędne to szerokość i wysokość w mapie a trzecia jest indeksem warstwy.

+

Otwórz Scenę Procedural Terrain. Wewnątrz znajduje się teren z podpiętym skryptem Procedural Texture Script, otwórz go w edytorze. Skrypt zawiera funkcję runProcedrualTexturing, w która ma teksturować teren. W tej chwili przypisuje każdemu punktowi losową wartość i wygląda następująco.

+
    public void runProcedrualTexturing() {
+        var terrain = gameObject.GetComponent<Terrain>();
+        var layers = terrain.terrainData.alphamapLayers;
+        var height = terrain.terrainData.alphamapWidth;
+        var width = terrain.terrainData.alphamapHeight;
+
+
+        var newSplatMap = new float[width, height, layers];
+        
+        for (int i=0; i < width;i++) {
+            for (int j = 0; j < width; j++) {
+                float x = j / (float)height;
+                float y = i / (float)width;
+
+                var splatWeights = new float[layers];
+
+                for (int k = 0; k < layers; k++) {
+                    splatWeights[k] = Random.RandomRange(0.0f,1.0f);
+                }
+                float sum = splatWeights.Sum();
+
+                for (int k = 0; k < layers; k++) {
+                    newSplatMap[i,j,k]=splatWeights[k]/sum;
+                }
+            }
+        }
+        terrain.terrainData.SetAlphamaps(0, 0, newSplatMap);
+    }
+

Przeanalizujmy instrukcje funkcji. Pierwsza pobiera komponent terenu. Następne trzy pobierają odpowiednio liczbę warstw, wysokość i szerokość splatmapy. Linia var newSplatMap = new float[width, height, layers]; tworzy tablicę, która będzie służyć za nową splatmapę. Następnie w pętli przechodzi po wszystkich indeksach splatmapy.

+
float x = j / (float)height;
+float y = i / (float)width;
+

Powyższe instrukcje dają współrzędne unormowane, które będą przydatne póżniej. Tablica splatWeights będzie przechowywać wagi warstw.

+
for (int k = 0; k < layers; k++) {
+    splatWeights[k] = Random.RandomRange(0.0f,1.0f);
+}
+

Ustawia indeksy na losowe wartości

+
float sum = splatWeights.Sum();
+for (int k = 0; k < layers; k++) {
+    newSplatMap[i,j,k]=splatWeights[k]/sum;
+}
+

Umieszcza wagi w splatmapie i normalizuje je. Suma wag powinna być równa zero, dlatego dzielimy przez sumę tablicy.

+
terrain.terrainData.SetAlphamaps(0, 0, newSplatMap);
+

Ustawia nową splatmapę

+

Zadanie

+

Zmodyfikuj funkcję tak, żeby teren, który znajduje się wyżej niż snowHeight był śniegiem a poniżej trawą. Wykorzystaj metodę terrain.terrainData.GetInterpolatedHeight by uzyskać poziom we współrzędnych x i y.

+

Wykorzystanie zwykłego warunku logicznego da ostre przejście, które wygląda nierealistycznie. Napisz funkcję transition(float start, float end,float value) która:

+ +

I wykorzystaj ją by zrobić bardziej stopniowe przejście

+

Zadanie domowe

+

Wykonaj coś podobnego dla tekstury kamieni, teraz zamiast wysokości terenu wykorzystaj kąt nachylenia, możesz go uzyskać za pomocą metody terrain.terrainData.GetSteepness.

+ + diff --git a/Treść zadań/cw6.md b/Treść zadań/cw6.md new file mode 100644 index 0000000..b1dc329 --- /dev/null +++ b/Treść zadań/cw6.md @@ -0,0 +1,279 @@ +# Tworzenie krajobrazu + +W trakcie tych zajęć skupimy się na tworzeniu krajobrazów z wykorzystaniem pakietu *Terrain Tools* i darmowych assetów dostępnych w sklepie unity. W pierwszej części skupimy się na ręcznym kształtowaniu terenu. Z tematem można sie zapoznać także korzystając z licznych tutoriali np [tym](https://youtu.be/smnLYvF40s4) lub [tym](https://www.youtube.com/watch?v=ddy12WHqt-M). + + +## Przygotowanie + +Do odtworzenia wyników z zajęć w nowym projekcie wymagany jest pakietu *Terrain Tools*, który jest w wersji preview, żeby go pobrać najpierw trzeba ustawić wyświetlanie tych pakietów w menadżerze. Poza tym potrzebne będą assety terenu, można wykorzystać te, które są dostępne jako przykładowe albo znaleźć inne (można na przykład pobrać jakieś darmowe sceny i ukraść z nich assety do celów edukacyjnych) lub zrobić je samemu. + +## Inicjalizacja terenu + +Obok zakładki **Inspector** powinna być zakładka **Terrain Toolbox**, która służy do zarządzania obiektami terenu. Jeżeli jej tam nie ma, możesz ją znaleźć pod opcją `Window > Terrain > Terrain Toolbox`. Zaznacz **Create New Terrain**, powinno się pojawić poniższe okno: + +![size](img\1.JPG) + +ustaw wymiary (szerokość wysokość i długość) terenu wedle własnego uznania, domyślnie jest to 500 na 500 na 600, ale dla oszczędzenia mocy obliczeniowej systemu w dalszych punktach zalecam ograniczyć się do 100 na 100 na 200. (Te opcje można też później zmienić w ustawieniach terenu lub globalnie dla wszystkich terenów w `Terrain Toolbox > Terrain Settings`.) Pozostaje stworzyć teren za pomocą przycisku **Create** na dole okna + +## Opcje + +Stworzony teren pojawi się w postaci płaskiej szachownicy w widoku sceny i w hierarchii wewnątrz grupy terenów + +![stworzony teren](img\3.JPG) + +Teraz skupimy się na opcjach jakie możliwości oferuje teren jako komponent terenu. Przełącz się na zakładkę **Inspector** i zaznacz teren. W inspektorze możesz zobaczyć szereg opcji jakie teren oferuje pogrupowanych w zakładki. + +![stworzony teren](img\4.JPG) + +Interesujące nas części to: + +* **Malowanie terenu** służy do rzeźbienia powierzchni terenu i nakładaniu tekstury na terenie. +* **Rozmieszenie drzew**, jak nazwa wskazuje służy do rozmieszczania drzew. +* **Rozmieszczenie detali** takich jak trawy, kwiaty polne czy kamienie + + + +## Formowanie Powierzchni + + +Kształt terenu jest opisywany przez heightmapę. Czyli maskę, której jasność oznacza wysokość terenu. Ta jest interpretowana przez unity i przetwarzana na mesh. Dzięki temu podejściu projektant nie musi martwić się o geometrię terenu, która jest generowana przez silnik. Wadą jest, że wszelkie wypukłości i lub jaskinie muszą być obsługiwane w inny sposób. + +Tworzenie sceny zaczniemy od wyrzeźbienia powierzchni terenu. Przejdź do zakładki **Malowanie terenu**. Pod paskiem z zakładkami jest lista rozwijana, w której można wybrać tryby malowania. Poniżej listy jest pole tekstowe, które opisuje co robi i dana opcja. Pod nią opcje pędzla, przede wszystkim można wybrać jakiego pędzla chce się używać, poza tym można modyfikować jego parametry, czyli rozmiar siłę, nacisku i spacing. Można dodawać swoje własne pędzle. Jeszcze niżej są opcje poszczególnych trybów - istotniejsze są omówione poniżej. + +Do formowania terenu interesuje nas pierwsze pięć i ostatnie cztery opcji, są to: + +* Sculpt - zbiór narzędzi związanych z rzeźbieniem, należą do nich: + + * Bridge - łączy dwa zaznaczone obszary mostem. + * Clone - kopiuje zaznaczony obszar w wybrane miejsce. + * Noise - modyfikuje teren z uwzględnieniem tekstury szumu - opcje pozwalają dostosowywać teksturę szumu. + * Terrace - tworzy tarasy na wzniesieniach - opcje pozwalają modyfikować liczbę tarasów i ostrość kątów. + +* Effects - Różnego rodzaju efekty. + + * Contrast - zwiększa rożnicę wysokości - opcje pozwalają ustalić rozmiar cech jakie będą brane pod uwagę. + * Sharpen Peaks. + * Slope Flatten. + +* Erosion - różne rodzaje erozji +* Mesh Stamp - pozwala odcisnąć mesh znajdujący się w assetach projektu. +* Raise or Lower Terrain - podnosi lub opuszcza teren, główne narzędzie przy formowaniu. +* Transform - ściskanie, przesuwanie i obracanie. +* Set Height - ustawia teren na wpisanej przez użytkownika wysokości. +* Smooth Height - wygładza teren. + +Poza tym można dodawać filtry na maski modyfikując ich możliwości. + +Przydatne skróty: + +* **A** Modyfikuje siłę nacisku pędzla. +* **S** Modyfikuje rozmiar pędzla. +* **D** Modyfikuje obrót pędzla. +* **Control+Lewy Przycisk Myszy** Odwraca działanie pędzla lub uruchamia jego alternatywny tryb. +* **Shift+Lewy Przycisk Myszy** Przełącza na wygładzanie. + +> Nim zaczniesz modelować teren dodaj jakiś obiekt, który będzie służył za punkt odniesienia, na przykład model postaci albo jakąś figurę geometryczną. Model wykorzystany w trakcie zajęć pochodzi [stąd](https://sketchfab.com/3d-models/shrek-3d-model-541d5a5dd9914679919cc1d9a437097e). + +### Zadanie + +wybierz jeden z krajobrazów znajdujących się w folderze `landscapes` i wymodeluj teren nimi inspirowany. Zdjęcia nie będą punktem odniesienia w ocenianiu. służą jedynie jako inspiracja do zrobienia terenu. + +Gdy skończysz, przenieś kamerę w miejsce docelowe. Możesz założyć, że scena jest statyczna, to znaczy, kamera się nie porusza. Zaznacz kamerę i użyj `Ctrl+Shift+F` by ustawić kamerę z widokiem sceny. + +## Teksturowanie terenu + +> Tekstury terenu są jak cebula, mają warstwy + +Na teren nakłada różne tekstury odpowiadające za rożne rodzaje powierzchni, takie podejście pozwala uniknąć monotonii i czyni teren bardziej atrakcyjnym. Osiąga się to za pomocą palety warstw oraz splatmapy, która opisuje z jakimi parametrami silnik ma mieszać warstwy. + +By przejść do teksturowania, należy wybrać opcję **Texture Paint** dostępną w rozwijanym menu. + +### Paleta Tekstur + +Pierwszym krokiem jest stworzenie lub wczytanie palety tekstur, które będą nakładane na teren. Ustawienia palety znajdziemy w zakładce **Layers**. + +![details](img\5.JPG) + +Gotowe warstwy można dodać za pomocą przycisku **Add Layer** a nowe za pomocą **Create**. Jednak tutaj trzeba wziąć pod uwagę, że dodaje się tylko teksturę, która odpowiada za kolor, pozostałe trzeba dodać do warstwy ręcznie. + +Warstwa terenu składa się z 3 tekstur: + +* Diffuse - kolor warstwy +* Normal - normalna +* Mask - zawiera na poszczególnych dodatkowo kanałach informacje standardowo to są: + * Czerwony - Metallic + * Zielony - Occlusion + * Niebieski - Height poprawia jakoś mieszania tekstur + * Alfa - Smoothness + +Nie ma potrzeby, żeby wszystkie tekstury znalazły sie w warstwie, zalecane jest, żeby były przynajmniej dwie pierwsze. + +Kolejność tekstur w palecie można swobodnie zmieniać, ale najlepiej ustalić ją na początku. Tekstury są indeksowane w splatmapie po ich kolejności w palecie, więc zmiana jej w trakcie malowania spowoduje nieprzewidziane w efekcie końcowym. + +Warstwy podobnie jak materiały można skalować za pomocą opcji tilling, z jednej storny pozwoli to na dopasowanie ich do sceny, z drugiej, stworzenie kilku warstw różniących się tylko tym parametrem może być dobrym sposobem na szybkie urozmaicenie sceny + +### Rysowanie terenu + +Pierwsza warstwa od góry jest tą domyślną, więc umieść ją na szczycie. Kolejność pozostałych nie ma znaczenia, póki nie zaczniesz nakładać ich na teren. Tekstury nakładasz wybierając teksturę i pędzel, następnie nakładasz ją na teren kliknięciami i pociągnięciami myszki. Spróbuj różnych ustawień pędzla. Przykładowo zwiększenie wartości *Brush Scatter*, *Brush Spacing* i *Jitter* i użycie nieregularnego pędzla da efekt rozrzucenia plam w losowych miejscach. + +### Zadanie + +Utwórz paletę korzystając z dostępnych warstw i tekstur. Następnie wykorzystaj ją do oteksturowania swojego terenu. Dodaj do swojego krajobrazu jakąś ścieżkę i nadaj jej odpowiednie tekstury. + +## Rozmieszczenie drzew + +> Zanim zaczniemy dodaj do kamery skrypt `Simple Camera Controller`, ułatwi on poruszanie się w scenie + + +Kolejne narzędzie służy do rozmieszczania drzew i innych większych elementów krajobrazu. + +![trees](img\6.JPG) + +Nowy rodzaj drzew dodaje się za pomocą `Edit Trees > Add Trees`, opcje w **Edit Trees** służy także do zmiany modelu drzewa czy usuwaniu rodzaju drzewa. + +Poniżej są suwaki odpowiadające za opcje ustawiania drzew. Dwa pierwsze modyfikują rozmiar i gęstość pędzla. kolejne odpowiadają za dostosowanie wielkości drzew. Opcja Lock **Width to Height** uzależnia szerokość drzewa od wysokości. Jej wyłączenie zwiększy różnorodność drzew, ale może prowadzić do niepożądanych efektów, przy źle dobranych parametrach. + +Można też umieścić drzewa losowo za pomocą `Mass Place Trees`, gdzie użytkownik może wybrać ile drzew ma się pojawić, a te zostaną umieszone losowo w scenie. + +## Umieszczanie detali + + + + + +Kolejna zakładka służy do dodawania detali, jak trawa i rośliny polne. Jest bardzo podobna do tej od drzew. Podobnie za pomocą Edit Details można dodać edytować lub usuwać detale. Dodatkowo jest opcja `Add Grass Texture`, która pozwala dodać teksturę jako [bilboard](https://youtu.be/puOTwCrEm7Q). Działanie pierwszego suwaka odpowiada za wielkość pędzla. Natomiast trzeciego za maksymalną ma być na tym terenie, a drugi za tępo w jakim tą wielkość się osiągnie. Jakby to porównać do malowania, to **Target Strength** by odpowiadało za siłę nasycenia, ale **Opacity** za siłę krycia. + + +![details](img\7.JPG) + + +### Dodawanie Detali + +Poniżej jest okno jakie się pojawia przy dodawaniu/edytowaniu detalu. Można w nim dostosować rozmiar, żeby zgadzał się z rozmiarem reszty obiektów w scenie. Wartości kolorów odpowiadają za modyfikacje odcienia detali w zależności od położenia, by nadać im większą różnorodność. Warto dopasować wartości kolorów, **Healthy Color** z reguły powinien być biały, natomiast **Dry Color** nie powinnien zbytnio odbiegać od oryginalnego koloru obiektu. + +![details](img\8.JPG) + +## Dodatkowe informacje + +### Dodawanie Skał i innych obiektów + +Narzędzia terenu same w sobie się nie nadają na tworzenie ostrych skał czy klifów, by je dodać do sceny lepiej dodać je jako osobne obiekty. Można to zrobić za pomocą systemu drzew, co jednak może dać nierealistyczne efekty lub umieścić je samemu ręcznie. + +Druga opcja jest preferowana, gdy chcemy wystającą skałę lub klif umieścić blisko kamery lub w centralnym punkcie naszej sceny. Słabym punktem takiego obiektu jest miejsce połączenia z terenem. Można je zamaskować poprzez dodanie w tym miejscu detali, jak trawa i/lub małe kamyki. + +![details](img\9.JPG) + +### Post-processing + +Post processing to zbiór technik wykorzystywanych wykonywanych na wyrenderowanej klatce. Dzięki nim można poprawić wygląd sceny lub dodać efekty jak mgła, bloom czy flary. + +Unity oferuje pakiet, który udostępnia podstawowe efekty postprocessingu o nazwie **PostProcessing**. By go zainicjalizować należy dodać do kamery komponenty `Post-Proces Layer` i `Post-Proces Volume`. Następnie dodać dodać nową warstwę w `Layer > Add Layer...`, nazwij ją **Post Processing**. Na koniec ustawić warstwę na **Post Processing** w kamerze i w `Post-Proces Layer`. + + + +![details](img\10.JPG) + +Teraz wystarczy stworzyć profil w `Post-Proces Volume` i dodać do niego komponenty. + +> Ustaw opcję `Is Global` żeby efekty były widoczne także w widoku sceny + +#### Niektóre dostępne obiekty efekty + +**Color Grading** - pozwala dostosować kolorystykę sceny, zmodyfikować ekspozycję i kontrast. Do prawidłowego funkcjonowania należy zmienić przestrzeń kolorów na liniową można to zrobić w `Edit > Project Settings > Player > Rendering > Color Space`. + +**Ambient Occlusion** - dodaje okluzję otoczenia, czyli zacienienie dla światła rozproszonego wynikającego z geometrii. Zalecane jest wybrać `Scalable Ambient Obscurance` jako typ. + +**Vigniette**, **Chromatic Aberration**, **Bloom** - efekty symulujące działanie rzeczywistych kamer zwiększają *filmowość* obrazu. + +**Motion Blur** - rozmycie w ruchu, nada więcej realizmu ruchom trawy i gałęzi. + + +![details](img\11.JPG) + + +### Zadanie domowe + +Znajdź w internecie model domku lub innego budynku i zbuduj wokół niego scenę przedstawiającą go na odludziu w otoczeniu przyrody. Wymodeluj teren, dodaj drzewa, trawę i inne detale, wykorzystaj post-processing. Możesz wykorzystać assety znajdujące się w projekcie lub ściągnąć własne. Postaraj się, żeby teren wyglądał estetycznie. Efekt końcowy może być statycznym ujęciem, w takim przypadku umieść kamerę w odpowiednim miejscu. + + + +## Teskturowanie w oparciu o ukształtowanie terenu + +Teksturowanie można częściowo zautomatyzować uzależniając występowanie warstwę od własności terenu, takich jak wysokość czy kąt nachylenia. Przypomnijmy, że to jaka i w jakim stopniu dana warstwa zostanie narysowana jest dyktowane przez splatmapę. Po stronie skryptu jest to tablica trójwymiarowa, której 2 pierwsze współrzędne to szerokość i wysokość w mapie a trzecia jest indeksem warstwy. + +Otwórz Scenę `Procedural Terrain`. Wewnątrz znajduje się teren z podpiętym skryptem `Procedural Texture Script`, otwórz go w edytorze. Skrypt zawiera funkcję `runProcedrualTexturing`, w która ma teksturować teren. W tej chwili przypisuje każdemu punktowi losową wartość i wygląda następująco. + +```C# + public void runProcedrualTexturing() { + var terrain = gameObject.GetComponent(); + var layers = terrain.terrainData.alphamapLayers; + var height = terrain.terrainData.alphamapWidth; + var width = terrain.terrainData.alphamapHeight; + + + var newSplatMap = new float[width, height, layers]; + + for (int i=0; i < width;i++) { + for (int j = 0; j < width; j++) { + float x = j / (float)height; + float y = i / (float)width; + + var splatWeights = new float[layers]; + + for (int k = 0; k < layers; k++) { + splatWeights[k] = Random.RandomRange(0.0f,1.0f); + } + float sum = splatWeights.Sum(); + + for (int k = 0; k < layers; k++) { + newSplatMap[i,j,k]=splatWeights[k]/sum; + } + } + } + terrain.terrainData.SetAlphamaps(0, 0, newSplatMap); + } + +``` +Przeanalizujmy instrukcje funkcji. Pierwsza pobiera komponent terenu. Następne trzy pobierają odpowiednio liczbę warstw, wysokość i szerokość splatmapy. Linia `var newSplatMap = new float[width, height, layers];` tworzy tablicę, która będzie służyć za nową splatmapę. Następnie w pętli przechodzi po wszystkich indeksach splatmapy. +```C# +float x = j / (float)height; +float y = i / (float)width; +``` +Powyższe instrukcje dają współrzędne unormowane, które będą przydatne póżniej. Tablica `splatWeights` będzie przechowywać wagi warstw. + +```C# +for (int k = 0; k < layers; k++) { + splatWeights[k] = Random.RandomRange(0.0f,1.0f); +} +``` +Ustawia indeksy na losowe wartości + + +```C# +float sum = splatWeights.Sum(); +for (int k = 0; k < layers; k++) { + newSplatMap[i,j,k]=splatWeights[k]/sum; +} +``` +Umieszcza wagi w splatmapie i normalizuje je. Suma wag powinna być równa zero, dlatego dzielimy przez sumę tablicy. + +```C# +terrain.terrainData.SetAlphamaps(0, 0, newSplatMap); +``` +Ustawia nową splatmapę + +### Zadanie + +Zmodyfikuj funkcję tak, żeby teren, który znajduje się wyżej niż `snowHeight` był śniegiem a poniżej trawą. Wykorzystaj metodę `terrain.terrainData.GetInterpolatedHeight` by uzyskać poziom we współrzędnych `x` i `y`. + +Wykorzystanie zwykłego warunku logicznego da ostre przejście, które wygląda nierealistycznie. Napisz funkcję `transition(float start, float end,float value)` która: + +* zwróci 0, jeżeli $\text{value}<\text{start}$; +* zwróci 1, jeżeli $\text{value}\ge \text{end}$; +* będzie liniowo interpolować między 0 a 1 jeżeli $\text{end}>\text{value}\ge\text{start}$, + +I wykorzystaj ją by zrobić bardziej stopniowe przejście + +### Zadanie domowe + +Wykonaj coś podobnego dla tekstury kamieni, teraz zamiast wysokości terenu wykorzystaj kąt nachylenia, możesz go uzyskać za pomocą metody `terrain.terrainData.GetSteepness`. \ No newline at end of file diff --git a/Treść zadań/cw7.html b/Treść zadań/cw7.html new file mode 100644 index 0000000..04fa15f --- /dev/null +++ b/Treść zadań/cw7.html @@ -0,0 +1,183 @@ + + + + + + + cw7 + + + + + + +

Dynamiczne rozmieszczenie traw

+
+

Ustawienie gęstości detali

+

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 Detail Resolution per Patch na 16 i Detail Resolution na 256 lub 128. details

+
+

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ę.

+

API detali

+

Trawa jest rozmieszczana na terenia na podstawie map detali. Są to dwuwymiarowe tablice typu int, której wartości określają zagęszczenie danego detalu w kolejnych polach polu. Tablicę ustawia się za pomocą funkcji terrain.terrainData.SetDetailLayer(int xBase, int yBase, int layer, int[,] details) gdzie xBase i yBase są początkowymi współrzędnymi (domyślnie 0,0), layer jest indeksem detalu a details to tablica opisująca wystąpienia detalu. Ostatnia może mieć maksymalnie wymiar terrain.terrainData.detailWidth-xBase na terrain.terrainData.detailHeight-yBase.

+

Zadanie

+

Uzupełnij funkcję reset o instrukcje, które ustawią trawę w krzyżyk idący po przekątnej terenu.

+

Model rozwoju trawy

+

Do rozwoju trawy wykorzystamy model reakcji-dyfuzji wzorując się na pracy. Wykorzystamy układ równań różniczkowych

+

\[ \frac{\partial w}{\partial t} = D_w\Delta w-w^2r^2+\text{feed}(0.5+w)\] \[ \frac{\partial r}{\partial t} = D_r\Delta r+r(wr-\text{kill})\]

+

gdzie \(r\) i \(w\) oznaczają odpowiednio wegetację (ilość roślin) i zawartość wody. Natomiast parametry \(D_w\), \(D_r\), kill i feed są parametrami, które będą kontrolować interakcję.

+

Analitycznie \(r\) i \(w\) są funkcjami od współrzędnych terenu \(x\) i \(y\) oraz czasu \(t\). Nas nie interesuje rozwiązanie przybliżone, więc współrzędne \(x\) i \(y\) będą realizowane jako tablica dwuwymiarowa, natomiast krok czasowy będzie dyskretny. Przy takich założeniach możemy zapisać następująco:

+

\[ \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]))\] \[ \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}) )\]

+

gdzie \(\text{newWater}[i,j]\) oznacza poziom wody w następnym kroku we współrzędnych i,j, \(\text{oldWater}[i,j]\) oznacza poziom wody w poprzednim kroku we współrzędnych i,j, analogicznie \(\text{newVegetation}[i,j]\) i \(\text{oldVegetation}[i,j]\). Natomiast \(dt\) to krok czasowy, jakaś mała wartość około 0.05.

+

Pozostaje symbol \(\Delta\) oznacza on operator Laplace i oznacza on sumę drugich pochodnych po współrzędnych, czyli w naszym przypadku: \[\Delta = \frac{\partial^2}{\partial x^2}+\frac{\partial^2}{\partial y^2}.\]

+

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

+

\[ 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].\]

+

Można je zobrazować za pomocą ilusracji: aa

+

Implementacja

+

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.

+

Utwórz nowy compute shader, nazwij go na przykład GrassCalculation. Otwórz go w edytorze, będzie on wyglądać tak

+
// Each #kernel tells which function to compile; you can have many kernels
+#pragma kernel CSMain
+
+// Create a RenderTexture with enableRandomWrite flag and set it
+// with cs.SetTexture
+///// Miejsce na parametry przesyłane 
+RWTexture2D<float4> Result;
+
+
+//Kernel czyli funkcja wywoływana przy aktywacji shadera
+[numthreads(8,8,1)]
+void CSMain (uint3 id : SV_DispatchThreadID)
+{
+    // TODO: insert actual code here!
+
+    Result[id.xy] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
+}
+

Ten kod zawiera definicję jednej zmiennej i funkcji, która jest kernelem. Kernel przyjmuje jeden argument typu uint3, 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.

+

Zmienne definiowane globalnie, jak RWTexture2D<float4> Result, są parametrami, które przesyła się ze strony CPU. Typy, których nazwy zaczynają się od RW, mogą być modyfikowane przez shader. Dzięki temu mogą być wykorzystywane do zapisywania wyników.

+

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 Result i ciała funkcji CSMain. Tablicę z wegetacja prześlemy jako RWStructuredBuffer<float>, zakładamy, że może ona osiągnąć wartości od 0 do 1. Zdefiniuj globalną zmienną vegetation typu RWStructuredBuffer<float>.

+

Shadery nie obsługują 2-wymiarowych tablic, więc ją zastąpić tablicą 1-wymiarową o rozmiarze \(x*y\) i ręcznie indeksować. Do tego potrzebujemy znać wymiary tablicy, dodaj zmienne int sizeX i int sizeY, których do tego użyjemy. Tablicę będziemy zapisywać wierszami, jak na obrazku.

+
+index +
+

Wartość o współrzędnych x,y znajduje się pod indeksem \(x+y*\text{sizeY}\). Napisz funkcję int index(int x,int y), która będzie konwertować indeksy z jednego zapisu do drugiego.

+

Uzupełnij funkcję CSMain o instrukcje, które będzie obniżać wegetację we współrzędnych id.x i id.y wartość obetnij od dołu przez zero a przez jeden od góry z użyciem funkcji clamp. Całość powinna wyglądać tak:

+
// Each #kernel tells which function to compile; you can have many kernels
+#pragma kernel CSMain
+
+// Create a RenderTexture with enableRandomWrite flag and set it
+// with cs.SetTexture
+///// Miejsce na parametry przesyłane 
+RWStructuredBuffer<float> vegetation;
+int sizeX;
+int sizeY;
+
+int index(int x, int y){
+  return x+y*sizeX;
+}
+
+//Kernel czyli funkcja wywoływana przy aktywacji shadera
+[numthreads(8,8,1)]
+void CSMain (uint3 id : SV_DispatchThreadID)
+{
+    vegetation[index(id.x, id.y)] = clamp(vegetation[index(id.x, id.y)] - 0.01,0,1);
+}
+

Obsługa shadera

+

Mamy napisany shader, teraz pozostaje przesłać dane,uruchomić go i odczytać wyniki. Skrypt GrassController Zawiera szkielet kodu potrzebnego do obsługi shadera. Zanim zaczniemy podepnij GrassComputation w inspektorze pod zmienną computeShader.

+

By zainicjalizować zwykłe zmienne wykorzystuje się metody SetInt lub SetFloat w zależności od typu. Pierwszym jej argumentem jest id danej zmiennej, pozyskuje się ją za pomocą funkcji Shader.PropertyToID, która przyjmuje nazwę zmiennej jako argument. Drugim jest wartość tej zmiennej.

+

Uzupełnij funkcję initParameters o inicjalizację zmiennych sizeX i sizeY. przypisz im wartość atrybutów sizeX i sizeY (wartości tych zmiennych pokrywają się z rozmiarem mapy detali).

+

Bufor trzeba najpierw stworzyć po stronie C#, klasa je obsługująca nazywa się ComputeBuffer. konstruktor przyjmuje 2 argumenty: liczbę komórek i rozmiar jednego pola. Pierwsza z nich w naszym przypadku wynosi sizeX*sizeY a druga sizeof(float). Stworzony bufor zapisz w jako atrybut o nazwie vegetation, będzie on potrzebny później, żeby odzyskać dane. Bufor inicjalizuje się za pomocą metody SetBuffer(int kernelIndex,str Name, ComputeBuffer buffer) compute shadera. Pierwszy jej argument w naszym przypadku wynosi zero, drugi to nazwa bufora (czyli vegetation) a trzeci to bufor.

+

Uzupełnij funkcję initComputeShader o inicjalizację bufora wegetacji.

+

W metodzie initBufferValues stwórz tablicę o wymiarze sizeX*sizeY i zapełnij ją losowymi wartościami. Następnie zapisz ją do bufora vegetation za pomocą metody SetData. usuń Wszystko z metody reset.

+

Teraz należy wywołać shader i odczytać dane. Shader wywołuje się za pomocą metody Dispatch(int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ), 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 id w CSMain. W naszym przypadku te wartości mają odpowiadać wymiarom tablicy, czyli odpowiednio sizeX, sizeY i 1.

+

Uzupełnij funkcję doSimulationSteps(int steps) o wywołanie shader steps razy w pętli.

+

Odczytanie danych wykonuje się za pomocą metody GetData bufora. Jako argument należy podać tablicę, w której dane zostaną umieszczone. W metodzie recoverData utwórz tablicę typu float o nazwie result o wymiarze sizeX*sizeY i pobierz do niej wartości wegetacji.

+

Pozostaje zinterpretować wyniki i wykorzystać je do rysowania trawy. Wciąż wewnątrz recoverData utwórz tablicę typu int o wymiarach sizeX na sizeY. W niej umieść podłogę z wyniku z result pomnożonego przez 10.

+

Dyfuzja

+

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ę float get(StructuredBuffer<float> buffer, int x, int y), która zwróci wartość tablicy jeżeli index(x,y) jest pomiędzy 0 a sizeX*sizeY i zero w przeciwnym wypadku. Następnie wykorzystaj ją, żeby napisać funkcję float laplace(StructuredBuffer<float> buffer, int x, int y), która obliczy laplacian dla punkty x,y.

+

Dyfuzje można opisać wzorem

+

\[\frac{\partial v}{\partial t}=\Delta v.\]

+

W wersji dyskretnej będzie to:

+

\[\text{newV[i,j]} = \text{oldV}[i,j] + \Delta(\text{oldV},i,j).\]

+

Należy dodać jeszcze jeden RWStructuredBuffer<float>, w którym będziemy przechowywać poprzedni krok, nazwij go oldVegetation. Obliczenia będziemy wykonywać na danych z niego i zapisywać w vegetation. to pozwoli uniknąć problemów z synchronizacją bufora. Zaimplementuj powyższy wzór.

+

Zainicjalizuj go po stronie C# analogicznie co bufor vegetation. Zapisz go jako atrybut o nazwie . W initBufferValues przypisz mu te same wartości co buforowi vegetation.

+

Zaimplementuj funkcję swapBuffers, w której zamień ze sobą bufory vegetation i oldVegetation i wywołaj dla nich ponownie computeShader.SetBuffer. W doSimulationSteps dodaj wywołanie swapBuffers przed wywołaniem w pętli shadera.

+

Interakcja z terenem

+

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 feed i kill od terenu.

+

Zadanie

+

W tym celu stwórz dwa bufory typu float o nazwie feedModifier i killModifier i rozmiarze sizeX*sizeY. Następnie zapełnij je wartościami. Uzależnić killModifier od rodzaju tekstury.

+

Po stronie shadera odczytaj wartość wartości feedModifier i killModifier dla danej współrzędnej i przemnóż je przez parametry feed i kill w równaniach.

+

Zadanie domowe

+

Uzależnij feedModifier od wysokości terenu. możesz do tego wykorzystać na przykład funkcję transition napisaną wcześniej.

+ + diff --git a/Treść zadań/cw7.md b/Treść zadań/cw7.md new file mode 100644 index 0000000..4d77687 --- /dev/null +++ b/Treść zadań/cw7.md @@ -0,0 +1,170 @@ +# Dynamiczne rozmieszczenie traw + + + + +>#### Ustawienie gęstości detali +>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 `Detail Resolution per Patch` na 16 i `Detail Resolution` na 256 lub 128. +>![details](img\2.JPG) + +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ę. + +## API detali + +Trawa jest rozmieszczana na terenia na podstawie map detali. Są to dwuwymiarowe tablice typu `int`, której wartości określają zagęszczenie danego detalu w kolejnych polach polu. +Tablicę ustawia się za pomocą funkcji + ``` + terrain.terrainData.SetDetailLayer(int xBase, int yBase, int layer, int[,] details) + ``` +gdzie `xBase` i `yBase` są początkowymi współrzędnymi (domyślnie 0,0), `layer` jest indeksem detalu a `details` to tablica opisująca wystąpienia detalu. Ostatnia może mieć maksymalnie wymiar `terrain.terrainData.detailWidth-xBase` na `terrain.terrainData.detailHeight-yBase`. + +### Zadanie + +Uzupełnij funkcję `reset` o instrukcje, które ustawią trawę w krzyżyk idący po przekątnej terenu. + +## Model rozwoju trawy + +Do rozwoju trawy wykorzystamy model reakcji-dyfuzji wzorując się na [pracy](https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2007RG000256). Wykorzystamy układ równań różniczkowych + +$$ \frac{\partial w}{\partial t} = D_w\Delta w-w^2r^2+\text{feed}(0.5+w)$$ +$$ \frac{\partial r}{\partial t} = D_r\Delta r+r(wr-\text{kill})$$ + +gdzie $r$ i $w$ oznaczają odpowiednio wegetację (ilość roślin) i zawartość wody. Natomiast parametry $D_w$, $D_r$, kill i feed są parametrami, które będą kontrolować interakcję. + +Analitycznie $r$ i $w$ są funkcjami od współrzędnych terenu $x$ i $y$ oraz czasu $t$. Nas nie interesuje rozwiązanie przybliżone, więc współrzędne $x$ i $y$ będą realizowane jako tablica dwuwymiarowa, natomiast krok czasowy będzie dyskretny. Przy takich założeniach możemy zapisać następująco: + +$$ \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]))$$ +$$ \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}) )$$ + +gdzie +$\text{newWater}[i,j]$ oznacza poziom wody w następnym kroku we współrzędnych i,j, $\text{oldWater}[i,j]$ oznacza poziom wody w poprzednim kroku we współrzędnych i,j, analogicznie $\text{newVegetation}[i,j]$ i $\text{oldVegetation}[i,j]$. Natomiast $dt$ to krok czasowy, jakaś mała wartość około 0.05. + +Pozostaje symbol $\Delta$ oznacza on operator Laplace i oznacza on sumę drugich pochodnych po współrzędnych, czyli w naszym przypadku: +$$\Delta = \frac{\partial^2}{\partial x^2}+\frac{\partial^2}{\partial y^2}.$$ + +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 + +$$ 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].$$ + +Można je zobrazować za pomocą ilusracji: +![aa](img/kernel.jpg) + +## Implementacja + +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. + +Utwórz nowy compute shader, nazwij go na przykład `GrassCalculation`. Otwórz go w edytorze, będzie on wyglądać tak + +```C +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel CSMain + +// Create a RenderTexture with enableRandomWrite flag and set it +// with cs.SetTexture +///// Miejsce na parametry przesyłane +RWTexture2D Result; + + +//Kernel czyli funkcja wywoływana przy aktywacji shadera +[numthreads(8,8,1)] +void CSMain (uint3 id : SV_DispatchThreadID) +{ + // TODO: insert actual code here! + + Result[id.xy] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0); +} +``` + +Ten kod zawiera definicję jednej zmiennej i funkcji, która jest kernelem. Kernel przyjmuje jeden argument typu `uint3`, 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. + +Zmienne definiowane globalnie, jak `RWTexture2D Result`, są parametrami, które przesyła się ze strony CPU. Typy, których nazwy zaczynają się od `RW`, mogą być modyfikowane przez shader. Dzięki temu mogą być wykorzystywane do zapisywania wyników. + +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 `Result` i ciała funkcji `CSMain`. Tablicę z wegetacja prześlemy jako `RWStructuredBuffer`, zakładamy, że może ona osiągnąć wartości od 0 do 1. Zdefiniuj globalną zmienną `vegetation` typu `RWStructuredBuffer`. + +Shadery nie obsługują 2-wymiarowych tablic, więc ją zastąpić tablicą 1-wymiarową o rozmiarze $x*y$ i ręcznie indeksować. Do tego potrzebujemy znać wymiary tablicy, dodaj zmienne `int sizeX` i `int sizeY`, których do tego użyjemy. Tablicę będziemy zapisywać wierszami, jak na obrazku. + +![index](img/index.jpg) + +Wartość o współrzędnych x,y znajduje się pod indeksem $x+y*\text{sizeY}$. Napisz funkcję `int index(int x,int y)`, która będzie konwertować indeksy z jednego zapisu do drugiego. + +Uzupełnij funkcję `CSMain` o instrukcje, które będzie obniżać wegetację we współrzędnych `id.x` i `id.y` wartość obetnij od dołu przez zero a przez jeden od góry z użyciem funkcji `clamp`. Całość powinna wyglądać tak: + +```C +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel CSMain + +// Create a RenderTexture with enableRandomWrite flag and set it +// with cs.SetTexture +///// Miejsce na parametry przesyłane +RWStructuredBuffer vegetation; +int sizeX; +int sizeY; + +int index(int x, int y){ + return x+y*sizeX; +} + +//Kernel czyli funkcja wywoływana przy aktywacji shadera +[numthreads(8,8,1)] +void CSMain (uint3 id : SV_DispatchThreadID) +{ + vegetation[index(id.x, id.y)] = clamp(vegetation[index(id.x, id.y)] - 0.01,0,1); +} +``` + +### Obsługa shadera + +Mamy napisany shader, teraz pozostaje przesłać dane,uruchomić go i odczytać wyniki. Skrypt `GrassController` Zawiera szkielet kodu potrzebnego do obsługi shadera. Zanim zaczniemy podepnij `GrassComputation` w inspektorze pod zmienną `computeShader`. + +By zainicjalizować zwykłe zmienne wykorzystuje się metody `SetInt` lub `SetFloat` w zależności od typu. Pierwszym jej argumentem jest id danej zmiennej, pozyskuje się ją za pomocą funkcji `Shader.PropertyToID`, która przyjmuje nazwę zmiennej jako argument. Drugim jest wartość tej zmiennej. + +Uzupełnij funkcję `initParameters` o inicjalizację zmiennych `sizeX` i `sizeY`. przypisz im wartość atrybutów `sizeX` i `sizeY` (wartości tych zmiennych pokrywają się z rozmiarem mapy detali). + +Bufor trzeba najpierw stworzyć po stronie C#, klasa je obsługująca nazywa się `ComputeBuffer`. konstruktor przyjmuje 2 argumenty: liczbę komórek i rozmiar jednego pola. Pierwsza z nich w naszym przypadku wynosi `sizeX*sizeY` a druga `sizeof(float)`. Stworzony bufor zapisz w jako atrybut o nazwie `vegetation`, będzie on potrzebny później, żeby odzyskać dane. Bufor inicjalizuje się za pomocą metody `SetBuffer(int kernelIndex,str Name, ComputeBuffer buffer)` compute shadera. Pierwszy jej argument w naszym przypadku wynosi zero, drugi to nazwa bufora (czyli `vegetation`) a trzeci to bufor. + +Uzupełnij funkcję `initComputeShader` o inicjalizację bufora wegetacji. + +W metodzie `initBufferValues` stwórz tablicę o wymiarze `sizeX*sizeY` i zapełnij ją losowymi wartościami. Następnie zapisz ją do bufora `vegetation` za pomocą metody `SetData`. usuń Wszystko z metody `reset`. + +Teraz należy wywołać shader i odczytać dane. Shader wywołuje się za pomocą metody `Dispatch(int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ)`, 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 `id` w `CSMain`. W naszym przypadku te wartości mają odpowiadać wymiarom tablicy, czyli odpowiednio `sizeX`, `sizeY` i `1`. + +Uzupełnij funkcję `doSimulationSteps(int steps)` o wywołanie shader `steps` razy w pętli. + +Odczytanie danych wykonuje się za pomocą metody `GetData` bufora. Jako argument należy podać tablicę, w której dane zostaną umieszczone. W metodzie `recoverData` utwórz tablicę typu `float` o nazwie `result` o wymiarze `sizeX*sizeY` i pobierz do niej wartości wegetacji. + +Pozostaje zinterpretować wyniki i wykorzystać je do rysowania trawy. Wciąż wewnątrz `recoverData` utwórz tablicę typu `int` o wymiarach `sizeX` na `sizeY`. W niej umieść podłogę z wyniku z `result` pomnożonego przez 10. + +### Dyfuzja + +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ę `float get(StructuredBuffer buffer, int x, int y)`, która zwróci wartość tablicy jeżeli `index(x,y)` jest pomiędzy 0 a `sizeX*sizeY` i zero w przeciwnym wypadku. Następnie wykorzystaj ją, żeby napisać funkcję `float laplace(StructuredBuffer buffer, int x, int y)`, która obliczy laplacian dla punkty x,y. + +Dyfuzje można opisać wzorem + +$$\frac{\partial v}{\partial t}=\Delta v.$$ + +W wersji dyskretnej będzie to: + +$$\text{newV[i,j]} = \text{oldV}[i,j] + \Delta(\text{oldV},i,j).$$ + +Należy dodać jeszcze jeden `RWStructuredBuffer`, w którym będziemy przechowywać poprzedni krok, nazwij go `oldVegetation`. Obliczenia będziemy wykonywać na danych z niego i zapisywać w `vegetation`. to pozwoli uniknąć problemów z synchronizacją bufora. Zaimplementuj powyższy wzór. + +Zainicjalizuj go po stronie C# analogicznie co bufor `vegetation`. Zapisz go jako atrybut o nazwie . W `initBufferValues` przypisz mu te same wartości co buforowi `vegetation`. + +Zaimplementuj funkcję `swapBuffers`, w której zamień ze sobą bufory `vegetation` i `oldVegetation` i wywołaj dla nich ponownie `computeShader.SetBuffer`. W `doSimulationSteps` dodaj wywołanie `swapBuffers` przed wywołaniem w pętli shadera. + + + +### Interakcja z terenem + +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 `feed` i `kill` od terenu. + +### Zadanie + +W tym celu stwórz dwa bufory typu `float` o nazwie `feedModifier` i `killModifier` i rozmiarze `sizeX*sizeY`. Następnie zapełnij je wartościami. Uzależnić `killModifier` od rodzaju tekstury. + +Po stronie shadera odczytaj wartość wartości `feedModifier` i `killModifier` dla danej współrzędnej i przemnóż je przez parametry `feed` i `kill` w równaniach. + + +### Zadanie domowe + +Uzależnij `feedModifier` od wysokości terenu. możesz do tego wykorzystać na przykład funkcję `transition` napisaną wcześniej. diff --git a/Treść zadań/img/1.JPG b/Treść zadań/img/1.JPG new file mode 100644 index 0000000..cc8926b Binary files /dev/null and b/Treść zadań/img/1.JPG differ diff --git a/Treść zadań/img/10.JPG b/Treść zadań/img/10.JPG new file mode 100644 index 0000000..16859c8 Binary files /dev/null and b/Treść zadań/img/10.JPG differ diff --git a/Treść zadań/img/11.JPG b/Treść zadań/img/11.JPG new file mode 100644 index 0000000..945f424 Binary files /dev/null and b/Treść zadań/img/11.JPG differ diff --git a/Treść zadań/img/2.JPG b/Treść zadań/img/2.JPG new file mode 100644 index 0000000..2d373c3 Binary files /dev/null and b/Treść zadań/img/2.JPG differ diff --git a/Treść zadań/img/3.JPG b/Treść zadań/img/3.JPG new file mode 100644 index 0000000..a8d7006 Binary files /dev/null and b/Treść zadań/img/3.JPG differ diff --git a/Treść zadań/img/4.JPG b/Treść zadań/img/4.JPG new file mode 100644 index 0000000..a1d4ac8 Binary files /dev/null and b/Treść zadań/img/4.JPG differ diff --git a/Treść zadań/img/5.JPG b/Treść zadań/img/5.JPG new file mode 100644 index 0000000..966b4a8 Binary files /dev/null and b/Treść zadań/img/5.JPG differ diff --git a/Treść zadań/img/6.JPG b/Treść zadań/img/6.JPG new file mode 100644 index 0000000..24deaa0 Binary files /dev/null and b/Treść zadań/img/6.JPG differ diff --git a/Treść zadań/img/7.JPG b/Treść zadań/img/7.JPG new file mode 100644 index 0000000..4945bce Binary files /dev/null and b/Treść zadań/img/7.JPG differ diff --git a/Treść zadań/img/8.JPG b/Treść zadań/img/8.JPG new file mode 100644 index 0000000..6a171e7 Binary files /dev/null and b/Treść zadań/img/8.JPG differ diff --git a/Treść zadań/img/9.jpg b/Treść zadań/img/9.jpg new file mode 100644 index 0000000..894f6be Binary files /dev/null and b/Treść zadań/img/9.jpg differ diff --git a/Treść zadań/img/index.jpg b/Treść zadań/img/index.jpg new file mode 100644 index 0000000..a4de5db Binary files /dev/null and b/Treść zadań/img/index.jpg differ diff --git a/Treść zadań/img/kernel.jpg b/Treść zadań/img/kernel.jpg new file mode 100644 index 0000000..a65ab14 Binary files /dev/null and b/Treść zadań/img/kernel.jpg differ diff --git a/Treść zadań/landscapes/1.jpg b/Treść zadań/landscapes/1.jpg new file mode 100644 index 0000000..ba5c6fb Binary files /dev/null and b/Treść zadań/landscapes/1.jpg differ diff --git a/Treść zadań/landscapes/10.jpg b/Treść zadań/landscapes/10.jpg new file mode 100644 index 0000000..e04bfe1 Binary files /dev/null and b/Treść zadań/landscapes/10.jpg differ diff --git a/Treść zadań/landscapes/11.jpg b/Treść zadań/landscapes/11.jpg new file mode 100644 index 0000000..3cd2965 Binary files /dev/null and b/Treść zadań/landscapes/11.jpg differ diff --git a/Treść zadań/landscapes/12.jpg b/Treść zadań/landscapes/12.jpg new file mode 100644 index 0000000..df86237 Binary files /dev/null and b/Treść zadań/landscapes/12.jpg differ diff --git a/Treść zadań/landscapes/13.jpg b/Treść zadań/landscapes/13.jpg new file mode 100644 index 0000000..590af2f Binary files /dev/null and b/Treść zadań/landscapes/13.jpg differ diff --git a/Treść zadań/landscapes/2.jpg b/Treść zadań/landscapes/2.jpg new file mode 100644 index 0000000..8462daf Binary files /dev/null and b/Treść zadań/landscapes/2.jpg differ diff --git a/Treść zadań/landscapes/3.jpg b/Treść zadań/landscapes/3.jpg new file mode 100644 index 0000000..bccd513 Binary files /dev/null and b/Treść zadań/landscapes/3.jpg differ diff --git a/Treść zadań/landscapes/4.jpg b/Treść zadań/landscapes/4.jpg new file mode 100644 index 0000000..fb1aa75 Binary files /dev/null and b/Treść zadań/landscapes/4.jpg differ diff --git a/Treść zadań/landscapes/5.jpg b/Treść zadań/landscapes/5.jpg new file mode 100644 index 0000000..e860139 Binary files /dev/null and b/Treść zadań/landscapes/5.jpg differ diff --git a/Treść zadań/landscapes/6.jpg b/Treść zadań/landscapes/6.jpg new file mode 100644 index 0000000..d061985 Binary files /dev/null and b/Treść zadań/landscapes/6.jpg differ diff --git a/Treść zadań/landscapes/7.jpg b/Treść zadań/landscapes/7.jpg new file mode 100644 index 0000000..1ca7858 Binary files /dev/null and b/Treść zadań/landscapes/7.jpg differ diff --git a/Treść zadań/landscapes/8.jpg b/Treść zadań/landscapes/8.jpg new file mode 100644 index 0000000..6d4df00 Binary files /dev/null and b/Treść zadań/landscapes/8.jpg differ diff --git a/Treść zadań/landscapes/9.jpg b/Treść zadań/landscapes/9.jpg new file mode 100644 index 0000000..caf471b Binary files /dev/null and b/Treść zadań/landscapes/9.jpg differ diff --git a/Treść zadań/landscapes/links.txt b/Treść zadań/landscapes/links.txt new file mode 100644 index 0000000..60c3b16 --- /dev/null +++ b/Treść zadań/landscapes/links.txt @@ -0,0 +1,11 @@ +https://500px.com/photo/95948529/OMG-Never-ending-dunes-by-Nagaraju-Hanchanahal/ +https://500px.com/photo/302447491/Badlands-from-Zabriskie-Point-by-Christopher-Petroff/ +https://500px.com/photo/96326699/Eureka-Dunes-by-James-Marvin-Phelps/ +https://500px.com/photo/88552513/Iceland-landscape-by-Luis-Courtot/ +https://500px.com/photo/97138987/Calm-Anchor-in-Time-by-Martin-Walser/ +https://500px.com/photo/89425869/Yellow-River-by-Tom-Rogula/ +https://500px.com/photo/1032143380/Desert-wandering--by-Charly-Savely/ +https://500px.com/photo/58341764/Landmannalaugar-by-Mathias-Beller +https://500px.com/photo/221480033/Burning-summits-by-Haag +https://500px.com/photo/90574585/The-mornings-of-Miyajima-by-E-Jacqui-Chan/ +https://500px.com/photo/1006854216/Hokkaido-Flower-Field-by-David-Lew diff --git a/Treść zadań/render.py b/Treść zadań/render.py new file mode 100644 index 0000000..db3fcaf --- /dev/null +++ b/Treść zadań/render.py @@ -0,0 +1,7 @@ + +import os +rootdir = './' +for filename in os.listdir(rootdir): + if filename.endswith(".md"): + name = filename[:-3] + os.system(f'pandoc -s -o {name}.html {name}.md --mathjax --css style.css') \ No newline at end of file diff --git a/Treść zadań/style.css b/Treść zadań/style.css new file mode 100644 index 0000000..cf42427 --- /dev/null +++ b/Treść zadań/style.css @@ -0,0 +1,107 @@ +html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + +body{ +color:#444; +font-family:Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', serif; +font-size:12px; +line-height:1.5em; +padding:1em; +margin:auto; +max-width:42em; +background:#fefefe; +} + +a{ color: #0645ad; text-decoration:none;} +a:visited{ color: #0b0080; } +a:hover{ color: #06e; } +a:active{ color:#faa700; } +a:focus{ outline: thin dotted; } +a:hover, a:active{ outline: 0; } + +::-moz-selection{background:rgba(255,255,0,0.3);color:#000} +::selection{background:rgba(255,255,0,0.3);color:#000} + +a::-moz-selection{background:rgba(255,255,0,0.3);color:#0645ad} +a::selection{background:rgba(255,255,0,0.3);color:#0645ad} + +p{ +margin:1em 0; +} + +img{ +max-width:100%; +} + +h1,h2,h3,h4,h5,h6{ +font-weight:normal; +color:#111; +line-height:1em; +} +h4,h5,h6{ font-weight: bold; } +h1{ font-size:2.5em; } +h2{ font-size:2em; } +h3{ font-size:1.5em; } +h4{ font-size:1.2em; } +h5{ font-size:1em; } +h6{ font-size:0.9em; } + +blockquote{ +color:#666666; +margin:0; +padding-left: 3em; +border-left: 0.5em #EEE solid; +} +hr { display: block; height: 2px; border: 0; border-top: 1px solid #aaa;border-bottom: 1px solid #eee; margin: 1em 0; padding: 0; } +pre, code, kbd, samp { color: #000; font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 0.98em; } +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } + +b, strong { font-weight: bold; } + +dfn { font-style: italic; } + +ins { background: #ff9; color: #000; text-decoration: none; } + +mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } + +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + +ul, ol { margin: 1em 0; padding: 0 0 0 2em; } +li p:last-child { margin:0 } +dd { margin: 0 0 0 2em; } + +img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } + +table { +border-collapse: collapse; +border-spacing: 0; +width: 100%; +} +th { border-bottom: 1px solid black; } +td { vertical-align: top; } + +@media only screen and (min-width: 480px) { +body{font-size:14px;} +} + +@media only screen and (min-width: 768px) { +body{font-size:16px;} +} + +@media print { + * { background: transparent !important; color: black !important; filter:none !important; -ms-filter: none !important; } + body{font-size:12pt; max-width:100%;} + a, a:visited { text-decoration: underline; } + hr { height: 1px; border:0; border-bottom:1px solid black; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } + pre, blockquote { border: 1px solid #999; padding-right: 1em; page-break-inside: avoid; } + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page :left { margin: 15mm 20mm 15mm 10mm; } + @page :right { margin: 15mm 10mm 15mm 20mm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3 { page-break-after: avoid; } +} \ No newline at end of file