Lekcja 23. Mapowanie sferyczne
Autor: Marcin 'Aklimx' Milewski
Oryginał: Sphere Mapping Quadrics (GB Schmick)
Źródła: http://nehe.gamedev.net/data/lessons/vc/lesson23.zip

Sferyczne mapowanie środowiska (ang. Sphere Environment Mapping) jest szybko metodą na dodanie odbicia na powierzchniach metalicznych. Chociaż nie jest ono tak dokładne jak w rzeczywistości, albo jak sześcienne mapowanie środowiska (ang. Cube Environment Mapping), to jest o niebo szybsze! Jako szablonu użyjemy lekcji osiemnastej o kwadrykach. Nie wykorzystamy jednak tych samych tekstur - użyjemy mapowania sferycznego i jednej tekstury na tło.

Zanim zaczniemy... The Red Book definiuje mapowanie sferyczne jako obraz sceny na metalowej kuli z nieskończoności do punktu centralnego. Takie coś jest niemożliwe w rzeczywistości. Najlepszą metodą jaką znalazłem na stworzenie dobrej mapy sferycznej bez używania 'rybiego oka' jest użycie programu Adobe Photoshop.

Tworzenie mapy sferycznej w Photoshop'ie

Potrzebujesz obrazka środowiska, które ma się odbijać w kuli. Otwórz obraz w Photoshop'ie i go zaznacz. Skopiuj obraz, po czym utwórz nowy PSD (format Photoshop'a) tego samego rozmiaru. Wklej obrazek w nowe miejsce. Robimy tak, żeby Photoshop mógł nałożyć swoje filtry. Zamiast kopiować, możesz wybrać menu rozwijane i wybrać tryb RGB. Wszystkie filtry powinny być wtedy dostępne.

Teraz musimy przeskalować obrazek, żeby jego wymiary były potęgą dwójki (zobacz lekcję 6.). Pod menu obrazka wybierz rozmiar obrazka, odznacz zachowanie proporcji i zmień jego rozmiary na poprawne. Jeżeli masz obrazek 100x90 to wybierz raczej 128x128 niż 64x64. Zmniejszając obrazek tracisz szczegóły.

Ostatnią rzeczą jaką musisz zrobić to wybrać menu filtrów, wybierz zniekształcenie i nałóż modyfikator sferyczny. Powinieneś zobaczyć, że środek obrazka wybuchł jak balon, teraz w normalnej mapie sferycznej obszar zewnętrzny będzie czarny, ale to nie ma znaczenia. Zapisz kopię obrazka jako bitmapę i zaczynamy kodzić!

Nie dodajemy żadnej zmiennej globalnej tym razem, a jedynie modyfikujemy liczbę tekstur na 6.

GLuint texture[6];         // Miejsce na 6 tekstur
    

Teraz zmodyfikujemy funkcję LoadGLTextures() tak, że możemy załadować 2 tekstury i stworzyć 3 filtry. Wykonujemy dwa obiegi pętli tworząc 3 tekstury z różnymi opcjami filtrowania. Prawie cały kod jest nowy lub zmodyfikowany.

int LoadGLTextures()         // Załaduj bitmapy i stwórz tekstury
{
    int Status=FALSE;         // Status (FALSE oznacza niepowodzenie)
    AUX_RGBImageRec *TextureImage[2];         // Stwórz miejsce na tekstury
    memset(TextureImage,0,sizeof(void *)*2);         // Wyzeruj wskaźnik
        // Załaduj bitmapy, jeżeli ich nie ma to wyjdź
    if ((TextureImage[0]=LoadBMP("Data/BG.bmp")) &&         // Tekstura tła
     (TextureImage[1]=LoadBMP("Data/Reflect.bmp")))         // Tekstura odbicia (Spheremap)
    {
        Status=TRUE;         // Ustaw Status na TRUE
        glGenTextures(6, &texture[0]);         // Stwórz trzy tekstury (po dwa obrazy)
        for (int loop=0; loop<2; loop++)
        {
        // Tekstura filtrowana liniowo (ang. Nearest)
            glBindTexture(GL_TEXTURE_2D, texture[loop]);         // Generuj tekstury 0 i 1
            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[loop]->sizeX, TextureImage[loop]->sizeY,
                0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
             Tekstura filtrowana bilinearnie (ang. Linear)
            glBindTexture(GL_TEXTURE_2D, texture[loop+2]);         // Generuj tekstury 2, 3 i 4
            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);
        // Tekstury filtrowane trilinearnie (ang. Mipmap)
            glBindTexture(GL_TEXTURE_2D, texture[loop+4]);         // Generuj tekstury 4 i 5
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
            gluBuild2DMipmaps(GL_TEXTURE_2D, 3,
                TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
                GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
        }
        for (loop=0; loop<2; loop++)
        {
            if (TextureImage[loop])         // Jeżeli tekstura istnieje
            {
                if (TextureImage[loop]->data)         // Jeśli obraz tekstury istnieje
                {
                    free(TextureImage[loop]->data);         // Zwolnij pamięć obrazka tekstury
                }
                free(TextureImage[loop]);         // Zwolnij strukturę obrazka
            }
        }
    }
    return Status;         // Zwróć Status
}
    

Zmodyfikujemy kod rysujący sześcian. Zamiast używać -1.0 i 1.0 dla normalnych, użyjemy 0.5 i -0.5. Zmieniając te wartości możesz przybliżać i oddalać odbicie. Im większa normalna tym odbijany obrazek będzie większy i może gorzej wyglądać. Przez ustawienie normalnych na 0.5 i -0.5 zapobiegamy temu zjawisku, bo odbijany obraz jest dalej. Ustawienie zbyt małych normalnych stworzy obraz mniej wyraźny.

