Lekcja 7. Filtry tekstur, oświetlenie i klawiatura
Autor: Borys 'borys94' Jeleński
Oryginał: Filters, Lighting & Keyboard (Jeff 'NeHe' Molofee)
Źródła: http://nehe.gamedev.net/data/lessons/vc/lesson07.zip

W tej lekcji nauczę Cię jak używać trzech osobnych filtrów tekstur, jak poruszać obiektem używając klawiatury oraz w jaki sposób stworzyć proste oświetlenie dla twojej sceny. Wiele rzeczy się tutaj powtarza, więc jeśli poprzednie lekcje wciąż sprawiają Ci problemy, koniecznie do nich wróć. Ważne jest zrozumienie podstaw przed przystąpieniem do omawiania poniższego kodu.

Po raz kolejny będziemy modyfikować kod z poprzednich tutoriali. Jeśli znajdą się jakieś poważniejsze zmiany wypiszę całą sekcję kodu, która się zmieniła. Zaczniemy od zdefiniowania kilku nowych zmiennych.

#include <windows.h>         // Nagłówek Windows
#include <stdio.h>         // Nagłówek Standardowego Wejścia/Wyjścia
#include <gl\gl.h>         // Nagłówek biblioteki OpenGL32
#include <gl\glu.h>         // Nagłówek biblioteki Glu32
#include <gl\glaux.h>         // Nagłówek biblioteki Glaux
HDC hDC=NULL;         // Kontekst urządzenia GDI
HGLRC hRC=NULL;         // Kontekst renderowania
HWND hWnd=NULL;         // Uchwyt okna
HINSTANCE hInstance;         // Uchwyt aplikacji/instancji
bool keys[256];         // Tablica stanu klawiszy
bool active=TRUE;         // Flaga aktywności okna
bool fullscreen=TRUE;         // Flaga pełnego ekranu

Poniższe linie są nowe. Dodajemy trzy zmienne typu BOOL. BOOL oznacza, że zmienna tego typu może przybierać tylko dwie wartości ? TRUE lub FALSE. Definiujemy zmienną light do przechowywania informacji o tym, czy oświetlenie ma być włączone. Zmienne lp i lf przechowywać będą stan klawiszy 'L' i 'F' (czy są wciśnięte lub nie). Później wyjaśnię dlaczego ich potrzebujemy, póki co, wiedz, że są ważne.

bool light;         // Światło włączone/wyłączone (NOWE)
bool lp;         // L wciśnięte? (NOWE)
bool fp;         // F wciśnięte? (NOWE)

Teraz stworzymy parę zmiennych, które kontrolować będą kąt obrotu naszej skrzynki według osi X (xrot), kąt obrotu według osi Y (yrot), prędkość obracania się skrzynki względem osi X (xspeed) oraz prędkość obracania skrzynki względem osi Y (yspeed). Zdefiniujemy również zmienną o nazwie z, która kontrolować będzie jak głęboko w ekranie (na osi z) znajduje się skrzynka.

GLfloat xrot;         // Kąt obrotu X
GLfloat yrot;         // Kąt obrotu Y
GLfloat xspeed;         // Prędkość obracania się X
GLfloat yspeed;         // Prędkość obracania się Y
GLfloat z=-5.0f;         // Pozycja Z

Następnie definiujemy kilka tablic, które posłużą nam do stworzenia światła. Będziemy używać jego dwóch typów. Pierwszym typem światła jest światło otaczające (ang. ambient light). Światło otaczające, to takie, które nie pochodzi z żadnego konkretnego kierunku. Wszystkie obiekty na twojej scenie będą przez nie lekko oświetlone. Drugim typem jest światło rozproszone (ang. diffuse light), które tworzone jest na podstawie określonego źródła światła i rozjaśnia powierzchnie, na które które pada. Jego zastosowanie stworzy nieźle wyglądający efekt cieniujący na bokach naszej skrzynki.

Światło tworzymy tak samo, jak kolor - na podstawie trzech wartości. Jeśli pierwsza równa jest 1.0f, a dwie następne 0.0f to otrzymamy jasne czerwone światło. Jeśli trzecia jest równa 1.0f, a dwie pierwsze 0.0f to otrzymamy jasne niebieskie światło. Dochodzi do tego jeszcze czwarta wartość alpha, ale póki co będziemy ustawiać ją na 1.0f.

