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ń.
programTex
(tzn. powtórz dla nich punkt 6.a, ale zamiast programSun
nazwij zmienną programTex
).programTex
.b) ładowanie tekstury:
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
. namespace
wykonuje się za pomocą ::, czyli by odwołać się do grid
w namespace
texture
należy napisać texture::grid
)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).
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
vertexTexCoord
out
w vertex shaderze i odpowiadająca jej zmienna in
we fragment shaderze)c) Prześlij teksturę do fragment shadera:
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 shaderaCore::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:
vec4 textureColor = texture2D(~nazwaZmiennejSampler2D, ~nazwaZmiennejWspolrzedneTekstury)
" (vec4
zawiera kolor RGBA)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).
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
.
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.
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
.Przenieś wektor światła i wektor widoku do przestrzeni stycznych
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.
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
Przekształć wektory viewDir
i lightDir
do przestrzeni stycznej mnożąc je przez macierz TBN. Wynik zapisz w zmiennychviewDirTS
i lightDirTS
odpowiednio.
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.)
Przekształć fragment shader, by obsługiwał tangent space
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
normalSampler
. Pobierz z niego wektor normalny analogicznie, jak czytamy kolor zwykłej tekstury z samplera textureSampler
i zapisz go w zmiennej N
.Core::LoadTexture
mapy normalnych dla wszystkich modeli. Maja one taką sama nazwę jak zwykle tekstury, tyle ze z suffiksem "_normals".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
.