Lekcja 20. Maskowanie
Autor: Łukasz 'lmmilewski' Milewski
Oryginał: Masking (Jeff 'NeHe' Molofee)
Źródła: http://nehe.gamedev.net/data/lessons/vc/lesson20.zip

Witaj na dwudziestej lekcji. Format bitmap jest obsługiwany przez prawie każdy system operacyjny. Łatwo załadować bitmapę i wykorzystać ją jako teksturę. Do tej pory, używaliśmy mieszania kolorów, aby umieścić tekst i inne obrazki na ekranie, bez wymazywania tego, co jest za nimi. Jest to efektywne, ale wynik nie zawsze jest zadowalający.

W większości wypadków tekstura, dodana poprzez mieszanie kolorów, zbyt jest bardzo lub niedostatecznie widoczna. Gdy tworzysz grę, wykorzystującą sprity, raczej nie chcesz aby scena za postacią prześwitywała przez jej ciało. Gdy wypisujesz tekst na ekran, chcesz aby był jednolity i łatwy do odczytania.

W tym momencie wygodne staje się maskowanie. Jest to proces składający się z dwóch kroków. Najpierw umieszcza się czarnobiały obraz tekstury na scenie (maska). Biały reprezentuje część przezroczystą, czarny nieprzezroczystą. Wykorzystujemy odpowiedni tryb mieszania kolorów, tak aby pokazał się tylko czarny kolor. Następnie zmieniamy tryb mieszania kolorów i mapujemy teksturę na czarny kawałek na scenie. Wykorzystujemy odpowiedni tryb mieszania kolorów tak aby tekstura pojawiła się tylko tam, gdzie obecnie jest czarny kolor. Ogólnie, tam gdzie nałożymy maskę będzie czarno (pozostała część tła zostanie nie zmieniona). Przy nakładaniu tekstury, robimy to tak, że pojawi się tylko w miejscach gdzie jest czarno.

Umieszczę kod programu z tej lekcji w tekscie. Wyjątekiem będą części, które się nie zmieniły. Jeżeli jesteś gotowy nauczyć się czegoś nowego to... do dzieła.

#include <windows.h>         // Nagłówki windowsa
#include <math.h>         // Biblioteka matematyczna
#include <stdio.h>         // Standardowe wejście/wyjście
#include <gl\gl.h>         // Biblioteka OpenGL
#include <gl\glu.h>         // Biblioteka Glu32
#include <gl\glaux.h>         // Biblioteka GLAux
HDC        hDC=NULL;                            
HGLRC        hRC=NULL;                            
HWND        hWnd=NULL;                            
HINSTANCE    hInstance;                            

Skorzystamy z siedmiu zmiennych globalnych. masking to wartość boolowska (PRAWDA/FAŁSZ), pamiętająca czy maskowanie jest włączone, czy nie. Zmienna mp jest wykorzystywana do oznaczania, że klawisz M jest trzymany. Podobnie sp - oznacza, że spacja jest wciśnięta. Zmienna scene pamięta czy rysujemy pierwszą czy drugą scenę.

Rezerwujemy także miejsce dla pięciu tekstur w tablicy texture. loop to licznik pętli. Skorzystamy z niego kilka razy w programie. Jest też zmienna roll. Przyda się przy przewijaniu tekstur przez ekran. Takie przewijanie daje dość ciekawy efekt. Skorzystamy z niej także, aby obracać obiekt w scenie drugiej.

bool    keys[256];         // Tablica wykorzystywana do obsługi klawiatury
bool    active=TRUE;         // Czy okno jest aktywne
bool    fullscreen=TRUE;         // Czy tryb pełnoekranowy
bool    masking=TRUE;         // Maskowanie włączone/wyłączone
bool    mp;         // Czy klawisz M jest naciśnięty
bool    sp;         // Czy spacja jest naciśnięta
bool    scene;         // Którą scenę narysować
GLuint    texture[5];         // Pamięć na pięć tekstur
GLuint    loop;         // Licznik w pętlach
GLfloat    roll;         // Przewijanie tekstury
LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);         // Deklaracja WndProc