W linii poniżej zapisujemy wartości dla białego otaczającego światła o połowie intensywności. Tworzymy je, ponieważ bez światła otaczającego miejsca do których nie docierałoby światło rozproszone pozostałyby bardzo ciemne.

GLfloat LightAmbient[]={ 0.5f, 0.5f, 0.5f, 1.0f };         // Wartości światła otaczającego (NOWE)

Teraz z kolei zapiszemy wartości dla bardzo jasnego, w pełni intensywnego światła rozproszonego. Wszystkie wartości równe są 1.0f, co oznacza, że światło jest maksymalnie jasne. Oświetli ono ładnie przód naszej skrzynki.

GLfloat LightDiffuse[]={ 1.0f, 1.0f, 1.0f, 1.0f };         // Wartości światła rozproszonego (NOWE)

Ostatecznie zapisujemy pozycję światła. Pierwsze trzy liczby mają takie samo znaczenie, jak trzy parametry funkcji glTranslatef. Pierwsza określa pozycję na osi X, co pozwala poruszać obiektem w prawo i w lewo, druga, pozycję na osi Y, co pozwala poruszać obiektem w górę i w dół, trzecia z nich pozwala poruszać obiektem w głąb i na zewnątrz ekranu według osi Z. Ponieważ chcemy by nasze światło padało prosto na przód naszej skrzynki, ustawiamy pierwszą wartość na 0.0f (nie poruszamy światła po osi X). Nie chcemy również by światło było powyżej lub poniżej skrzynki, więc drugą wartość ustawiamy również na 0.0f. Chcemy mieć jednak zawsze pewność, że światło znajdować się będzie przed skrzynką, więc ustawiamy je ?poza ekranem? podając jako trzecią liczbę 2.0f. Powiedzmy, że powierzchnia twojego monitora to 0.0f na osi Z. Gdybyś mógł zobaczyć nasze światło, lewitowałoby właśnie przed twoim monitorem. Ustawiając w ten sposób jego pozycję, jedyna możliwość by znajdowało się ono za skrzynką polega na tym, że skrzynka również znajdowałaby się przed monitorem. Oczywiście, jeśli zaszłaby taka sytuacja przestałbyś ją widzieć, więc w tym wypadku nie ma znaczenia gdzie światło się znajduje. Ma to sens?

Naprawdę trudno wyjaśnić znaczenie tej trzeciej liczby, określającej pozycję na osi Z. Powinieneś jednak wiedzieć, że obiekt, którego pozycja to -2.0f na osi Z będzie znajdował się bliżej ciebie, niż o wartości Z -5.0f, czy -100.0f. Gdy ustawisz wartość Z na 0.0f, a obiekt, który wyświetlasz jest duży, może zasłonić on cały ekran. Gdy zaczniesz jednak wprowadzać dodatnie wartości, nie zobaczysz już dłużej tego obiektu, ponieważ "minie on ekran". Właśnie to mam na myśli mówiąc "poza ekranem". Obiekt ciągle tam jest, tyle że go nie widać.

GLfloat LightPosition[]={ 0.0f, 0.0f, 2.0f, 1.0f };         // Pozycja światła

Zmienna filter, którą definiujemy poniżej przechowuje informację, którą teksturę należy aktualnie wyświetlić. Tablica texture przechowywać będzie trzy tekstury różniące się zastosowanym na nich filtrem. Pierwsza z nich (texture[0]) nie będzie filtrowana (GL_NEAREST). Druga (texture[1]) będzie używać filtrowania liniowego (GL_LINEAR), co trochę ją wygładzi. Trzecią z kolei (texture[2]) stworzymy w oparciu o tekstury mipmappowych, co sprawi, że będzie ona świetnie wyglądać.

GLuint filter;         // Której tekstury użyć?
GLuint texture[3];         // Trzy osobne tekstury
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);         // Deklaracja funkcji WndProc

Teraz zajmiemy się ładowaniem bitmapy i tworzeniem z niej tekstur. Użyjemy do tego celu biblioteki glaux, więc upewnij się, że masz ją załączoną w swoim projekcie przed kompilowaniem go. Z tego co wiem, Delphi i Visual C++ posiadają tą bibliotekę, nie jestem jednak pewny, co do innych języków.

[T

UWAGA: Najnowsza wersja Microsoft Visual C++, czyli 2008 (9.0) nie posiada w swoim zbiorze biblioteki glaux. Istnieją dwie metody rozwiązania tego problemu:

1. Ściągnięcie z internetu plików glaux.h oraz glaux.lib oraz wklejenie ich do odpowiednich folderów:

a) glaux.h do C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\gl\