GLvoid glDrawCube()
{
glBegin(GL_QUADS);
        // Przednia ściana
glNormal3f( 0.0f, 0.0f, 0.5f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
        // Tylna ściana
glNormal3f( 0.0f, 0.0f,-0.5f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
        // Górna ściana
glNormal3f( 0.0f, 0.5f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
        // Dolna ściana
glNormal3f( 0.0f,-0.5f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
        // Prawa ściana
glNormal3f( 0.5f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
        // Lewa ściana
glNormal3f(-0.5f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
}
    

Teraz, w InitGL(), dodajemy dwa nowe wywołania funkcji. Ustawiają one sposób generowania współrzędnych tekstury na mapowanie sferyczne. Współrzędne tekstury S, T, R i Q odpowiadają współrzędnym x, y, z, w. Jeżeli używasz tekstur jednowymiarowych (1D) to użyjesz współrzędnej S. Jeżeli dwuwymiarowych to S i T.

Poniższy kod mówi OpenGL, żeby automatycznie wygenerował współrzędne tekstury bazują na formule mapowania sferycznego. Współrzędny R i Q są zwykle ignorowane. Współrzędna Q może być wykorzystana do zaawansowanego teksturowania korzystając z rozszerzeń. Współrzędna R może okazać się przydatna kiedy mapowanie tekstur 3D zostało dodane do OpenGL, ale na razie zignoruj koordynaty R i Q. Współrzędna S działa poziomo, a współrzędna T pionowo wzdłuż ściany naszego wielokąta

        // Ustaw sposób generowania współrzędnej S na mapowanie sferyczne ( NOWE )
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
        // Ustaw sposób generowania współrzędnej T na mapowanie sferyczne ( NOWE )
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    

Prawie skończyliśmy! Teraz wszystko co musimy zrobić to ustawić rendering. Usunąłem pare kwadryk, ponieważ nie chciały współpracować z mapowaniem środowiska. Pierwsza rzecz, jaką robimy to włączenie generowania tekstury. Następnie wybieramy odbijaną teksturę (mapę sferyczną) i rysujemy nasze obiekty. Po tym wyłączamy generowanie tekstur, bo wszystko zostanie wymapowane sferycznie :) Wyłączamy mapowanie sferyczne przed narysowaniem tła sceny. Zauważysz pewnie, że bindowanie tekstury wygląda trochę bardziej złożenie. Wszystko co robimy to wybór filtra, którego użyjemy podczas rysowania map sferycznych lub tła.

int DrawGLScene(GLvoid)         // Tu wszystko rysujemy
{
        // Wyczyść ekran i bufor głębi
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();         // Zresetuj widok
glTranslatef(0.0f,0.0f,z);
glEnable(GL_TEXTURE_GEN_S);         // Włącz generowanie wsp. S tekstury ( NOWE )
glEnable(GL_TEXTURE_GEN_T);         // Włącz generowanie wsp. T tekstury ( NOWE )
        // Wybierz mapowanie sferyczne
glBindTexture(GL_TEXTURE_2D, texture[filter+(filter+1)]);
glPushMatrix();
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
switch(object)
{
case 0:
glDrawCube();
break;
case 1:
glTranslatef(0.0f,0.0f,-1.5f);         // Środek cylindra
        // Cylinder - promień i wysokość
gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);
break;
case 2:
        // Sfera - promień
gluSphere(quadratic,1.3f,32,32);
break;
case 3:
glTranslatef(0.0f,0.0f,-1.5f);         // Środek stożka
        // Stożek - dolny promień, wysokość
gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);
break;
};
glPopMatrix();
glDisable(GL_TEXTURE_GEN_S);         // Wyłącz generowanie tekstury ( NOWE )
glDisable(GL_TEXTURE_GEN_T);         // Wyłącz generowanie tekstury ( NOWE )
        // Ustaw teksturę tła ( NOWE )
glBindTexture(GL_TEXTURE_2D, texture[filter*2]);
glPushMatrix();
glTranslatef(0.0f, 0.0f, -24.0f);
glBegin(GL_QUADS);
glNormal3f( 0.0f, 0.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-13.3f, -10.0f, 10.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 13.3f, -10.0f, 10.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 13.3f, 10.0f, 10.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-13.3f, 10.0f, 10.0f);
glEnd();
glPopMatrix();
xrot+=xspeed;
yrot+=yspeed;
return TRUE;         // Idź dalej
}
    

Ostatnia rzecz jaką robimy to uaktualnienie sekcji ze spacją, żeby zmieniała mapowane sferycznie kwadryki. (Usunęliśmy dysk)

                if (keys[' '] && !sp)
                {
                    sp=TRUE;
                    object++;
                    if(object>3)
                    {
                        object=0;
                    }
                }
    

Podczas zamykania programu koniecznie trzeba usunąć kwadryki, żeby nie doszło do wycieków pamięci

        // Zakończ
    gluDeleteQuadric(quadratic);
    KillGLWindow();         // Zabij okno
    return (msg.wParam);         // Wyjdź z programu
}
    

Skończyliśmy! Teraz umiesz zrobić kilka imponujących rzeczy z mapowaniem środowiska jak np. tworzenie prawie dokładnego odbicia pokoju! Planowałem pokazać sześcienne mapowanie środowiska, ale moja karta graficzna go nie wspiera :( może za miesiąc, jak kupię GeForce'a 2 :) Jeżeli coś w tej lekcji jest niedokładnie wyjaśnione to napisz do mnie albo do NeHe.

Dziękuję i życzę powodzenia!

Odwiedź moją stronę: http://www.tiptup.com/