Kod odpowiedzialny za załadowanie bitmapy się nie zmienił. Jest taki sam jak np. w lekcji 6.

W poniższym kodzie tworzymy miejsce na pięć obrazków. Czyścimy pamięć i ładujemy pięć bitmap. Przechodzimy w pętli po wszystkich obrazkach i konwertujemy je do tekstur. Tekstury są zapamiętane w texture[0-4].

int LoadGLTextures()         // Ładuje bitmapy i konwertuje je do tekstur
{
    int Status=FALSE;         // Wskaźnik statusu
    AUX_RGBImageRec *TextureImage[5];         // Miejsce na dane tekstur
    memset(TextureImage,0,sizeof(void *)*5);         // Zerujemy pamięć
    if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&         // Ładuj teksturę
     (TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&         // Pierwsza maska
     (TextureImage[2]=LoadBMP("Data/image1.bmp")) &&         // Pierwszy obraz
     (TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&         // Druga maska
     (TextureImage[4]=LoadBMP("Data/image2.bmp")))         // Drugi obraz
    {
        Status=TRUE;         // Ustaw status na TRUE
        glGenTextures(5, &texture[0]);         // Stwórz pięć tekstur
        for (loop=0; loop<5; loop++)         // pętla po wszystkich obrazkach
        {
            glBindTexture(GL_TEXTURE_2D, texture[loop]);
            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[loop]->sizeX, TextureImage[loop]->sizeY,
                0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
        }
    }
    for (loop=0; loop<5; loop++)         // Pętla po pięciu teksturach
    {
        if (TextureImage[loop])         // Jeżeli tekstura istnieje
        {
            if (TextureImage[loop]->data)         // Jeżeli obraz tekstury istnieje
            {
                free(TextureImage[loop]->data);         // Zwolnij pamięć po obrazku tekstury
            }
            free(TextureImage[loop]);         // Zwolnij strukturę obrazka
        }
    }
    return Status;         // Zwróć status
}

Kod ReSizeGLScene() nie uległ zmianie, więc pozwolę go sobie pominąć. Kod inicjalizujący jest prosty jak drut. Ładujemy tekstury, ustawiamy kolor, ustawiamy bufor głębokości, włączamy ładne cienie oraz teksturowanie. Prosty program. Nie ma potrzeby tworzyć skomplikowanej funkcji init ;-)

int InitGL(GLvoid)         // Wszystkie ustawienia OpenGL'a
{
    if (!LoadGLTextures())         // Załaduj tekstury
    {
        return FALSE;         // Jeżeli się nie udało to zwróć FALSE
    }
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);         // Wyczyść tło na kolor czarny
    glClearDepth(1.0);         // Włącz czyszczenie bufora głębi
    glEnable(GL_DEPTH_TEST);         // Włącz testowanie głębi
    glShadeModel(GL_SMOOTH);         // Włącz ładne, ciągłe cieniowanie
    glEnable(GL_TEXTURE_2D);         // Włącz teksturowanie 2D
    return TRUE;         // Inicjalizacja poszła dobrze
}

Teraz ciekawa część. Kod rysujący. Zaczynamy tak jak zawsze. Czyścimy ekran i bufor głębi. Następnie resetujemy macierz modelview i przesuwamy się w stronę sceny o dwie jednostki, dzięki czemu możemy zobaczyć scenę.

int DrawGLScene(GLvoid)         // Tu jest całe rysowanie
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);         // Wyczyść scenę i bufor głębi
    glLoadIdentity();         // Resetuj macierz modelview
    glTranslatef(0.0f,0.0f,-2.0f);         // Przesuń się dwie jednostki do sceny