b) glaux.lib do C:\Program Files\Microsoft SDKs\Windows\v6.0A\Lib

2. Skorzystanie z innych funkcji w celu załadowania bitmap

T]Zaraz po powyższym kodzie oraz przed definicją funkcji ReSizeGLScene(), dodamy następującą sekcję. To ten sam kod, którego użyliśmy w lekcji szóstej. Nic się nie zmieniło. Jeśli nie jesteś pewien co robią poniższe linie, przeczytaj szósty tutorial. Wyjaśnia on szczegółowo ładowanie bitmap i tworzenie z nich tekstur.

AUX_RGBImageRec *LoadBMP(char *Filename)         // Ładowanie bitmapy
{
FILE *File=NULL;         // Uchwyt pliku
if (!Filename)         // Upewnij się, że użytkownik podał ścieżkę
{
return NULL;         // Jeśli tak się nie stało, zwróć NULL
}
File=fopen(Filename,"r");         // Sprawdź czy plik istnieje
if (File)         // Jeśli tak
{
fclose(File);         // Zamknij uchwyt pliku
return auxDIBImageLoad(Filename);         // Załaduj bitmapę i zwróć do niej wskaźnik
}
return NULL;         // Jeśli coś poszło nie tak, zwróć NULL
}

Poniższa funkcja załaduje bitmapę (wywołując kod powyżej) i stworzy z niej trzy tekstury. Zmienna Status używana jest by zapisać stan wykonywanych operacji (czy się powiodły lub nie).

