# II Teksturowanie ### II.0 Obejrzyj plik **Texture.h** aby zapoznać się z nowym interfejsem do obsługi tekstur. ### II.1 Przygotuj shadery i załaduj teksturę: a) W kolejnych zadaniach będziemy potrzebować trzecią parę shaderów: **shader_4_tex.vert** i **shader_4_tex.frag** są one gotowymi shaderami z poprzednich zadań. - Wczytaj shadery i przechowaj adres programu pod zmienną globalną `programTex` (tzn. powtórz dla nich punkt 6.a, ale zamiast `programSun` nazwij zmienną `programTex`). - Prześlij globalne *uniformy* do `programTex`. b) ładowanie tekstury: - Pod `namespace texture` znajdują się zmienne globalne typu `Gluint`, które będą przechowywać adresy tekstur. W funkcji `init()` załaduj tekstury z folderu **textures** przy pomocy `Core::LoadTexture`. Ta funkcja zwraca adres do wczytanej tekstury, przypisz je do zmiennych z `namespace texture`. - (odwołanie się do `namespace` wykonuje się za pomocą ::, czyli by odwołać się do `grid` w `namespace` `texture` należy napisać `texture::grid`) ### II.2. Stwórz funkcje odpowiadająca za rysowanie teksturowanych obiektów: - Skopiuj funkcję `drawObject`. Nazwij kopię `drawObjectTexture()` i zmodyfikują ją tak, aby przyjmowała jako parametr identyfikator tekstury, a nie wektor koloru. (Usuń w niej linijkę odpowiadającą za przesyłanie koloru, żeby uniknąć błędu komunikacji, później dodamy na jej miejsce ładowanie tekstury). ### II.3. Zmodyfikuj shader tak, aby nakładał teksturę na obiekt: a) Narysuj jedną z planet za pomocą `drawObjectTexture()`, użyj `programTex` jako programu. b) Prześlij współrzędne mapowania tekstur z *vertex shadera* do *fragment shadera* - Współrzędne tekstur to kolejny (po pozycjach i wektorach normalnych) atrybut wierzchołków - są dostępne w *vertex shaderze* pod nazwą `vertexTexCoord` - Prześlij je znanym już sposobem do *fragment shadera* (zmienna `out` w *vertex shaderze* i odpowiadająca jej zmienna `in` we *fragment shaderze*) c) Prześlij teksturę do fragment shadera: - Stwórz zmienną typu `uniform sampler2D` (nazwij ją na przykład `colorTexture`) we fragment shaderze - analogicznie do innych zmiennych typu uniform, służy ona do przesyłania informacji bezpośrednio z kodu C++ do shadera - Po stronie kodu C++ użyj funkcji `Core::SetActiveTexture` wewnątrz `drawObjectTexture()` aby ustawić zmienną `sampler2D` na wczytaną wcześniej teksturę d) Użyj wartości uzyskanej z tekstury zamiast koloru (`objectColor`) do pokolorowania obiektu: - Wykonaj próbkowanie tekstury we współrzędnych otrzymanych przez fragment shader: `vec4 textureColor = texture2D(~nazwaZmiennejSampler2D, ~nazwaZmiennejWspolrzedneTekstury)`" (`vec4` zawiera kolor RGBA) - Użyj pierwszych trzech współrzędnych (RGB) uzyskanego wektora jako nowego koloru bazowego piksela ### II.4. Pobaw się mechanizmem teksturowania: - Przemnóż jedną lub obie ze współrzędnych mapowania przez 5 i sprawdź co się stanie - Wypróbuj pozostałe tekstury: **grid_color.png**, **earth.png** i **earth2.png**. - Tekstury Ziemi wyświetlają się "do góry nogami". Napraw to. - Jeśli chcesz mieć kilka planet o różnych teksturach możesz skorzystać z [link](https://www.solarsystemscope.com/textures/) lub [link2](https://stevealbers.net/albers/sos/sos.html) ### II.5*. Teksturowanie proceduralne a) Stwórz czwartą parę plików z shaderami (np. **shader_proc_tex.vert** i **shader_proc_tex.frag**). Następnie zainicjalizuj program jak w I.6.a) i II.1.a) (nazwij go np `programProcTex`). Do wywołania rysowania wykorzystuj funkcję `drawObject` (Żeby nie tracić oteksturowanych planet, możesz rysować za ich pomocą wyłącznie statek). b) Prostym sposobem proceduralnego teksturowania, jest uzależnienie koloru od pozycji piksela w przestrzeni lokalnej (użycie przestrzeni świata spowodowałoby, że wzór na obiekcie zmieniałby się przy poruszaniu go). - Prześlij z vertex shadera do fragment shadera pozycję wierzchołka w przestrzeni lokalnej (czyli tej, w której wyrażone są atrybuty wierzchołka - nie trzeba więc wykonywać żadnej transformacji macierzowej) - We fragment shaderze oblicz sinus współrzędnej y pozycji piksela - Jeżeli sinus jest większy od zera, to ustaw bazowy kolor obiektu na wybrany kolor, a jeśli jest mniejszy od zera, to na inny kolor - Możesz przesłać te kolory przy użyciu zmiennych uniform z kodu C++ - pozwoli to rysować różne obiekty z różnymi parami kolorów - Poeksperymentuj z innymi metodami teksturowania proceduralnego podanymi na wykładzie # III Normal Mapping W tej części będziemy dalej modyfikować shadery **shader_4_tex** poprzez implementację normal mappingu Obliczenia dla map normalnych należy wykonywać w przestrzeni stycznych. Przestrzeń styczna jest wyliczana dla każdego punktu w obiekcie. Jej celem jest takie przekształcenie przestrzeni, żeby wektor normalny był wektorem jednostkowym (0,1,0). Do wyliczenia przestrzeni stycznej potrzebujemy dla każdego wierzchołka oprócz wektora normalnego wektor styczny i bistyczny (*tangent* i *bitangent*). Są one wyliczane przez bibliotekę `Assimp`. ### III.0 Wykonaj kopię shaderów shader_4_tex.vert shader_4_tex.frag ### III.1 Przenieś obliczenia światła do przestrzeni stycznej 1) Oblicz macierz **TBN**. Macierz **TBN** to macierz 3x3 wyznaczana przez wektory *tangent*, *bitangent* i *normal*, służy do przenoszenia wektorów z przestrzeni świata do przestrzeni stycznej. 1. W **vertex shaderze** przekrztałć wektory `vertexNormal`, `vertexTangent` i `vertexBitangent` do przestrzeni świata (przemnóż macierz modelu przez te wektory, tak jak to robiliśmy wcześniej z wektorem normalnym, z uwzględnieniem zmiennej w=0) i zapisz wyniki odpowiednio w zmiennych `normal`, `tangent` i `bitangent`. 2. Stwórz macierz 3x3 TBN jako transpose(mat3(tangent, bitangent, normal)). Macierz transponujemy, aby szybko uzyskać jej odwrotność (możemy tak zrobić przy założeniu, ze jest ortogonalna). 2. Przenieś wektor światła i wektor widoku do przestrzeni stycznych 1. Musimy przekształcić wektor światła (L) i wektor widoku (V) do przestrzeni stycznych. Co ważne, zrobimy to w vertex shaderze. W tym celu przenieś potrzebne dane dotyczące światła i kamery (uniformy `lightPos` i `cameraPos`) z **fragment shadera** do **vertex shadera.** 2. Oblicz wektor `viewDir` jako znormalizowana różnice `cameraPos` i `fragPos` (tu jeszcze działamy w przestrzeni świata). Analogicznie oblicz `lightDir` jako różnicę `lightPos` i `fragPos` 3. Przekształć wektory `viewDir` i `lightDir` do przestrzeni stycznej mnożąc je przez macierz **TBN**. Wynik zapisz w zmiennych`viewDirTS` i `lightDirTS` odpowiednio. 4. Przekaż `viewDirTS` i `lightDirTS` do fragment shadera. (zadeklaruj je jako zmienne `out`) > (Sufiks TS oznacza tangent space. Wazne jest, aby oznaczac (np. dopisujac cos do nazwy zmiennej) w jakiej przestrzeni znajduja sie uzywane wektory, tak aby poprawnie wykonywac obliczenia. Trzeba zawsze zwracac uwage na to, w jakiej przestrzeni dzialamy.) 3. Przekształć **fragment shader**, by obsługiwał **tangent space** 1. Nie potrzebujemy już we **fragment shaderze** informacji na temat pozycji fragmentu i wektora normalnego geometrii, skasuj wiec zmienne przekazujące te wektory pomiędzy shaderami. 2. wektora `lightDir` powinniśmy użyć wektora `lightDirTS` (należy go dodatkowo znormalizować), a jako wektor widoku V powinniśmy użyć wektora `viewDirTS` (również należy go znormalizować). Jako wektora N użyj na razie wektora vec3(0,0,1). Efekt finalny powinien wyglądać tak samo jak w przed jakąkolwiek zmianą. Następnym krokiem będzie wykorzystanie map normalnych ### III.2 Wykorzystaj normalmapy 1. Chcemy wczytywać normalne z tekstury, w tym celu dodaj we **fragment shaderze** dodatkowy sampler do czytania map normalnych, nazwij go `normalSampler`. Pobierz z niego wektor normalny analogicznie, jak czytamy kolor zwykłej tekstury z samplera `textureSampler` i zapisz go w zmiennej `N`. 2. Ponieważ w teksturze wartości są w przedziale [0,1], musimy jeszcze przekształcić je do przedziału [-1,1]. W tym celu przemnóż wektor N przez 2 i odejmij 1. Na koniec warto jeszcze znormalizować wektor normalny, aby uniknąć błędów związanych z precyzja lub kompresja tekstury. 3. Wczytaj pliki zawierające mapy normalnych w kodzie C++ W tym celu załaduj przy użyciu funkcji `Core::LoadTexture` mapy normalnych dla wszystkich modeli. Maja one taką sama nazwę jak zwykle tekstury, tyle ze z suffiksem "_normals". 4. Zmodyfikuj na koniec funkcje `drawObjectTexture`. Dodaj do niej nowy argument `GLuint normalmapId`, który będzie służył do przekazywania id tekstury zawierającej mapę normalnych. Przy użyciu funkcji `Core::SetActiveTexture` załaduj `normalmapId` jako `normalSampler` i ustaw jednostkę teksturowania nr 1. Argument odpowiadający za normalne w w miejscach wywołania funkcji `drawObjectTexture`.