GRK/cw 5/zadania 5.md
secret_dude a7bd7ecb75 master
2022-01-12 16:07:16 +01:00

8.9 KiB

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 lub link2

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

  1. 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 zmiennychviewDirTS 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.)

  2. 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`.