int LoadGLTextures()         // Ładowanie bitmapy i tworzenie tekstur
{
int Status=FALSE;         // Status wykonywanych czynności
AUX_RGBImageRec *TextureImage[1];         // Zarazerwuj pamięć dla bitmapy
memset(TextureImage,0,sizeof(void *)*1);         // Ustaw wskaźnik na zero

Polecenie TextureImage[0]=LoadBMP("Data/Crate.bmp") wywoła dla nas przed chwilą zdefiniowaną funkcję LoadBMP() i zapisze rezultat jej wykonania w TextureImage[0]. W tym wypadku załadowana zostanie mapa bitowa o nazwie Crate.bmp, znajdująca się w folderze Data.

        // Załaduj bitmapę, sprawdź czy wystąpiły błędy
if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
{
Status=TRUE;         // Ustaw status na TRUE

Teraz kiedy już mamy załadowaną bitmapę, użyjemy jej do stworzenia tekstur. Linia poniżej mówi OpenGL, że chcemy wygenerować trzy tekstury i przechować je w tablicy texture.

glGenTextures(3, &texture[0]);         // Stwórz trzy tekstury

W szóstym tutorialu używaliśmy tekstur liniowo filtrowanych (GL_LINEAR). Wymagają nieco więcej mocy obliczeniowej, ale nie najgorzej wyglądają. Pierwsza tekstura, którą stworzymy będzie używać parametru GL_NEAREST, co oznacza brak filtrowania. Pobiera to bardzo niewiele mocy obliczeniowej, ale wygląda fatalnie. Jedyną korzyścią tego typu tekstur jest to, że aplikacje je wykorzystujące będą działać szybko nawet na słabych komputerach.

Zauważ, że ustawiamy GL_NEAREST (brak filtrowania) zarówno dla sytuacji gdy wyświetlana tekstura jest większa niż obraz, z którego została stworzona (GL_TEXTURE_MAG_FILTER) jak i dla sytuacji odwrotnej, gdy wyświetlana tekstura jest mniejsza od oryginalnego obrazka (GL_TEXTURE_MIN_FILTER). Możesz również mieszać sposoby filtrowania, dzięki czemu tekstury będą wyglądać trochę lepiej. Nas jednak interesuje szybkość działania, więc ustawiamy GL_NEAREST dla obu przypadków.

        // Stwórz niefiltrowaną teksturę
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Następna tekstura, którą stworzymy jest tego samego typu, co tekstury używane przez nas w kursie szóstym - filtrowana liniowo. Jedyną rzeczą, która się zmieniła, jest to, że zapisujemy teksturę w texture[1] zamiast w texture[0], ponieważ jest to druga tekstura, która tworzymy. Gdybyśmy zapisali ją w texture[0], jak powyżej, nadpisałoby to teksturę używającą GL_NEAREST (tą pierwszą).

        // Stwórz tekturę filtrowaną liniowo
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Teraz czas na nowy sposób tworzenia tekstur. Mipmapping! Być może zauważyłeś, że gdy wyświetlasz obrazek w pomniejszeniu, wiele szczegółów zanika. Wzory, które miały wyglądać naprawdę dobrze zaczynają wyglądać naprawdę źle. Gdy powiesz OpenGL, by stworzył mipmappową tekturę, spróbuje on stworzyć wiele tekstur wysokiej jakości w różnych rozmiarach. Gdy zechcesz wyświetlisz mipmappową teksturę na ekranie, OpenGL wybierze tą najlepiej wyglądającą z tych, które stworzył (o największej liczbie szczegółów) i wyświetli ją na ekranie nie skalując oryginalnego obrazu, co powoduj utratę jakości.

Ponieważ jest to trzecia z kolei tekstura, zapiszemy ją w texture[2].

        // Stwóz mipmappową teksturę
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

Powiedziałem w szóstej lekcji, że istnieje limit rozmiaru obrazka z jakiego tekstura zostanie stworzona. Chodziło o to, że szerokość i wysokość obrazka muszą być potęgami dwójki (64, 128, 256 itd.). Powiedziałem również, że istnieje sposób na ominięcie tego problemu. Jest nim funkcja glBuild2DMipmaps. Możesz użyć każdej bitmapy jakiej chcesz (o dowolnym rozmiarze), gdy tworzysz mipmappowe tekstury.

Poniższa linia tworzy właśnie taką teksturę. Mówimy OpenGL, że jest to tekstura 2D używająca trzech wartości kolorów. TextureImage[0]->sizeX jest szerokością obrazka, TextureImage[1]->sizeY ? jego wysokością. GL_RGB oznacza, że używamy koloru czerwonego, zielonego i niebieskiego. GL_UNSIGNED_BYTE oznacza, że dane, które tworzą obraz są typu unsigned byte (bajt bez znaku). TextureImage[0]->data z kolei to dane mapy bitowej.

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}

Teraz zwalniamy pamięć. Sprawdzamy czy dane bitmapy zostały zapisane w TextureImage[0]. Jeśli tak, zwalniamy je.

if (TextureImage[0])         // Jeśli tekstura istnieje
{
if (TextureImage[0]->data)         // Jeśli obraz tektury istnieje
{
free(TextureImage[0]->data);         // Zwolnij dane tektury
}
free(TextureImage[0]);         // Zwolnij teksturę
}

Ostatecznie zwracamy zmienną Status. Jeśli wszystko poszło dobrze Status będzie równy TRUE, jeśli coś się nie udało przybierze wartość FALSE.

return Status;         // Zwróć Status
}

Teraz załadujemy tekstury i zainicjalizujemy OpenGL. Pierwsza linia funkcji InitGL ładuje tekstury używając kodu, który zdefiniowaliśmy przed chwilą. Następnie włączamy mechanizm tekstur używając glEnable(GL_TEXTURE_2D). Tryb cieni ustawiony jest na miękkie cieniowanie, kolor tła ustawiony jest na czarny, aktywujemy testowanie głębi, a następnie obliczamy najlepszą perspektywę.

int InitGL(GLvoid)         // Wszystkie ustawienia OpenGL idą tutaj
{
if (!LoadGLTextures())         // Załaduj tekstury, sprawdź czy nastąpiły błędy
{
return FALSE;         // Jeśli tak się stało,zwróć FALSE
}
glEnable(GL_TEXTURE_2D);         // Włącz mechanizm tekstur
glShadeModel(GL_SMOOTH);         // Włącz miekkie cieniowanie
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);         // Czarne tło
glClearDepth(1.0f);         // Ustawienia bufora głębokości
glEnable(GL_DEPTH_TEST);         // Włącz testowanie głębi
glDepthFunc(GL_LEQUAL);         // Type testowanie głębi
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);         // Naprawdę niezłe obliczenia perspektywy

Teraz inicjalizujemy oświetlenie. Linia poniżej ustawi światło otaczające jako światło numer jeden (GL_LIGHT1). Na początku tego kursu zapisaliśmy jego parametry w AmbientLight.

glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);         // Ustaw światło otaczające

Następnie ustawiamy światło rozproszone ponownie jako światło numer jeden, jego parametry znajdują się w tablicy DiffuseLight.

glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);         // Ustaw światło rozproszone

Teraz ustalimy pozycję światła. Zapisaliśmy ją w LighPosition (prosto przed przednią ścianą, 0.0f na osi x, 0.0f na osi y, dwie jednostki w kierunku widza na osi z, czyli 2.0f).

glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);         // Pozycja światła

Ostatecznie aktywujemy nasze światło. Nie zobaczysz jednak jeszcze żadnego oświetlenia, ponieważ nie wywołaliśmy funkcji glEnable z parametrem GL_LIGHTING. Światło jest uaktywnione oraz ustawione na odpowiedniej pozycji, ale bez włączonego GL_LIGHTING, nie będzie ono działać.

glEnable(GL_LIGHT1);         // Włącz światło numer jeden
return TRUE;         // Inicjalizacja udana
}

W następnej sekcji kodu wyświetlimy naszą oteksturowaną skrzynię. W razie wątpliwości wróć do szóstego tutoriala.

int DrawGLScene(GLvoid)         // Tutaj zajmujemy się wyświetlaniem
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         // Wyczyść ekran i bufor głębokości
glLoadIdentity();         // Zresetuj widok

Następne trzy linie ustawiają i obracają naszą skrzynię. glTranslatef umieści ją na odpowiedniej pozycji, a za pomocą GlRotatef obrócimy ją najpierw względem osi X, następnie względem osi Y. Używamy do tego wcześnie zdefiniowanych zmiennych xrot oraz yrot.

glTranslatef(0.0f,0.0f,z);         // Ustaw odpowiedią pozycję
glRotatef(xrot,1.0f,0.0f,0.0f);         // Obróć według osi X
glRotatef(yrot,0.0f,1.0f,0.0f);         // Obróć według osi Y

Następna linia nie zmieniła się za bardzo w porównaniu do szóstego kursu. Teraz jednak nie wybieramy texture[0], a texture[filter]. Za każdym razem kiedy naciśniemy klawisz 'F', wartość zmiennej filter zwiększy się, co pozwoli na wybranie jednej z trzech stworzonych przez nas tekstur. Jeśli zmienna ta będzie większa niż dwa, ustawimy ją na zero.

glBindTexture(GL_TEXTURE_2D, texture[filter]);         // Wybierz tekturę

glBegin(GL_QUADS);         // Rozpocznij wyświetlanie czworokątów

glNormal3f jest nową funkcją w moich lekcjach. Normalna jest linią wyznaczającą prosto ze środka figury kąt 90 stopni. Mówi ona OpenGL, w którą stronę zwrócony jest wielokąt, która strona jest górą itd. Gdy używasz oświetlenia musisz ją określić. Jeśli tego nie zrobisz, możesz zobaczyć dziwne rezultaty.

Patrząc na kod przedniej ściany zauważysz, że jej wartość normalnej na osi Z jest dodatnia. To oznacza, że normalna wskazuje na widza, czyli dokładnie tam gdzie chcemy. Dla tylnej ściany wartość normalnej na osi Z jest ujemna, co oznacza, że normalna wskazuje na przestrzeń przed widzem, znowu tak, jak chcemy. Jeśli nasza skrzynka obróci się o 180 stopni względem osi X lub Y przednia ściana będzie skierowana w kierunku ekranu, a tylna ściana w kierunku widza. Nie ważne która ściana skierowana jest na widza, normalna tej ściany będzie wskazywała również na niego. Ponieważ światło znajduje się blisko widza, kiedy normalna na niego wskazuje, wskazuje również na światło. Kiedy to się stanie, ściana zostanie oświetlona. Im dokładniej normalna będzie na nie wskazywała, tym jaśniej ściana zostanie oświetlona. Jeśli jednak ?wejdziesz? do środka skrzyni zauważysz, że panuje tam ciemność. Pamiętaj - Normalna wskazuje na zewnątrz, nie do wewnątrz obiektu, więc w środku skrzynki nie będzie światła, dokładnie tak jak powinno być.

        // Przednia ścina
glNormal3f( 0.0f, 0.0f, 1.0f);         // Normalna wskazująca na widza
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);         // Punkt 1 (Przód)
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);         // Punkt 2 (Przód)
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);         // Punkt 3 (Przód)
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);         // Punkt 4 (Przód)
        // Tylna ściana
