Wiedza o nakładaniu tekstur (ang. texture mapping) przynosi wiele korzyści. Powiedzmy, że chcesz, aby rakieta leciała przez ekran. Przed tą lekcją, zapewne stworzyłbyś rakietę z wielokątów i pokolorował ją. Używając teksturowania możesz wziąć obrazek rakiety i przesuwać go po ekranie. Co wygląda lepiej? Fotografia czy obiekt z trójkątów? Korzystając z tekstur nie tylko poprawisz wygląd, ale także przyspieszysz swój program. Oteksturowana rakieta będzie tylko jednym czworokątem poruszającym się po ekranie. Rakieta stworzona z wielokątów może ich zawierać setki tysięcy. Pojedynczy, oteksturowany czworokąt oznacza mniejsze zapotrzebowanie na moc.
Zacznijmy od dodania paru linijek na początku kodu. Pierwsza linia to #include <stdio.h>. Dodając ten nagłówek możemy pracować z plikami. Następnie dodajemy trzy zmienne zmiennoprzecinkowe (ang. float)... xrot, yrot, zrot. Posłużą nam one do obracania sześcianu wokół osi. Ostatnia linia GLuint texture[1] daje nam miejsce na jedną teksturę. Jeżeli chcesz mieć więcej tekstur, zmień liczbę w nawiasie.
Teraz zaraz za powyższym kodem, a przed ReSizeGLScene(), dodajemy poniższą sekcję. Zajmie się ona załadowaniem bitmapy z pliku. Jeżeli takowa nie istnieje dostaniemy NULL, co znaczy, że nie udało się jej załadować. Zanim zaczne, jedna BARDZO ważna rzecz. Szerokość i wysokość obrazka, którego chcesz użyć MUSZĄ być potęgą dwójki. Muszą mieć przynajmniej 64 pixele i raczej nie powinny mieć więcej niż 256 pixeli. Są spodoby na ominięcie tego ograniczenia, ale teraz zastosujmy się do tych wymogów.
Najpierw tworzymy uchwyt do pliku. Uchwyt to liczba określająca zasób tak, że nasz program może z niego skorzystać. Początkowo ustawiamy uchwyt na NULL.
Musimy się upewnić, że nazwa pliku została podana. Ktoś mógł użyć LoadBMP() bez podania parametru, więc musimy to sprawdzić. Nie chcemy próbować ładować niczego :)
Jeżeli podano nazwę pliku, sprawdzamy czy istnieje. Ta linia próbuje go otworzyć
Jeżeli możemy go otworzyć to znaczy, że istnieje. Zamykamy plik. Następnie odczytujemy dane funkcją auxDIBImageLoad(Filename).
Jeżeli nie możemy otworzyć pliku zwracamy NULL. Znaczy to że plik nie mógł zostać załadowany. Później w programie sprawdzimy czy plik udało się załadować, jeżeli nie, to pokażemy komunikat błędu i zamkniemy program.
Ta sekcja ładuje bitmapę i przekształca ją w texturę
Tworzymy zmienną Status - posłuży nam do kontrolowania procesu tworzenia textury z bitmapy. FALSE (pol. fałsz) oznacza, że nie załadowano lub nie zbudowano textury. Jest to wartość domyślna
Teraz tworzymy strukturę, w której będziemy mogli trzymać naszą bitmapkę. Rekord ten zawiera wysokość, szerokość oraz dane bitmapy.
Czyścimy obrazek, żeby upewnić się, że jest pusty.
Następnie ładujemy bitmapę i przekształcamy ją w teksturę. TextureImage[0]=LoadBMP("Data/NeHe.bmp") skoczy do procedury LoadBMP(). Plik NeHe.bmp, który jest w katalogu Data zostanie załadowany. Jeżeli wszystko pójdzie dobrze to dane obrazka znajdą się w TextureImage[0]. Ustawiamy Status na TRUE (pol. prawda) i zaczynamy budować teksturę.
Teraz, kiedy załadowaliśmy obrazek do TextureImage[0], zbudujemy z niego texturę. Pierwsza linia glGenTextures(1, &texture[0]) mówi OpenGL, że chcemy stworzyć jedną teksturę (zwiększ liczbę, jeżeli chcesz mieć ich więcej). Pamiętasz pewnie jak na początku stworzyliśmy miejsce na jedną teksturę pisząc GLuint texture[1]. Pierwsza tekstura jest przechowywana w &texture[0], a nie w &texture[1], jak mogłeś pomyśleć. Gdybyśmy chcieli mieć dwie textury napisalibyśmy GLuint texture[2] i druga tekstura byłaby wtedy w texture[1].
Druga linia glBindTexture(GL_TEXTURE_2D, texture[0]) każe OpenGL powiązać nazwę texture[0] z obiektem tekstury. Tekstury 2D mają wysokość (oś Y) i szerokość (oś X). Głównym zadaniem glBindTexture() jest przypisanie nazwy tekstury do jej danych. W tym wypadku mówimy OpenGL, że pamięć jest dostępna w &texture[0]. Możemy już stworzyć teksturę, będzie ona przechowywana w pamięci, na którą wskazuje referencja &texture[0].
Następnie tworzymy rzeczywistą teksturę. Poniższe linie mówia, żę tekstura będzie dwuwymiarowa (GL_TEXTURE_2D). Zero oznacza poziom szczegółowości - z reguły równe zero. Trzy jest ilością składników obrazu. Ponieważ obraz jest zrobiony z czerwonego, zielonego i niebieskiego, wpisujemy 3. TextureImage[0]->sizeX oznacza szerokość tekstury. Jeśli znasz szerokość możesz ją tu wpisać, ale pewniej jest jak komputer zrobi to za Ciebie. TextureImage[0]->sizeY to wysokość tekstury. Zero znasza ramkę - najczęściej ustawione na zero. GL_RGB mówi OpenGL, że obraz składa się z czerownego, zielonego i niebieskiego - w tej kolejności. GL_UNSIGNED_BYTE oznacza, że dane, które tworzą obraz są typu unsigned byte, i w końcu...TextureImage[0]->data mówi OpenGL skąd wziąć dane o obrazie.
Następne dwie linie mówią OpenGL jakiego typu filtrowania użyć, kiedy obrazek jest większy (GL_TEXTURE_MAG_FILTER) albo rozciągnięty na ekranie, a jakiego kiedy mniejszy (GL_TEXTURE_MIN_FILTER) na ekranie niż w texturze. Bardzo często używam GL_LINEAR dla obu. Tekstura wygląda wtedy gładko z daleka i z bliska. Używanie GL_LINEAR wymaga większej pracy procesora karty graficznej, więc jeśli Twój system jest wolny, skorzystaj z GL_NEAREST. Tekstura będzie wyglądała gorzej, ale przyspieszysz program. Możesz też spróbować wymieszać sposoby filtrowania. Lepiej filtrując obiekty, które są blisko, a gorzej te w oddali.
Teraz zwalniamy pamięć, w której przechowywana była bitmapa. Sprawdzamy, czy była ona w TextureImage[0]. Jeżeli tak to sprawdzamy czy dane o niej były tam przechowywane. Jeżeli tak to usuwamy ją. Wtedy zwalniamy strukturę obrazka upewniając się, że użyta pamięć została zwolniona.
W końcu zwracamy status. Jeżeli wszystko dobrze, zmienna status będzie miała wartość TRUE. Jeżeli coś zawiodło - FALSE.
Dodałem parę linijek do kodu InitGL. Przepiszę całą sekcję, żebyś widział co i gdzie dodałem. Pierwsza linia if (!LoadGLTextures()) skacze do procedury wyżej - ładuje bitmapę i robi z niej teksturę. Jeśli LoadGLTexture() zawiedzie, to następna linia zwróci FALSE. Jeżeli wszystko się powiedzie, włączamy mechanizm teksturowania (ang. texture mapping). Jeżeli zapomnisz o tej lini, Twój obiekt będzie prawdopodobnie koloru białego - uważaj na to.
Teraz rysujemy sześcian (ang. cube). Możesz zamienić kod DrawGLScene z kodem poniżej, albo tylko dodać nowe linie. Ta sekcja jest obfita w komentarze i datego jej zrozumienie nie powinno sprawić Ci problemów. Pierwsze dwie linie glClear() oraz glLoadIdentity() są takie jak w poprzedniej lekcji. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) wyczyści ekran na kolor wybrany w InitGL(). w naszym przypadku będzie to czarny. Bufor głębi także zostaje wyczyszczony. Widok jest resetowany poleceniem glLoadIdentity().
Te trzy linie obracają kostkę wokół osi x, y i z. Jak bardzo to robią zależy od zmiennych xrot, yrot, zrot.
Następna linia wybiera teksturę, której chcemy użyć. Jeżeli byłoby więcej tekstur, wybierał byś ją w tak: glBindTexture(GL_TEXTURE_2D, texture[numer_tekstury]). Jeżeli chcesz zmienić teksturę, to poprostu bindujesz inną - nic prostszego :) Bardzo ważną rzeczą jest zakaz bindowania tekstury w bloku glBegin() i glEnd() - rób to zawsze przed albo po.
Żeby właściwie nałożyć teksturę na czworokąt, musisz upewnić się, że prawy górny róg textury jest nakładany na prawy górny róg czworokąta - pozostałe analogicznie. Jeżeli narożniki tekstury nie pasują do odpowiednich rogów czworokąta, to obrazek będzie zniekształcony (np. do góry nogami) albo nie będzie go wcale :(
Pierwsz wartość glTexCoord2f to współrzędna X, 0.0f oznacza lewą część tekstury, 0.5f - środek, 1.0f - prawą. Druga wartość to współrzędna Y. 0.0f oznacza dół, 0.5f - środek, 1.0f - górę.
Wiemy, że lewy górny róg textury to (0.0f, 1.0f) a lewy górny wierzchołek czworokąta to (-1.0f, 1.0f). Teraz musisz połączyć pozostałe trzy wierzchołki textury z narożnikami czworokąta.
Spróbuj pobawić się z wartościami w glTexCoord2f(). Zmieniając 1.0f na 0.5f spowodujesz rysowanie tylko jednej połówki textury, od 0.0f(lewo) do 0.5f(środek). Zmiana 0.0f na 0.5f spowoduje narywowanie tylko drugiej połówki tekstury, od 0.5f(środek) do 1.0f(prawo).
Teraz zwiększamy wartości dla xrot, yrot i zrot. Spróbuj zmienić poniższe wartości, to kostka będzie obracać się szybciej lub wolniej. Spróbuj też pozmieniać znaki.
Teraz powinieneś lepiej rozumieć mechanizm nakładania tekstur (and. texture mapping). Powinieneś być w stanie nakładać tekstury na każdą powierzchnię. Jeśli czujesz, że dobrze rozumiesz temat, to spróbuj nałożyć sześć różnych tekstur na nasz sześcian.
Teksturowanie nie jest trudne, kiedy rozumiesz jak działają współrzędne tekstury. Jeśli masz problem w zrozumieniu, którejś części tej lekcji, to daj znać. Przepiszę wtedy tą sekcję, albo odpowiem na maila. Baw się dobrze tworząc oteksturowane obiekty na scenie :)