W kolejnych programach korzystajacych z biblioteki OpenGL zajmiemy sie podstawowymi zagadnieniami, zwiazanymi ze scena 3D: obszarem renderingu, rzutowaniem i połozeniem obserwatora. Informacje tu zawarte stanowia podstawe do wszystkich nastepnych programów.
Obszar renderingu
W pierwszym programie obszar renderingu, który zajmował początkowo całe okno, nie był modyfikowany podczas zmiany rozmiarów tego okna. W efekcie jedyny element sceny 3D - kwadrat - zawsze znajdował sie w tym samym miejscu wzgledem lewego dolnego naroznika okna. W aplikacjach pracujacych w systemach okienkowych problem zmiany rozmiaru okna jest jednak tak powszechny, ze wymaga specjalnego potraktowania. Jednym z mozliwych sposobów jego rozwiazania jest dynamiczna
modyfikacja obszaru renderingu. Słuzy to tego funkcja:
void glViewport( GLint x, GLint y, GLsizei width, GLsizei height )
której parametry oznaczaja:
Domyślnie obszar renderingu zajmuje całe okno udostepnione dla aplikacji OpenGL. W naszym drugim programie w trakcie zmiany rozmiaru okna (funkcja Reshape) bedziemy modyfikowac obszar renderingu na dwa sposoby.
Pierwszy polega na objeciu obszarem renderingu całego dostępnego okna, drugi na takim wyborze okna renderingu aby okno zachowało pierwotny aspekt obrazu, czyli stosunek szerokosci do wysokosci. Oczywiście przy zastosowaniu pierwszej metody kwadrat bedzie zazwyczaj zdeformowany (patrz rysunki 1 i 2). Zmiany sposobu definiowania okna renderingu mozna dokonac w dowolnym momencie, poprzez wybranie odpowiedniej opcji w menu podrecznym.
W funkcji Menu została uzyta do tej pory nieopisana funkcja z biblioteki GLUT:
int glutGet( GLenum type )
O tym jakiego rodzaju informacje zwróci funkcja glutGet decyduje parametr type. W przykładowym programie sa to szerokosc i wysokosc okna, co odpowiada parametrom opisanym stałymi GLUT WINDOW WIDTH i GLUT WINDOW HEIGHT.
Warto jeszcze kilka słów poswiecic zagadnieniu aspektu obrazu. Typowe monitory komputerowe posiadaja aspekt 4:3, który jest zgodny z większością popularnych rozdzielczosci roboczych (np. 640 × 480, 800 × 600, 1.024 × 768, 1.600 × 1.200), ale inna popularna rozdzielczosc 1.280 × 1.024 pikseli odpowiada aspektowi 5:4.
Plik kwadrat2.cpp
#include <GL/glut.h>
#include <stdlib.h>
enum
{
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int Aspect = FULL_WINDOW;
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glColor3f( 1.0, 0.0, 0.0 );
glBegin( GL_POLYGON );
glVertex3f( 0.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glVertex3f( 1.0, 1.0, 0.0 );
glVertex3f( 1.0, 0.0, 0.0 );
glEnd();
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
if( Aspect == ASPECT_1_1 )
{
if( width > height )
glViewport(( width - height ) / 2, 0, height, height );
else
if( width < height )
glViewport( 0,( height - width ) / 2, width, width );
}
else
glViewport( 0, 0, width, height );
Display();
}
void Menu( int value )
{
switch( value )
{
case FULL_WINDOW:
Aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
Aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 400, 400 );
glutCreateWindow( "Kwadrat 2" );
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Obszar renderingu - całe okno", FULL_WINDOW );
glutAddMenuEntry( "Obszar renderingu - aspekt 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Obszar renderingu - cale okno", FULL_WINDOW );
glutAddMenuEntry( "Obszar renderingu - aspekt 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Rzutowanie prostokątne
Rzutowaniem okreslamy odwzorowanie zawartosci trójwymiarowej sceny graficznej na płaskim ekranie monitora. Biblioteka OpenGL oferuje standardowo dwie metody rzutowania: rzutowanie prostokatne i rzutowanie perspektywiczne. Domyslnie stosowane jest rzutowanie prostokatne. W rzutowaniu prostokatnym (lub ortogonalnym) proste rzutowania sa prostopadłe do rzutni, która jest reprezentowana przez obszar renderingu. Z rzutowaniem prostokatnym scisle zwiazane jest pojęcie bryły odcinania - prostopadłoscianu, który stanowi ograniczenie sceny 3D. Obiekty znajdujące sie poza bryła odcinania nie sa rysowane, a obiekty ja przecinajace rysowane sa tylko czesciowo. Rozmiar bryły odcinania w rzutowaniu prostokatnym okresla funkcja:
void glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far )
której parametry okreslaja współrzedne punktów przeciecia płaszczyzn tworzących bryłe odcinania z osiami układu współrzednych kartezjanskich. Płaszczyzny te opisane sa nastepujacymi równaniami:
x = right
x = left
y = top
y = bottm
z = ?near
z = ?far
Obszar renderingu zawiera sie w płaszczyznie o równaniu z = ?near. Połozenie poszczególnych płaszczyzn tworzacych bryłe odcinania przedstawia rysunek 3. Trzeba jednak wyraznie zwrócic uwage, ze poczatek układu współrzednych nie musi znajdowac sie wewnatrz bryły odcinania ? rozmiary i połozenie bryły ograniczone sa jedynie zakresem stosowanych liczb.
Domyslnie bryła odcinania ma postac szescianu o bokach równych 2, którego srodek pokrywa sie z poczatkiem układu współrzednych, co odpowiada wywołaniu funkcji glOrtho (-1,1,-1,1,-1,1). Os OZ jest prostopadła do płaszczyzny obszaru renderingu i przechodzi przez srodek tego obszaru. Dlatego rysowany w pierwszym i drugim programie kwadrat zajmował poczatkowo czwarta czesc okna.
Funkcja glOrtho tworzy macierz rzutu prostokatnego:
która jest nastepnie mnozona przez biezaca macierz i umieszczona na szczycie stosu z biezaca macierza. OpenGL zawiera kilka stosów macierzy, z których w przykładowym programie wykorzystamy stos macierzy rzutowania oraz stos macierzy modelowania. Wybór biezacej macierzy umozliwia funkcja:
void glMatrixMode( GLenum mode )
gdzie parametr mode moze przyjac jedna z wartosci:
Poniewaz poczatkowa wartosc wybranej macierzy jest nieokreslona, przed wywołaniem glOrtho nalezy biezacej macierzy przyporzadkowac macierz jednostkowa. Najłatwiej mozna to zrobic uzywajac funkcji:
void glLoadIdentity( void )
Analogiczne postepowanie dotyczy macierzy modelowania. Po jej wyborze przykładowym programie (plik szescian1.cpp) w funkcji Display macierzy modelowania takze przyporzadkowywana jest macierz jednostkowa. Jezeli renderowana scena jest dwuwymiarowa, do ustawienia parametrów bryły odcinania w rzutowaniu prostokatnym mozna uzyc funkcji z biblioteki GLU:
void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top )
której parametry left, right, bottom i top odpowiadaja parametrom funkcji glOrtho, a przednia (near) i tylna (far) płaszczyzna obcinania maja wartosci odpowiednio -1 i 1. W przykładowym programie poczatkowa bryła odcinania ma postac sześcianu o krawedziach długosci 4, a rysowana figura - takze szescian ? ma krawedzie o długosci 2. Centralne umieszczenie rysowanego szescianu w połaczeniu z zastosowanym rzutowaniem prostokatnym daje w efekcie kwadrat (patrz rysunek 4). Podobnie jak w poprzednim programie mozliwy jest wybór, czy scena ma byc rysowana z zachowaniem poczatkowego aspektu obrazu czy tez bez zachowania tej proporcji. Jednak w tym przypadku nie jest modyfikowany obszar renderingu ale współrzedne bryły odcinania (funkcja Reshape).
Plik szescian1.cpp
#include <GL/glut.h>
#include <stdlib.h>
enum
{
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int Aspect = FULL_WINDOW;
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glColor3f( 0.0, 0.0, 0.0 );
glBegin( GL_LINES );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glEnd();
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( Aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glOrtho( - 2.0, 2.0, - 2.0 * height / width, 2.0 * height / width, - 2.0, 2.0 );
else
if( width >= height && height > 0 )
glOrtho( - 2.0 * width / height, 2.0 * width / height, - 2.0, 2.0, - 2.0, 2.0 );
}
else
glOrtho( - 2.0, 2.0, - 2.0, 2.0, - 2.0, 2.0 );
Display();
}
void Menu( int value )
{
switch( value )
{
case FULL_WINDOW:
Aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
Aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 400, 400 );
#ifdef WIN32
glutCreateWindow( "Sześcian 1" );
#else
glutCreateWindow( "Szescian 1" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Rzutowanie perspektywiczne
Rzutowanie perspektywiczne daje bardziej realistyczne efekty niz rzutowanie prostokatne, stad jest szeroko stosowane np. w grach. Parametry bryły odcinania, która przy rzutowaniu perspektywicznym ma postac ostrosłupa scietego o wierzchołku znajdujacym sie w poczatku układu współrzędnych (patrz rysunek 5), okresla funkcja:
void glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far )
Parametry left, right, bottom i top wyznaczaja rozmiary górnej podstawy bryły odcinania (jest to obszar bezposrednio odwzorowywany na obszar renderingu), a near i far wyznaczaja połozenie odpowiednio górnej i dolnej podstawy ostrosłupa (przedniej i tylnej płaszczyzny odcinania), które zawieraja sie w płaszczyznach o równaniach: z = ?near i z = ?far. Parametry near i far musza miec wartosci dodatnie.
Macierz rzutowania perspektywicznego, tworzona przez funkcje glFrustum i mnozona przez aktualnie wybrana macierz, ma postac:
Warto zauwazyc, ze precyzja działania jeszcze przez nas nieużywanego z-bufora, zalezy od wartosci stosunku parametrów near i far:
Im wieksza wartosc r, tym mniej efektywne jest działanie z-bufora. Oczywiście near nigdy nie moze przyjac wartosci równej 0, bowiem przednia płaszczyzna odcinania przechodziła by wówczas przez srodek perspektywy. Alternatywny sposób okreslania rzutu perspektywicznego umozliwia funkcja z biblioteki GLU:
void gluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar )
gdzie parametr fovy okresla w stopniach kat widzenia obserwatora zawarty w płaszczyznie YZ (6 (top, 0, bottom)), a aspect jest stosunkiem szerokości do wysokosci przedniej płaszczyzny odcinania, czyli górnej podstawy ostrosłupa ograniczajacego scene 3D. Parametry zNear i zFar odpowiadaja parametrom near i far funkcji glFrustum. Macierz rzutowania perspektywicznego, tworzona przez funkcje gluPerspective
i mnozona przez aktualnie wybrana macierz, ma postac:
Wewnetrznie funkcja gluPerspective wykorzystuje do ustawienia macierzy rzutowania perspektywicznego funkcje glFrustum. Oto wzory przekształcenia parametrów funkcji gluPerspective na parametry glFrustum:
W kolejnym przykładowym programie (plik szescian2.cpp) do utworzenia macierzy rzutowania perspektywicznego wykorzystamy funkcje glFrustum. Przednia płaszczyzna odcinania bedzie miała takie same rozmiary jak w poprzednim programie. Zmianie ulegna natomiast współrzedne przedniej i tylnej płaszczyzny obcinania - poprzednio jedna z tych płaszczyzn miała wartosc ujemna, której nie akceptuje funkcja glFrustum. Rysowanym obiektem ponownie bedzie szescian ale próba narysowania go w tym samym miejscu jak w poprzednim programie spowoduje, ze będzie widoczna tylko jedna jego sciana. Wszystko dlatego, ze pozostałe sciany szescianu znajduja sie poza obszarem bryły odcinania. Mozliwe sa trzy sposoby rozwiazania tego problemu. Pierwszy polega na zmianie współrzędnych wierzchołków szescianu w taki sposób, aby szescian zmiescił sie w zmienionej bryle obcinania. Rozwiazanie to ma jedna zasadnicza wade ? wierzchołki szescianu trzeba bedzie modyfikowac przy kazdej zmianie parametrów sceny 3D. W przypadku jednego obiektu nie stanowi to specjalnego problemu, ale czyni pomysł niewykonalnym przy kazdej bardziej skomplikowanej scenie 3D.
Drugie, zastosowane w programie rozwiazanie, polega na przesunieciu wierzchołków szescianu o wektor [0, 0,?3], czyli o -3 jednostki wzdłuz osi OZ. Realizuje to funkcja glTranslatef, która wywoływana jest bezpośrednio po zainicjowaniu macierzy modelowania macierza jednostkowa (patrz funkcja Display). W efekcie otrzymamy szescian przedstawiony na rysunku 6. Nalezy dodac, ze taka metoda jest czesto stosowana praktyka. Obiekty 3D definiowane sa z róznymi współrzednymi, a nastepnie odpowiednio transformowane do docelowego połozenia w scenie 3D. Funkcje umożliwiające takie przekształcenia poznamy blizej w nastepnym odcinku kursu. Trzecia metoda jest modyfikacja połozenia obserwatora sceny 3D - zapoznamy się z ta technika jeszcze w tym odcinku.
Plik szescian2.cpp
#include <GL/glut.h>
#include <stdlib.h>
enum
{
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int Aspect = FULL_WINDOW;
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, - 3.0 );
glColor3f( 0.0, 0.0, 0.0 );
glBegin( GL_LINES );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glEnd();
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( Aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( - 2.0, 2.0, - 2.0 * height / width, 2.0 * height / width, 1.0, 5.0 );
else
if( width >= height && height > 0 )
glFrustum( - 2.0 * width / height, 2.0 * width / height, - 2.0, 2.0, 1.0, 5.0 );
}
else
glFrustum( - 2.0, 2.0, - 2.0, 2.0, 1.0, 5.0 );
Display();
}
void Menu( int value )
{
switch( value )
{
case FULL_WINDOW:
Aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
Aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 400, 400 );
#ifdef WIN32
glutCreateWindow( "Sześcian 2" );
#else
glutCreateWindow( "Sześcian 2" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Drugi program przedstawiajacy rzutowanie perspektywiczne (plik szescian3.cpp) stosuje funkcje gluPerspective. Aby jednak nie powielac rozwiązań z poprzedniego programu dodamy mechanizm pokazujacy jaki wpływ na wyglad obiektów 3D ma zmiana połozenie srodka perspektywy, realizowana
poprzez zmiane kata widzenia obserwatora (parametr fovy funkcji gluPerspective). W tym celu potrzebna jest obsługa klawiatury. Podstawowa funkcja obsługi klawiatury (w przykładowym programie jest to funkcja Keyboard) ma trzy parametry:
Aby obsługa klawiatury działała, w czesci głównej programu nalezy włączyć funkcje obsługi klawiatury wywołujac funkcje:
void glutKeyboardFunc( void( * func )( unsigned char key, int x, int y ) )
Poczatkowe okno programu Szescian 3 zawiera rysunek 7. Przyciskając klawisze ?+? i ?-? mozna modyfikowac kat patrzenia obserwatora, który poczatkowo wynosi 90 stopni.
Plik szescian3.cpp
#include <GL/glut.h>
#include <stdlib.h>
enum
{
EXIT
};
GLdouble fovy = 90;
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, - 3.0 );
glColor3f( 0.0, 0.0, 0.0 );
glBegin( GL_LINES );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glEnd();
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
GLdouble aspect = 1;
if( height > 0 )
aspect = width /( GLdouble ) height;
gluPerspective( fovy, aspect, 1.0, 5.0 );
Display();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' && fovy < 180 )
fovy++;
else
if( key == '-' && fovy > 0 )
fovy--;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
void Menu( int value )
{
switch( value )
{
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 400, 400 );
#ifdef WIN32
glutCreateWindow( "Sześcian 3" );
#else
glutCreateWindow( "Szescian 3" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Położenie obserwatora
Ostatnim z podstawowych elementów wymagajacych omówienia przy tworzeniu sceny 3D jest połozenie obserwatora, nazywane takze położeniem kamery lub ?oka?. Domyslnie obserwator w OpenGL połozony jest w poczatku układu współrzednych i skierowany jest w strone ujemnej półosi OZ. Obserwator jest tak zorientowany w przestrzeni, ze kierunek ?do góry? pokrywa sie z kierunkiem osi OY. Zasadniczo OpenGL nie umozliwia zmiany połozenia obserwatora. Wszystkie przekształcenia połozenia obserwatora faktycznie realizowane SA jako odpowiednie przekształcenia układu współrzednych. Aby jednak ułatwic prace zwiazane z definiowaniem tych przekształcen, biblioteka GLU zawiera funkcje gluLookAt, która pozwala na jednorazowe zdefiniowanie wszystkich parametrów opisujacych obserwatora:
void gluLookAt( GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery,
GLdouble centerz,
GLdouble upx, GLdouble upy, GLdouble upz )
Kolejne trójki parametrów funkcji gluLookAt oznaczaja:
Domyślne połozenie obserwatora odpowiada wywołaniu
gluLookat( 0.0, 0.0, 0.0, 0.0, 0.0, - 100.0, 0.0, 1.0, 0.0 )
W kolejnym przykładowym programie (plik szescian4.cpp) będziemy modyfikowac tylko współrzedne połozenia obserwatora. Przy niezmiennych współrzednych punktu, w którego kierunku patrzy obserwator, daje to ciekawy efekt obserwacji sceny z pewnej odległosci. Zmiana połozenia obserwatora realizowana jest w funkcjach Keyboard (przyciski ?+? i ?-?) oraz SpecialKeys (klawisze kursora).Warto zauwazyc, ze zmiany współrzednych obserwatora, które reprezentuja zmienne eyex, eyey i eyez, sa odwrotne niz mozna by sie spodziewac. Przykładowo nacisniecie strzałki w dół powoduje zwiekszenie o 0,1 zmiennej eyey, która okresla współrzedna Y połozenia obserwatora. Jest to spowodowane tym, ze macierz modelowania, modyfikowana przy wywołaniu funkcji gluLookAt, odgrywa podwójna role. Z jednej strony umozliwia przekształcenia współrzędnych obiektu (patrz poprzedni przykład), a z drugiej przekształcenia współrzednych obserwatora. Przykładowo, to co z punktu widzenia obiektu jest przesunieciem o wektor [1, 0, 0], dla obserwatora jest przesunieciem o wektor przeciwny tj. [?1, 0, 0]. Dobre poznanie opisanego mechanizmy wymaga eksperymentów, do których goraco zachecam Czytelników.
Do omówienia pozostała wprowadzona w ostatnim przykładzie obsługa klawiszy kursora. Jest ona realizowana odrebnie od obsługi przycisków, które reprezentowane sa bezposrednio przez kody ASCII (funkcja Keyboard). Funkcja obsługujaca klawisze kursora oraz przyciski funkcyjne (w przykładowym programie jest to funkcja SpecialKeys) ma trzy parametry:
key - kod przycisku; zwracana jest jedna z ponizszych wartości:
x, y - współrzedne kursora myszki w chwili nacisniecia przycisku klawiatury.
Podobnie jak w przypadku poprzedniej funkcji obsługujacej klawiature, w głównym programie nalezy właczyc obsługe klawiszy kursora i klawiszy funkcyjnych wywołujac funkcje:
void glutSpecialFunc( void( * func )( int key, int x, int y ) )
Rysunek 8 przedstawia przykładowe okno programu Szescian 4, którego kod zródłowy znajduje sie poniżej.
Plik szescian4.cpp
#include <GL/glut.h>
#include <stdlib.h>
enum
{
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int Aspect = FULL_WINDOW;
GLdouble eyex = 0;
GLdouble eyey = 0;
GLdouble eyez = 3;
GLdouble centerx = 0;
GLdouble centery = 0;
GLdouble centerz = - 100;
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
gluLookAt( eyex, eyey, eyez, centerx, centery, centerz, 0, 1, 0 );
glColor3f( 0.0, 0.0, 0.0 );
glBegin( GL_LINES );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( - 1.0, 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, 1.0 );
glVertex3f( - 1.0, - 1.0, 1.0 );
glVertex3f( 1.0, - 1.0, - 1.0 );
glVertex3f( - 1.0, - 1.0, - 1.0 );
glVertex3f( 1.0, 1.0, - 1.0 );
glVertex3f( - 1.0, 1.0, - 1.0 );
glEnd();
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( Aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( - 2.0, 2.0, - 2.0 * height / width, 2.0 * height / width, 1.0, 5.0 );
else
if( width >= height && height > 0 )
glFrustum( - 2.0 * width / height, 2.0 * width / height, - 2.0, 2.0, 1.0, 5.0 );
}
else
glFrustum( - 2.0, 2.0, - 2.0, 2.0, 1.0, 5.0 );
Display();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' )
eyez -= 0.1;
else
if( key == '-' )
eyez += 0.1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_LEFT:
eyex += 0.1;
break;
case GLUT_KEY_UP:
eyey -= 0.1;
break;
case GLUT_KEY_RIGHT:
eyex -= 0.1;
break;
case GLUT_KEY_DOWN:
eyey += 0.1;
break;
}
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
void Menu( int value )
{
switch( value )
{
case FULL_WINDOW:
Aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
Aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 400, 400 );
#ifdef WIN32
glutCreateWindow( "Sześcian 4" );
#else
glutCreateWindow( "Szescian 4" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}