glNormal3f( 0.0f, 0.0f,-1.0f);         // Normalna wskazująca w głąb ekranu
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);         // Punkt 1 (Tył)
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);         // Punkt 2 (Tył)
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);         // Punkt 3 (Tył)
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);         // Punkt 4 (Tył)

        // Górna ściana
glNormal3f( 0.0f, 1.0f, 0.0f);         // Normalna wskazująca w górę
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);         // Punkt 1 (Góra)
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);         // Punkt 2 (Góra)
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);         // Punkt 3 (Góra)
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);         // Punkt 4 (Góra)

        // Dolna ściana
glNormal3f( 0.0f,-1.0f, 0.0f);         // Normalna wskazująca w dół
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);         // Punkt 1 (Dół)
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);         // Punkt 2 (Dół)
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);         // Punkt 3 (Dół)
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);         // Punkt 4 (Dół)

        // Prawa ściana
glNormal3f( 1.0f, 0.0f, 0.0f);         // Normalna wskazująca w prawo
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);         // Punkt 1 (Prawo)
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);         // Punkt 2 (Prawo)
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);         // Punkt 3 (Prawo)
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);         // Punkt 4 (Prawo)

        // Lewa ściana
glNormal3f(-1.0f, 0.0f, 0.0f);         // Normalna wskazująca w lewo
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);         // Punkt 1 (Lewo)
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);         // Punkt 2 (Lewo)
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);         // Punkt 3 (Lewo)
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);         // Punkt 4 (Lewo)
glEnd();

Następne dwie linie zwiększają zmienne xrot i yrot o wartości zapisane w xspeed oraz yspeed. Im te wartości będą większe, tym szybciej nasze zmienne będą się zwiększały, co skutkować będzie szybszym obracaniem się skrzyni.

xrot+=xspeed;         // Dodaj xspeed do xrot
yrot+=yspeed;         // Dodaj yspeed do yrot
return TRUE;         // Kontynuuj działanie
}

Teraz wchodzimy do funkcji WinMain(). Dodamy kod włączający/wyłączający światło, obracający skrzynkę, zmieniający filtry tekstur oraz poruszający skrzynkę w głąb i na zewnątrz ekranu. Gdzieś na jej końcu zobaczysz funkcję SwapBuffers(hDC). Zaraz po tej linii, dodaj następujący kod.

SwapBuffers(hDC);         // Przerzuć bufory (Podwójne buforowanie)
if (keys['L'] && !lp)         // Klawisz L zostal naciśnięty nie będąc wcześniej przytrzymywanym?
{

Jeśli lp było równe FALSE, oznacza to, że klawisz 'L' nie został naciśnięty lub został zwolniony. Takie sprawdzanie zmusza osobę do puszczenia klawisza zanim poniższy kod zostanie wywołany. Gdybyśmy tego nie sprawdzali, światło ciągle by migotało, ponieważ program myślałby, że bez przerwy naciskamy klawisz i za każdym razem przechodziłby do poniższego kodu.

Gdy lp przybierze wartość TRUE, oznacza to, że klawisz został wciśnięty, więc włączamy lub wyłączamy światło. Zmienna light może przybierać tylko dwie wartości - TRUE lub FALSE, więc jeśli wydajemy polecenie light=!light, mówimy komputerowi, że ma przestawić wartość zmiennej light na przeciwną. Oznacza to, że jeśli wartością zmiennej light było TRUE, zmienna zostanie teraz ustawiona na FALSE. Analogicznie w odwrotnym przypadku.

lp=TRUE;         // Przypisz lp wartość TRUE
light=!light;         // Zmień wartość zmiennej light na przeciwną

Teraz sprawdzamy wartość zmiennej light i w zależności od niej wykonujemy odpowiednie czynności. Jeśli zmienna ta jest równa jest FALSE, wyłączamy światło. W odwrotnym przypadku światło zostaje włączone.

if (!light)         // Jeśli light równe FALSE
{
glDisable(GL_LIGHTING)         // Wyłącz światło
}
else         // W innym przypadku
{
glEnable(GL_LIGHTING);         // Włącz światło
}
}

Następne linie sprawdzają czy przestaliśmy naciskać klawisz 'L'. Jeśli tak, ustawiamy zmienną lp na FALSE, co oznacza, że klawisz nie jest wciśnięty. Gdybyśmy nie sprawdzali czy klawisz jest zwolniony, bylibyśmy w stanie włączyć światło raz, lecz komputer wciąż myślałby, że 'L' jest ciągle wciśnięte i nie pozwoliłby nam wyłączyć światła z powrotem.