Pierwsza linia poniżej wybiera teksturę 'logo'. Wyświetlimy tę teksturę na ekran wykorzystując czworokąt. Specyfikujemy cztery koordynaty tekstury razem z wierzchołkami.

Uzupełnienie, której autorem jest Jonathan Roy: Pamiętaj, że OpenGL ma system grafiki oparty na wierzchołkach. Większość ustawianych parametrów jest zapamiętywana jako atrybuty wierzchołka. Koordynaty tekstury to jeden z nich. Po prostu specyfikujesz koordynaty tekstury dla każdego wierzchołka wielokąta, a OpenGL automatycznie wypełnia powierzchnię, między wierzchołkami, teksturą. Ten proces jest znany jako interpolacja. Interpolacja to standardowa technika, która pozwala OpenGL określić jak zadany parametr zmienia się pomiędzy wierzchołkami (wartości są znane tylko w wierzchołkach).

Tak, jak w poprzednich lekcjach udajemy, że widzimy czworokąt od przodu i przypisujemy koordynaty tekstury następująco: (0.0, 0.0) do lewego, dolnego rogu, (0.0, 1.0) do lewego górnego rogu, (1.0, 0.0) do prawego, dolnego i (1.0, 1.0) do prawego, górnego. Teraz, gdy ustawiłeś te wartości, możesz powiedzieć jakie koordynaty powinny być przypisane do środka czworokąta. Tak!! (0.5, 0.5). Nigdzie, w kodzie, nie wyspecyfikowałeś tej wartości, nieprawdaż? Gdy OpenGL rysuje czworokąt, to oblicza tę wartość. To magia, bo OpenGL potrafi obliczyć te wartości niezależnie od kształtu, wielkości czy orientacji wielokąta.

W tej lekcji zrobimy sztuczkę. Zadamy koordynaty tekstury inne niż 0.0 czy 1.0. Koordynaty tekstury powinny być znormalizowane. Wartość 0.0 odpowiada jednemu krańcowi tekstury, podczas gdy 1.0 przeciwległemu. Powyżej 1.0, odwzorowanie zawija się wokół krawędzi i tekstura się powtarza. Innymi słowy. Koordynaty (0.3, 0.5) oznaczają dokładnie ten sam piksel obrazka tekstury, co (1.3, 0.5), albo (12.3, -2.5). W tej lekcji, uzyskamy efekt kaflowania, poprzez zadanie wartości 3.0 zamiast 1.0. W ten sposób łatwo powtarzamy teksturę 9 razy (3x3) na powierzchni czworokąta.

Dodatkowo, użyjemy zmiennej roll aby przesuwać teksturę po powierzchni czworokąta. Wartość 0.0 roll, która jest dodawana do pionowych koordynatów, oznacza, że tekstura przy dolnej krawędzi czworokąta zaczyna się dolną krawędzią obrazka tekstury. Gdy roll równa się 0.5, to mapowanie dolnej grawędzi czworokąta zaczyna się pół obrazka wyżej. Przesuwanie tekstur może służyć do generowania ciekawych efektów, takich jak poruszające się chmury czy słowa obracające się wokół obiektu.

    glBindTexture(GL_TEXTURE_2D, texture[0]);         // Wybierz teksturę logo
    glBegin(GL_QUADS);         // Zacznij rysować oteksturowany czworokąt
        glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);         // Dół, lewo
        glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);         // Dół, prawo
        glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f, 1.1f, 0.0f);         // Góra, prawo
        glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f, 1.1f, 0.0f);         // Góra, lewo
    glEnd();         // Koniec rysowania czworokąta

OK, wróćmy do rzeczywistości. Teraz włączamy mieszanie kolorów. Aby ten efekt zadziałał, musimy wyłączyć testowanie głębi. To bardzo ważne. Jeżeli tego nie wyłączysz, to prawdopodobnie nic nie zobaczysz. Cały obrazek zniknie!

    glEnable(GL_BLEND);         // Włącz mieszanie kolorów
    glDisable(GL_DEPTH_TEST);         // Wyłącz test głębi

