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

b) ładowanie tekstury:

II.2. Stwórz funkcje odpowiadająca za rysowanie teksturowanych obiektów:

 

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

c) Prześlij teksturę do fragment shadera:

d) Użyj wartości uzyskanej z tekstury zamiast koloru (objectColor) do pokolorowania obiektu:

II.4. Pobaw się mechanizmem teksturowania:

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

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

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