if (!keys['L'])         // Klawisz L został zwolniony?
{
lp=FALSE;         // Jeśli tak, przypisz lp wartość FALSE
}

Teraz dodamy obsługę klawisza 'F'. Jeśli został on naciśnięty i nie jest przytrzymywany, zmienna fp przybierze wartość TRUE, co oznaczać będzie, że klawisz 'F' jest teraz wciśnięty. To zwiększy o jeden zmienną zwaną filter. Jeśli zmienna filter będzie większa od 2, zresetujemy ją, nadając jej z powrotem wartość zero.

if (keys['F'] && !fp)         // Klawisz F został naciśnięty?
{
fp=TRUE;         // Przypisz fp wartość TRUE
filter+=1;         // Zwiększ zmienną filter o jeden
if (filter>2)         // Filter większe od dwa?
{
filter=0;         // Jeśli tak, przypisz mu zero
}
}
if (!keys['F'])         // Klawisz F został zwolniony?
{
fp=FALSE;         // Jeśli tak, przypisz fp wartość FALSE
}

Następne linie sprawdzają czy nacisnęliśmy klawisz 'Page Up'. Jeśli tak , zmniejszamy wartość zmiennej z, przez co przesuwamy skrzynię w głąb ekranu. Dzieje się tak, ponieważ wywołujemy glTranslatef(0.0f,0.0f,z) w funkcji DrawGLScene.

if (keys[VK_PRIOR])         // Page Up zostało naciśnięte?
{
z-=0.02f;         // Jeśli tak, przesuń skrzynię w głąb ekranu
}

Te cztery linie sprawdzają stan klawisza 'Page Down'. Jeśli jest on wciśnięty zwiększamy wartość zmiennej z, poruszając tym samym skrzynię w kierunku widza.

if (keys[VK_NEXT])         // Page Dwon zostało naciśnięte?
{
z+=0.02f;         // Jeśli tak, przesuń skrzynię w kierunku widza
}

Teraz sprawdzamy wciśnięcia strzałek na klawiaturze. Naciskając strzałkę w lewo lub w prawo, zmienna xspeed jest zwiększa lub zmniejszana. Naciskając strzałkę w górę lub w dół, zmniejszana/zwiększana jest zmienna yspeed. Powiedziałem wcześniej, że im większa jest jedna z tych zmiennych, tym szybciej skrzynia będzie obracać się wokół określonej osi.

if (keys[VK_UP])         // Strzałka w górę została wciśnięta?
{
xspeed-=0.01f;         // Jeśli tak, zmniejsz xspeed
}
if (keys[VK_DOWN])         // Strzałka w dół została wciśnięta?
{
xspeed+=0.01f;         // Jeśli tak, zwiększ xspeed
}
if (keys[VK_RIGHT])         // Strzałka w prawo została wciśnięta?
{
yspeed+=0.01f;         // Jeśli tak, zwiększ yspeed
}
if (keys[VK_LEFT])         // Strzałka w lewo została wciśnięta?
{
yspeed-=0.01f;         // Jeśli tak, zmniejsz yspeed
}

Jak we wszystkich poprzednich tutorialach, upewnij się, że tytuł okna jest właściwy.

if (keys[VK_F1])         // F1 wciśnięte?
{
keys[VK_F1]=FALSE;         // Jeśli tak, ustaw stan klawisza na FALSE
KillGLWindow();         // Zniszcz nasze dotychczasowe okno
fullscreen=!fullscreen;         // Zmień wartość flagi fullscreen na przeciwną
        // Stwórz nowe okno OpenGL
if (!CreateGLWindow("NeHe's Textures, Lighting & KeyboardTutorial",640,480,16,fullscreen))
{
return 0;         // Wyjdź jeśli okno nie zostało stworzone
}
}
}
}
}
        // Wyłączanie
KillGLWindow();         // Zniszcz okno
return (msg.wParam);         // Wyjdź z programu
}

Po zakończeniu lektury tego kursu powinieneś umieć tworzyć i integrować z wysokiej jakości, oteksturowanymi obiektami, rozumieć korzyści z używania trzech odmiennych filtrów tekstur, posiadać umiejętność korzystania z klawiatury oraz potrafić stworzyć proste oświetlenie dla twojej sceny, tworząc ją bardziej realistyczną.