Pierwszą rzeczą, którą należy teraz zrobić, jest sprawdzenie czy będziemy maskować obraz czy mieszać go tak jak dawniej. Poniższa linia kodu sprawdza czy maskowanie jest ustawione na TRUE. Jeżeli tak, to ustawiamy mieszanie kolorów tak, aby maska była właściwie wyświetlana na ekranie.

    if (masking)         // Czy maskowanie jest włączone?
    {

Jeżeli masking jest ustawione na TRUE, to linia poniżej ustawi funkcję mieszającą dla naszej maski. Maska to kopia tekstury, którą chcemy narysować na ekranie. Tyle, że czarnobiała. Każdy obszar maski, który jest biały oznacza przezroczystość. Każdy obszar czarny, brak przezroczystości.

Komenda blend zachowuje się następująco: Kolor docelowy (kolor ekranu) będzie ustawiony na czarny jeżeli kopiowany obszar maski jest czarny. To oznacza, że obszary ekranu, które przykrywa czarna maska, będą czarne. Wszystko co było w tym miejscu pod maską zostanie wyczyszczone do koloru czarnego. Część maski, o białym kolorze, nie powoduje zmiany na ekranie.

        glBlendFunc(GL_DST_COLOR,GL_ZERO);         // Mieszaj kolor ekranu z zerem (czarny)
    }

Teraz sprawdzamy, którą scenę narysować. Jeżeli scene jest TRUE to rysujemy drugą, w przeciwnym wypadku pierwszą scenę.

    if (scene)         // Czy rysujemy drugą scenę?
    {

Nie chcemy, aby wszystko było duże, dlatego przesuwamy się jeszcze jedną jednostkę do sceny. To zmniejsza wielkość obiektów.

Po przesunięci, obrazamy scenę z 0-360 stopni zależnie od wartości roll. Jeżeli roll jest 0.0 będziemy obracać o 0 stopni. Jeżeli 1.0 to o 360. Szybka rotacja, ale nie chciało mi się tworzyć kolejnej zmiennej tylko po to, aby obrócić obrazek na środku ekranu ;-)

        glTranslatef(0.0f,0.0f,-1.0f);         // Przesunięcie
        glRotatef(roll*360,0.0f,0.0f,1.0f);         // Obrót wokół osi Z

Mamy przesuwające się logo. Scena obraca się wokół osi Z, powodując rotację każdego z rysowanych obiektów przeciwnie do ruchu wskazówek zegara. Teraz wystarczy sprawdzić czy maskowanie jest włączone. Jeżeli tak, to narysujemy maskę, a następnie nasz obiekt. Jeżeli maskowanie jest wyłączone, to po prostu narysujemy obiekt.

        if (masking)         // Is Masking On?
        {

Jeżeli masking jest TRUE, to kod poniżej narysuje maskę na ekran. Nasz tryb mieszania kolorów powinien być ustawiony odpowiednio, ponieważ już raz sprawdzaliśmy maskowanie. Teraz wystarczy tylko narysować maskę. Wybieramy maskę 2 (bo to jest druga scena). Po wybraniu maski, teksturujemy czworokąt. Wielokąt jest 1.1 jednostki w lewo i w prawo, tak aby wypełniał ekran trochę dalej. Chcemy aby tylko jedna tekstura się pojawiła, więc koordynaty są od 0.0 do 1.0.

Po narysowaniu maski na ekran, nieprzezroczysta kopia tekstury pojawi się na ekranie. Ostatecznie scena wygląda jakby ktoś wziąć foremkę do ciastek i wyciął figurę tekstury na scenie, pozostawiając czarne miejsce.

            glBindTexture(GL_TEXTURE_2D, texture[3]);         // Wybierz teksturę maski
            glBegin(GL_QUADS);         // Zacznij rysować czworokąt
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
            glEnd();
        }

Teraz, gdy ustawiliśmy maskę, przyszedł czas aby ponownie zmienić tryb mieszania kolorów. Tym razem powiemy OpenGL aby kopiował część naszej pokolorowanej tekstury, która nie jest czarna na ekran. Ponieważ końcowa tekstura jest dokładną kopią maski, ale z kolorem, to jedyne części, które zostaną narysowane, to te, które lądują na czarnym kawałku maski. Ponieważ maska jest czarna, nic z ekranu nie będzie prześwitywało przez naszą teksturę. To pozostawia nas z nieprzezroczystą teksturą unoszącą się nad ekranem.

Zauważ, że wybieramy drugi obraz po wybraniu ostatecznego trybu mieszania kolorów. To wybiera pokolorowany obraz (obraz, na którym bazuje druga maska). Zauważ także, że rysujemy ten obraz zaraz na masce. Te same koordynaty tekstury, te same wierzchołki.

Jeżeli nie położylibyśmy wcześniej maski, to obraz nadal były kopiowany na ekran. Tyle tylko, że prześwitywała by przez niego scena.

        glBlendFunc(GL_ONE, GL_ONE);         // Kopiuj obraz 2 na ekran
        glBindTexture(GL_TEXTURE_2D, texture[4]);         // Wybierz teksturę drugiego obrazu
        glBegin(GL_QUADS);                        
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
        glEnd();                            
    }

Jeżeli scene jest FALSE, to rysujemy pierwszą scenę (moja ulubiona).

    else
    {

Zaczynamy, sprawdzając czy masking jest TRUE czy FALSE. Tak jak wcześniej.

        if (masking)         // Is Masking On?
        {

Jeżeli masking jest TRUE to rysujemy maskę 1. Zauważ, że tekstura przesuwa się z prawej do lewej (roll jest dodawane do koordynatów poziomych). Chcemy aby tekstura wypełniła cały ekran, dlatego nie przesuwany się do sceny.

            glBindTexture(GL_TEXTURE_2D, texture[1]);         // Wybierz teksturę maski
            glBegin(GL_QUADS);                    
                glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);    
                glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);    
                glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);    
                glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);    
            glEnd();                        
        }

Ponownie włączamy mieszanie kolorów i wybieramy teksturę dla sceny 1. Nakładamy teksturę na maskę. Zauważ, że przesuwamy także tę teksturę. Gdybyśmy tak nie zrobili, to maska nie zgadzałaby się z obrazkiem.

        glBlendFunc(GL_ONE, GL_ONE);         // Kopiuj kolor z obrazka 1
        glBindTexture(GL_TEXTURE_2D, texture[2]);         // Wybierz teksturę pierwszego obrazka
        glBegin(GL_QUADS);                        
            glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);    
            glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);    
            glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);    
            glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);    
        glEnd();                            
    }

Następnie włączmy test głębokości i wyłączamy mieszanie kolorów. Dzięki temu nie będą się działy dziwne rzeczy w dalszej części programu ;-)

    glEnable(GL_DEPTH_TEST);         // Włącz test głębokości
    glDisable(GL_BLEND);         // Wyłącz mieszanie kolorów

Zostało tylko zwiększenie wartości roll. Jeżeli roll przekracza 1.0 to odejmujemy 1.0. To zapobiega zbyt szybkiemu wzrostowi wartości roll.

roll+=0.002f;         // Zwiększ wartość roll
    if (roll>1.0f)         // Czy roll większe od jedynki
    {
        roll-=1.0f;         // odejmij jeden
    }
    return TRUE;         // Wszystko OK
}

KillGLWindow(), CreateGLWindow() i WNDProc() się nie zmieniły, więc pomijamy.

W WinMain zmienił się tytuł okna. Teraz nazywa się "NeHe's Masking Tutorial". Zmień tytuł na jaki chcesz :-)

int WINAPI WinMain(    HINSTANCE    hInstance,         // Instancja
            HINSTANCE    hPrevInstance,         // Poprzednia instancja
            LPSTR        lpCmdLine,         // Parametry linii komend
            int        nCmdShow)                
{
    MSG    msg;         // Struktura komunikatu Windows
    BOOL    done=FALSE;         // Czy wyjść z pętli
        // Zapytaj użytkownika, czy ma być tryb pełnoekranowy
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;         // w oknie
    }
        // Stwórz okno OpenGL
    if (!CreateGLWindow("NeHe's Masking Tutorial",640,480,16,fullscreen))
    {
        return 0;         // Gdy tworzenie okna się nie powiodło
    }
    while(!done)                                
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))         // Czy jest jakiś komunikat do odebrania
        {
            if (msg.message==WM_QUIT)         // Czy jest komunikat Quit
            {
                done=TRUE;         // Jeżeli tak to wychodzimy
            }
            else         // Jeżeli nie to przetwarzamy pozostałe komunikaty
            {
                TranslateMessage(&msg);         // Tłumacz komunikat
                DispatchMessage(&msg);         // Wyślij komunikat
            }
        }
        else         // Jeżeli nie ma komunikatów
        {
        // Narysuj scenę. Uważaj na klawisz ESC
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])         // Czy okno aktywne? Czy nadszedł komunikat QUIT
            {
                done=TRUE;         // Odebrano komunikat zmuszający do wyjścia
            }
            else         // Uaktualnij scenę
            {
                SwapBuffers(hDC);         // Podwójne buforowaniek. Zamień bufory.

Teraz do kodu obsługujące klawiaturę. Sprawdzamy czy wciśnięto spację. Jeżeli tak to ustawiamy zmienną sp. Jeżeli sp jest TRUE, to poniższy kod nie zostanie przetworzony drugi raz aż do czasu zwolniania spacji. Dzięki temu program nie będzie szybko przeskakiwał ze sceny do sceny. Po ustawieniu sp na TRUE, zmieniamy wyświetlaną scenę (zmienna scene). Jeżeli było TRUE, to staje się FALSE, jeżeli było FALSE to staje się TRUE. W powyższym kodzie, jeżeli scene jest FALSE to jest rysowana scena numer jeden. Jeżeli scene jest TRUE to jest rysowana druga scena. [translator]Takie podejście nie jest zbyt dobre. Gdyby była potrzeba dodania trzeciej sceny to programista miałby sporo pracy. Lepiej zrobić zmienną typu int. W poważniejszym projekcie należałoby wydzielić sceny do osobnych klas i zdać się na mechanizm polimorfizmu.[/translator].

                if (keys[' '] && !sp)         // Czy spacja jest wciśnięta
                {
                    sp=TRUE;         // Ustaw zmienną sp
                    scene=!scene;         // Zmień renderowaną scenę
                }

Poniższy kod sprawdza czy spacja została puszczona. Jeżeli tak to ustawiamy sp na FALSE, dzięki czemu program wie, że spacja nie jest przytrzymana. Ustawiając sp na FALSE, powyższy kod sprawdzi czy spacja została wciśnięta ponownie. Jeżeli tak to przełączy stan sp. I tak w kółko.

                if (!keys[' '])         // Czy spacja została puszczona
                {
                    sp=FALSE;         // Powiadom program, że tak się stało
                }

Kolejna część kodu jest taka sama. Spacja została zamieniona na M, ale zasada działania jest ta sama. Klawisz m odpowiada za sterowanie zmienną masking, która określa czy maskowanie jest włączone czy nie.

                if (keys['M'] && !mp)         // Czy M jest wciśnięty?
                {
                    mp=TRUE;         // Powiadom program, że M jest przytrzymywane
                    masking=!masking;         // Włącz/Wyłącz maskowanie
                }
                if (!keys['M'])         // M został puszczony
                {
                    mp=FALSE;         // Powiadom program o tym fakcie
                }

Tak jak w poprzednich lekcjach, sprawdź czy tytuł okna został poprawnie ustawiony.

                if (keys[VK_F1])         // Czy F1 jest wciśnięte?
                {
                    keys[VK_F1]=FALSE;         // Jeżeli tak to ustaw zmienną klawisza na FALSE
                    KillGLWindow();         // Zamknij okno naszego programu
                    fullscreen=!fullscreen;         // Przełącz się między pełnym ekranem i okienkiem
        // Ponownie stwórz okno
                    if (!CreateGLWindow("NeHe's Masking Tutorial",640,480,16,fullscreen))
                    {
                        return 0;         // Wyjdź, jeżeli nie udało się utworzyć okna
                    }
                }
            }
        }
    }
        // Shutdown
    KillGLWindow();         // Zamknij okno
    return (msg.wParam);         // Wyjdź z programu
}

Tworzenie maski nie jest trudne, ale zajmuje trochę czasu. Najlepszym sposobem jest, mając gotowy obrazek, załadować go do programu graficznego (np. irfanview) i zredukować go do odcieni szarości. Następnie zmień kontrast tak, aby szare piksele stały się czarne. Jeżeli stworzysz maskę i zobaczysz kwadratowe kształty wokół tekstury, to oznacza, że biel w masce nie jest wystarczająco biała (255 lub FFFFFF), albo czerń nie jest prawdziwie czarna (0 lub 000000). Poniżej możesz zobaczyć przykład maski i obrazka, który zostanie położony na maskę. Obraz może być dowolnego koloru, dopóki jego tło jest czarne. Maska musi mieć białe tło i czarną kopię obrazka.

To jest maska -> Maska To jest obraz -> Obraz

Eric Desrosiers wskazał, że możesz sprawdzić wartość każdego piksela w bitmapie podczas ładowania. Jeżeli chcesz aby piksel był przezroczysty daj mu wartość alpha równą 0. Dla pozostałych kolorów wystarczy nadać im wartość alpha równą 255. Ta metoda również działa, ale wymaga trochę dodatkowego pisania. Aktualna wersja lekcji jest prosta i wymaga niewiele pisania. Nie jestem ślepy na inne techniki, ale gdy piszę lekcje, staram się robić to tak, aby były łatwe do napisania i zrozumienia. Chciałem tylko zwrócić Twoją uwagę, że jest inna metoda osiągnięcia tego samego efektu.

W tej lekcji pokazałem Ci prostą, ale efektywną metodę rysowania części tekstury, bez użycia kanału alpha. Zwykłe mieszanie kolorów zazwyczaj wygląda źle (tekstury są przezroczyste lub nie). Teksturowanie z użyciem kanału alpha wymaga, aby obrazki obsługiwały ten kanał. Bitmapy są bardzo wygodne w użyciu, ale nie obsługują kanału alpha. Ten program pokazuje jak obejść te ograniczenia.

Rob Santa! Dzięki za pomysł i przykładowy kod. Nigdy nie słyszałem o tej sztuczce, aż do czasu, kiedy mi ją pokazałeś. Chciał, abym zwrócił uwagę, że mimo iż trik działa, to potrzebuje dwóch przejść. Jest to cios wymierzony w wydajność. Rob zaznacza, że dla skomplikowanych scen lepiej użyć tekstur z kanałem alpha.

Mam nadzieję, że ten tutorial Ci się podobał. Jeżeli masz jakieś problemy ze zrozumieniem lub znalazłeś błąd, to pisz do mnie śmiało [translator]Do autora należy pisać w języku angielskim. W przypadku błędów językowych należy pisać na tej stronie, w dziale służącym do zgłaszania błędów.[/translator]. Staram się tworzyć najlepsze, dostępne lekcje. Odzew z Twojej strony jest dla mnie niezwykle ważny.