Model oświetlenia zastosowany w bibliotece OpenGL opiera się na trzech rodzajach światła. Pierwszy rodzaj to światło otaczające (ang. ambient light), które nie pochodzi z żadnego konkretnego kierunku i równomiernie oświetla wszystkie elementy sceny 3D. Drugim rodzajem światła jest światło rozproszone (ang. diffuse light), które pada na obiekt z określonego kierunku, ale jest na nim rozpraszane we wszystkich kierunkach. Ostatnim rodzajem światła obsługiwanego przez OpenGL jest światło odbite (ang. specular light), zwane także światłem kierunkowym, które pada na obiekt w określonym kierunku i odbijane jest także w ściśle określonym kierunku.
W bibliotece OpenGL ze światłem ściśle związane jest pojęcia materiałów określające właściwości oświetlanych obiektów. Właściwości materiału, poza reakcją na opisane wyżej trzy rodzaje światła, uwzględniają także możliwość emitowania światła.
Włączanie oświetlenia
Domyślnie biblioteka OpenGL nie wykonuje obliczeń związanych z oświetleniem. Uruchomienie oświetlenia wymaga wywołania funkcji glEnable z parametrem
GL_LIGHTING. Ponadto należy włączyć bufor głębokości. Nie jest to wprawdzie konieczne ale znacząco wpływa na realizm generowanej sceny 3D. Oczywiście wyłączenie oświetlenia sprowadza się do wywołania funkcji glDisable z parametrem
GL_LIGHTING.
Włączanie źródła światła
Specyfikacja OpenGL określa, że minimalną ilość źródeł światła, którą musi obsługiwać każda implementacja biblioteki wynosi 8. Każde źródło światła ma swój unikatowy numer oznaczony stałą
GL_LIGHTi, gdzie i zawiera się w przedziale [0, 7]. Ilość źródeł światła obsługiwanych przez daną implementację biblioteki OpenGL zwraca wywołanie funkcji glGetIntegerv z parametrem
GL_MAX_LIGHTS. Alternatywnie numery źródeł światła można określić przy pomocy wyrażenia:
GL_LIGHTi =
GL_LIGHT0 + i. Jest to szczególnie przydatne przy źródłach światła o numerach wyższych niż 7, bowiem pliki nagłówkowe biblioteki OpenGL standardowo nie zawierają stosownych stałych. Zasada ta odnosi się do każdej funkcji korzystającej z numeru źródła światła.
Poszczególne źródła światła włączamy i wyłączamy przy użyciu funkcji glEnable/glDisable z parametrem określającym numer źródła światła.
Parametry źródła światła
Parametry każdego ze źródła światła można modyfikować niezależnie od pozostałych źródeł światła. Służą do tego funkcje z grupy glLight, które można podzielić na dwie podgrupy. Pierwsza pozwala na modyfikację parametrów źródła światła określanych pojedynczą wartością; druga grupa funkcji przyjmuje wskaźniki na tablice z wartościami parametrów, przy czym można ich także używać do modyfikacji parametrów źródła światła określanych pojedynczą wartością.
Zmianę parametru źródła światła określanego pojedynczą wartością umożliwiają funkcje:
void glLightf( GLenum light, GLenum pname, GLfloat param )
void glLighti( GLenum light, GLenum pname, GLint param )
Natomiast zmianę parametru źródła światła określanego tablicą wartości umożliwiają funkcje:
void glLightfv( GLenum light, GLenum pname, const GLfloat * params )
void glLightiv( GLenum light, GLenum pname, const GLint * params )
Parametr light określa numer źródła światła, którego parametry chcemy zmienić. Numer źródła światła określają omówione już stałe
GL_LIGHTi. Parametr pname określa parametr źródła światła, który chcemy zmodyfikować. Dopuszczalne są następujące wartości tego parametru:
Wspomniane wyżej reflektory to po prostu światło kierunkowe, którego promienie rozchodzą się w przestrzeni ograniczonej stożkiem o wierzchołku znajdującym się w źródle światła. Połowę kąta rozwarcia stożka światła określa parametr
GL_SPOT_CUTOFF (patrz rysunek 1). Wszystkie obiekty sceny znajdujące się poza stożkiem światła reflektora pozostają nieoświetlone jego światłem.
Parametr
GL_SPOT_EXPONENT umożliwia regulację stopnia skupienia światła reflektora. Jest to wykładnik potęgi wyrażenia stanowiącego kosinus kąta pomiędzy kierunkiem padania światła, a kierunkiem od źródła światła do wierzchołka oświetlanego elementu sceny. Domyślna wartość 0 oznacza brak tłumienia kątowego, czyli równomierne rozchodzenie się światła we wszystkich kierunkach.
Trzy ostatnie parametry źródła światła określają współczynnik tłumienia światła dany wzorem:
gdzie d oznacza odległość wierzchołka od źródła światła, a stałe ac, al i aq odpowiednio stały (
GL_CONSTANT_ATTENUATION), liniowy (
GL_LINEAR_ATTENUATION) i kwadratowy (
GL_QUADRATIC_ATTENUATION) współczynnik tłumienia. Przy domyślnych ustawieniach parametrów źródeł światła wartość współczynnika tłumienia wynosi 1, co oznacza, że intensywność strumienie światła nie zależy od odległości pomiędzy jego źródłem a wierzchołkiem oświetlanego obiektu.
Zestawienie domyślnych wartości parametrów źródła światła
GL_LIGHT0 zawiera tabela 1. Te same dane dla pozostałych źródeł światła przedstawia tabela 2.
Tabela 1: Domyślne parametry źródła światła GL_LIGHT0
Tabela 2: Domyślne parametry źródeł światła GL_LIGHT1 - GL_LIGHT7
Jak widzimy na powyższych tabelach światło
GL_LIGHT0 jest światłem białym kierunkowym położonym w punkcie o współrzędnych (0, 0, 1). Światło skierowane jest zgodnie z kierunkiem wyznaczonym przez wektor o współrzędnych [0, 0, −1]. Pomimo kierunkowego charakteru stożek światła GL - LIGHT0 nie jest w żaden sposób ograniczony (parametr
GL_SPOT_CUTOFF ma wartość 180◦ ) i światło rozchodzi się każdym kierunku. Przy domyślnych początkowych ustawieniach układu współrzędnych oraz bryły odcinania źródło światła
GL_LIGHT0 znajduje się na środku płaszczyzny monitora (okna) i jest skierowane prostopadle w głąb monitora.
Źródła światła
GL_LIGHT1 -
GL_LIGHT7 różną się do światła
GL_LIGHT0 tylko barwą światła rozproszonego i odbitego.
Parametry modelu oświetlenia
Poza zdefiniowaniem właściwości poszczególnych źródeł światła do oświetlenia sceny w bibliotece OpenGL trzeba jeszcze określić parametry stosowanego modelu oświetlenia. Służą do tego funkcje z grupy glLightModel:
void glLightModelf( GLenum pname, GLfloat param )
void glLightModeli( GLenum pname, GLint param )
void glLightModelfv( GLenum pname, const GLfloat * params )
void glLightModeliv( GLenum pname, const GLint * params )
Funkcje te, podobnie jak funkcje z grupy glLight, występują w dwóch wersjach różniących się sposobem przekazywania parametrów.
Parametr pname określa właściwość modelu oświetlenia, który chcemy zmienić. Dopuszczalne są następujące wartości tego parametru:
Ustawienie wartości składowych RGBA globalnego światła otaczającego wymaga oczywiście użycia funkcji glLightModelfv lub glLightModeliv. Parametr
GL_LIGHT_MODEL_COLOR_CONTROL, będący jednym z elementów techniki nazywanej drugorzędnym kolorem odbicia (ang. secondary specular color), wprowadzono w wersji 1.2 biblioteki OpenGL. Wcześniej technika ta opisana była w rozszerzeniu EXT separate specular color. Zostanie to bliżej zaprezentowane w jednym z kolejnych odcinków kursu OpenGL.
Tabela 3 przedstawia wartości domyślne parametrów modelu oświetlenia w bibliotece OpenGL.
Tabela 3: Domyślne parametry modelu oświetlenia
Materiały
Integralnym elementem modelu oświetlenia przyjętego w bibliotece OpenGL jest opis sposobu zachowania się powierzchni obiektów na poszczególne rodzaje światła, czyli opis właściwości materiałów. Modyfikowanie właściwości materiałów umożliwiają funkcje z grupy glMaterial:
void glMaterialf( GLenum face, GLenum pname, GLfloat para )
void glMateriali( GLenum face, GLenum pname, GLint para )
void glMaterialfv( GLenum face, GLenum pname, const GLfloat * params )
void glMaterialiv( GLenum face, GLenum pname, const GLint * params )
Jak Czytelnik zauważył także i te funkcje występują w dwóch wersjach różniących się sposobem przekazywania parametrów.
Parametr face ustala, którą stronę wielokąta dotyczy modyfikowany parametr. Dopuszczalne są poznane już wcześniej wartości:
GL_FRONT - przednia strona wielokąta,
GL_BACK - tylna strona wielokąta i
GL_FRONT_AND_BACK - obie strony wielokąta.
Parametr pname określa zmienianą wartość parametru materiału. Dopuszczalne są poniższe wartości:
Zestawienie domyślnych własności materiałów zawiera tabela 4. Zwróćmy uwagę, że parametr
GL_AMBIENT_AND_DIFFUSE nie posiada domyślnych wartości.
Tabela 4: Domyślne parametry materiałów
Największy wpływ na kolor obiektu mają składowe
GL_DIFFUSE.
Dobranie materiału o odpowiednich właściwościach nie jest zadaniem łatwym, choćby z uwagi na dużą ilość niezależnych od siebie parametrów. Podamy jednak kilka praktycznych rad przy dobieraniu materiałów. Przy materiałach matowych duże wartości powinny mieć składowe
GL_DIFFUSE przy małych wartościach składowych
GL_SPECULAR. Materiały imitujące tworzywa sztuczne także mają wysokie wartości składowych
GL_DIFFUSE przy neutralnych (białych) składowych
GL_SPECULAR. Powierzchnie metaliczne to małe wartości składowych
GL_DIFFUSE i jednocześnie duże wartości składowych
GL_SPECULAR. Stosownie do rodzaju powierzchni trzeba także dobrać odpowiednią wartość współczynnika
GL_SHININESS.
Przykładowe definicje materiałów Czytelnik znajdzie w jednym z programów testowych.
Śledzenie kolorów
Biblioteka OpenGL umożliwia także definiowanie parametrów materiałów na podstawie kolorów wierzchołków określanych, przypomnijmy, przy pomocy funkcji z grupy glColor. Technika ta nazywana jest śledzeniem kolorów (ang. color tracking). Wybór parametrów materiału, które będą określane poprzez kolory wierzchołków, określa funkcja:
void glColorMaterial( GLenum face, GLenum mode )
Parametr face ustala, którą stronę wielokąta dotyczy modyfikowany parametr. Dopuszczalne są wszystkie opisane wcześniej wartości:
GL_FRONT,
GL_BACK i
GL_FRONT_AND_BACK. Wartością domyślną jest
GL_FRONT_AND_BACK.
Parametr mode wskazuje, która z właściwości materiału ma być definiowana zgodnie z bieżącym kolorem wierzchołka. Dopuszczalne są niemal wszystkie opisane wcześniej własności materiałów:
GL_EMISSION,
GL_AMBIENT,
GL_DIFFUSE,
GL_SPECULAR i
GL_AMBIENT_AND_DIFFUSE. Wartością domyślną jest
GL_AMBIENT_AND_DIFFUSE.
Śledzenie kolorów jest domyślne wyłączone, stąd przed użyciem funkcji glColorMaterial trzeba wywołać funkcję glEnable z parametrem
GL_COLOR_MATERIAL. Oczywiście wyłączenie śledzenia kolorów wymaga wywołania funkcji glDisable z parametrem
GL_COLOR_MATERIAL.
Używanie techniki śledzenie kolorów jest szczególnie użyteczne, gdy na każdy wierzchołek obiektu zmieniamy tylko jedną właściwość materiału.
Wektory normalne
Do prawidłowego generowania efektów oświetlenia, a w szczególności określenia orientacji wierzchołków obiektów względem źródeł światła, biblioteka OpenGL wymaga definiowania wektorów normalnych (prostopadłych). Ponieważ obliczenia związane z oświetleniem OpenGL wykonuje dla każdego wierzchołka, stąd każdy wierzchołek ma przypisany swój wektor normalny.
Wektor normalny definiują funkcje z grupy glNormal3, które dzielą się na dwie grupy. Pierwsza grupa posiada trzy parametry określające składowe wektora normalnego:
void glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz )
void glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz )
void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )
void glNormal3i( GLint nx, GLint ny, GLint nz )
void glNormal3s( GLshort nx, GLshort ny, GLshort nz )
Druga grupa funkcji posiada jeden parametr będący wskaźnikiem na tablicę ze składowymi wektora normalnego:
void glNormal3bv( const GLbyte * v )
void glNormal3dv( const GLdouble * v )
void glNormal3fv( const GLfloat * v )
void glNormal3iv( const GLint * v )
void glNormal3sv( const GLshort * v )
Podobnie jak w przypadku kolorów czy własności materiału dany wektor normalny obowiązuje do czasu zdefiniowania kolejnego. W szczególności można użyć jednego wektora normalnego do grupy wierzchołków opisujących płaski obiekt. Początkowa wartość wektora normalnego wynosi [0, 0, 1].
Do wyznaczania wektora normalnego można użyć iloczynu wektorowego. Przy obliczeniach wykorzystujemy wektory utworzone z krawędzi ściany obiektu, pamiętając o zachowaniu odpowiedniej orientacji. Jeżeli ściany mają sprawiać wrażenie płaskich, wówczas obliczamy wektor normalny tylko dla pierwszego wierzchołka. Pozostałe wierzchołki będą korzystały z tego samego wektora.
Jeżeli natomiast zależy nam na uzyskaniu wrażenia gładkości oświetlanego obiektu wykorzystujemy technikę uśredniania wektorów normalnych. Polega ona na obliczaniu wektora normalnego dla każdego wierzchołka obiektu 3D. Tym razem wektor ten obliczamy nie na podstawie krawędzi jednej ściany ale wszystkich krawędzi wychodzących z wierzchołka. Można do tego celu wykorzystać złożenie (sumę) wektorów normalnych wszystkich ścian zawierających dany wierzchołek. Technika ta pozwala na zwiększenie realizmu sceny bez radykalnego zwiększania złożoności renderowanych obiektów.
Aby obliczenia oświetlenia były wykonywane poprawne wektor normalny musi mieć długość jednostkową, inaczej mówiąc musi być znormalizowany (jednostkowy). Normalizację wektorów można wykonać programowo, przy czym należy uwzględniać przekształcenia macierzy modelowania, które modyfikują także wektory normalne i mogą przy okazji zmienić ich długość. W szczególności dotyczy to operacji skalowania macierzy modelowania.
Drugim sposobem normalizacji wektorów normalnych jest skorzystanie z mechanizmu udostępnianego przez OpenGL, który automatycznie normalizuje wektory normalne. W tym celu trzeba wywołać funkcję glEnable z parametrem
GL_NORMALIZE. Domyślnie mechanizm ten jest nieaktywny. Wyłączenie automatycznej normalizacji wektorów normalnych wymaga wywołania funkcji glDisable z parametrem
GL_NORMALIZE.
Bardzo pomocnym przy stosowaniu wektorów normalnych jest wprowadzony w wersji 1.2 biblioteki OpenGL mechanizm automatycznego skalowania jednostkowych wektorów normalnych. Przy jego zastosowaniu normalizacja wektora normalnego jest wykonywana tylko raz, a późniejsze jego modyfikacje wykonywane są wyłącznie przy skalowaniu macierzy modelowania, które, przypomnijmy, zmienia długość wektora normalnego.
Technikę automatycznego skalowania jednostkowych wektorów normalnych wprowadzono wcześniej w rozszerzeniu EXT rescale normal, a jej wykorzystanie sprowadza się do wywołania funkcji glEnable z parametrem
GL_RESCALE_NORMAL (lub
GL_RESCALE_NORMAL_EXT). Wyłączenie automatycznego skalowania jednostkowych wektorów normalnych wymaga wywołania funkcji glDisable z parametrem
GL_RESCALE_NORMAL lub
GL_RESCALE_NORMAL_EXT.
Odczyt parametrów źródła światła i materiałów
Odczyt ustawień wybranego źródła światła umożliwiają funkcje z grupy glGetLight:
void glGetLightfv( GLenum light, GLenum pname, GLfloat * params )
void glGetLightiv( GLenum light, GLenum pname, GLint * params )
Znaczenie parametrów light i pname jest takie same jak analogicznych parametrów funkcji z grupy glLight. Wartości pobieranych ustawień źródła światła umieszczane są w tablicy, do której wskaźnik należy podać w parametrze params.
Analogicznie wygląda pobieranie ustawień bieżącego materiału, do czego służą funkcje z grupy glGetMaterial:
void glGetMaterialfv( GLenum face, GLenum pname, GLfloat * params )
void glGetMaterialiv( GLenum face, GLenum pname, GLint * params )
Parametry face i pname są odpowiednikami parametrów funkcji z grupy glMaterial, a wartości pobieranych ustawień bieżącego materiału umieszczane są w tablicy wskazywanej przez parametr params.
Oczywiście przy korzystaniu z powyższych funkcji program musi zapewnić wystarczającą ilość miejsca w tablicy, która będzie zawierała pobierane parametry źródła światła lub materiału.
Programy przykładowe
Pierwszy przykładowy program (patrz plik globalne swiatlo otaczajace.cpp) oświetla scenę wyłącznie przy pomocy globalnego światła otaczającego i pozwala na modyfikację wartości poszczególnych składowych RGB tego światła. Jak już powiedzieliśmy na wstępie światło otaczające nie pochodzi z żadnego konkretnego kierunku i równomiernie oświetla wszystkie elementy sceny 3D. Przedstawione wcześniej funkcje z grupy glLightModel umożliwiają modyfikację globalnego światła otaczającego dla całej sceny 3D.
Pozwala to, przy braku innych źródeł światła, na równomierne oświetlenie całej sceny.
Wartości początkowe składowych tego światła przyjęte w programie odpowiadają wartościom domyślnym przyjmowanym przez bibliotekę OpenGL. Początkowy wygląd ścian: czerwonej, niebieskiej i zielonej sześcianu RGB przedstawia rysunek 2. Jak widać domyślne światło otaczające jest stosunkowo ciemne. Dla porównania rysunek 3 przedstawia te same ściany sześcianu ale przy maksymalnych wartościach składowych RGB globalnego światła otaczającego. Natomiast efekty oświetlenia obiektu sceny globalnych światłem otaczającym z wybranymi składowymi RGB o wartościach ujemnych przedstawiają rysunki 4 i 5.
Warto także zwrócić uwagę, że przy definiowaniu sześcianu nie były wykorzystywane wektory normalne. Nie ma takiej potrzeby, bowiem jak wcześniej napisaliśmy, globalne światło otaczające nie pochodzi z żadnego konkretnego kierunku.
W ramach ćwiczeń Czytelnik może łatwo sprawdzić, że wygląd sześcianu RGB przedstawiony na rysunku 3, odpowiada wyglądowi tego samego sześcianu ale przy wyłączonych efektach oświetlenia. Oznacza to, że domyślnie biblioteka OpenGL faktycznie nie wykonuje żadnych obliczeń związanych z oświetleniem, a obiekty są bezpośrednio rasteryzowane zgodnie z wartościami składowych RGB.
Plik globalne_swiatlo_otaczajace.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "colors.h"
enum
{
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat ambient_light[ 4 ] =
{
0.2, 0.2, 0.2, 1.0
};
void DrawString( GLfloat x, GLfloat y, char * string )
{
glRasterPos2f( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0, 0 );
glRotatef( rotatey, 0, 1.0, 0 );
glScalef( 1.15, 1.15, 1.15 );
glEnable( GL_LIGHTING );
glLightModelfv( GL_LIGHT_MODEL_AMBIENT, ambient_light );
glEnable( GL_COLOR_MATERIAL );
glColorMaterial( GL_FRONT, GL_AMBIENT );
glEnable( GL_DEPTH_TEST );
glBegin( GL_TRIANGLES );
glColor3fv( Red );
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 );
glColor3fv( Magenta );
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 );
glColor3fv( Cyan );
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 );
glColor3fv( Lime );
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 );
glColor3fv( Blue );
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 );
glColor3fv( Yellow );
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();
glDisable( GL_LIGHTING );
glDisable( GL_COLOR_MATERIAL );
char string[ 100 ];
GLfloat rgba[ 4 ];
glColor3fv( Black );
glGetFloatv( GL_LIGHT_MODEL_AMBIENT, rgba );
sprintf( string, "AMBIENT: R=%f G=%f B=%f A=%f", rgba[ 0 ], rgba[ 1 ], rgba[ 2 ], rgba[ 3 ] );
glLoadIdentity();
glTranslatef( 0, 0, - near );
DrawString( left, bottom, string );
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( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
Display();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == 'R' && ambient_light[ 0 ] < 1.0 )
ambient_light[ 0 ] += 0.05;
else
if( key == 'r' && ambient_light[ 0 ] > - 1.0 )
ambient_light[ 0 ] -= 0.05;
if( key == 'G' && ambient_light[ 1 ] < 1.0 )
ambient_light[ 1 ] += 0.05;
else
if( key == 'g' && ambient_light[ 1 ] > - 1.0 )
ambient_light[ 1 ] -= 0.05;
if( key == 'B' && ambient_light[ 2 ] < 1.0 )
ambient_light[ 2 ] += 0.05;
else
if( key == 'b' && ambient_light[ 2 ] > - 1.0 )
ambient_light[ 2 ] -= 0.05;
Display();
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_LEFT:
rotatey -= 1;
break;
case GLUT_KEY_UP:
rotatex -= 1;
break;
case GLUT_KEY_RIGHT:
rotatey += 1;
break;
case GLUT_KEY_DOWN:
rotatex += 1;
break;
}
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
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 | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Globalne światło otaczające" );
#else
glutCreateWindow( "Globalne swiatlo otaczajace" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Drugi przykładowy program (plik materialy.cpp) prezentuje sposób wykorzystania materiałów. Definicje materiałów, umieszczone w pliku materials.h, pochodzą pochodzą z pracy „Advanced Graphics Programming Techniques Using OpenGL” (SIGGRAPH 1998), która dostępna jest pod adresem: http://www.opengl.org.
Do prezentacji materiałów wykorzystamy kwadryki, czyli powierzchnie stopnia drugiego, udostępniane przez bibliotekę pomocniczą GLU. Nową kwadrykę tworzymy wywołując funkcję:
GLUquadric * gluNewQuadric()
Funkcja zwraca wskaźnik do struktury, lub klasy w przypadku użycia języka C++, typu GLUquadric, który jest później używany przy wszelkich operacjach na kwadrykach. Warto zauważyć, że specyfikacja biblioteki GLU używa przy opisie kwadryk równorzędnego typu GLUquadricObj. Po zakończeniu używania kwadryki trzeba zwolnić przydzieloną jej pamięć wywołując funkcję:
void gluDeleteQuadric( GLUquadric * state )
Biblioteka GLU udostępnia cztery rodzaje kwadryk. Są to: kula (sfera), cylinder, dysk i niepełny dysk, przy czym dwie ostatnie są figurami płaskimi. Kulę tworzy wywołanie funkcji:
void gluSphere( GLUquadric * quadobj,
GLdouble radius, GLint slices, GLint stacks )
której parametry radius, slices i stacks maję te same znaczenie jak parametry opisywanych wcześniej funkcji glutWireSphere i glutSolidSphere. Parametr quadobj to oczywiście wskaźnik na strukturę (klasę) GLUquadric, zwrócony przez funkcję gluNewQuadric przy tworzeniu kwadryki.
Cylinder tworzony jest poprzez wywołanie funkcji:
void gluCylinder( GLUquadric * quadobj,
GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks )
Parametry baseRadius i topRadius określają odpowiednio promień dolnej i górnej podstawy cylindra. Promienie mogą oczywiście mieć różną wielkość, co spowoduje utworzenie stożka ściętego. W szczególnym przypadku jeden z promieni może mieć wartość 0. Jak się Czytelnik domyśla, otrzymamy wówczas stożek. W każdym przypadku obiekt rysowany jest bez górnej i dolnej podstawy. Można je utworzyć korzystając z opisanego dalej dysku.
Parametry slices i stacks mają takie same znaczenie jak w przypadku tworzenia kuli. Są to odpowiedniki południków i równoleżników na kuli. W przypadku cylindra można je określić jako wzdłużne i poprzeczne segmenty.
Kolejną kwadryką dostępną w bibliotece GLU jest dysk (koło) tworzony wywołaniem funkcji:
void gluDisk( GLUquadric * quadobj,
GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops )
Parametry innerRadius i outerRadius określają promień wewnętrzny i zewnętrzny dysku. Jeżeli promień wewnętrzny jest większy od 0, to dysk zawiera w środku okrągły otwór. Taką figurę nazywamy pierścieniem kołowym. Parametry slices i loops określają ilość sektorów w kierunku obwodowym i promieniowym, z których składa się dysk.
Ostatnią kwadryką dostępną w bibliotece GLU jest częściowy dysk (wycinek kołowy). Tworzy go wywołanie funkcji:
void gluPartialDisk( GLUquadric * quadobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle )
Parametry innerRadius, outerRadius, slices i loops mają takie same znaczenie jak przy tworzeniu dysku za pomocą funkcji gluDisk. Natomiast parametry startAngle i sweepAngle określają kąt początkowy i kąt środkowy dysku. Jeżeli promień wewnętrzny, określony parametrem outerRadius, jest większy od 0, otrzymamy figurę nazywaną wycinkiem pierścienia kołowego.
Biblioteka GLU zawiera cztery funkcje umożliwiające modyfikację sposobu rysowania kwadryk. Trzy z nich zostały wykorzystane w przykładowym programie, a czwartą obsługującą teksturowanie, wykorzystamy w jednym z następnych odcinków kursu.
Sposób prezentacji kwadryki określa funkcja:
void gluQuadricDrawStyle( GLUquadric * quadobj, GLenum drawStyle )
której parametr drawStyle przyjmuje jedną z poniższych wartości:
Druga funkcja określa sposób generowania wektorów normalnych kwadryk:
void gluQuadricNormals( GLUquadric * quadobj, GLenum normals )
Parametr normals przyjmuje jedną z wartości:
Kolejna prezentowana funkcja określa orientację generowanych wektorów
normalnych:
void gluQuadricOrientation( GLUquadric * quadobj, GLenum orientation )
Parametr orientation przyjmuje jedną z dwóch wartości:
Oczywiście pojęcia „na zewnątrz” i „do wewnątrz” nabierają różnego znaczenia w zależności od rodzaju rysowanej kwadryki. W przypadku figur płaskich będzie to po prostu jedna ze stron figury.
Zadaniem ostatniej z opisywanych funkcji jest nałożenie dwuwymiarowej tekstury na kwadrykę:
void gluQuadricTexture( GLUquadric * quadobj, GLboolean texture )
Parametr quadobj to oczywiście wskaźnik do struktury (klasy) GLUquadric. Drugi parametr określa czy kwadryka ma być teksturowana bieżącą teksturą (
GL_TRUE) czy też nie (
GL_FALSE).
Ostatnim elementem obługi kwadryk w bibliotece GLU jest możliwość zdefiniowania funkcji zwrotnej wywoływanej w momencie wystąpienia błędu. Dołączenie funkcji zwrotnej realizuje funkcja:
void gluQuadricCallback( GLUquadric * quadobj, GLenum which, void( * fn )() )
której parametr which określa rodzaj błędu obsługiwanego przez funkcję zwrotną wskazywaną w parametrze fn. Ponieważ jednak w przypadku kwadryk biblioteka GLU nie posiada specjalnego zestawu kodów błędów, jedyną dopuszczalną wartością parametru which jest stała
GLU_ERROR.
Wróćmy teraz do możliwości naszego programu. Kwadryki oświetlane są światłem
GL_LIGHT0 z parametrami domyślnymi. Program z założenia nie modyfikuje żadnych ustawień tego źródła światła, umożliwia natomiast wybór z poziomu menu podręcznego wszystkich opisanych wcześniej opcji rysowania kwadryk. Z poziomu menu użytkownik może także wybrać rodzaj materiału użytego do narysowania kwadryki. Dodatkowo przy pomocy przycisków PageUp i PageDown można zmieniać ilość elementów, z których będzie złożona kwadryka. Wyświetlany cały czas na ekranie wskaźnik FPS (ang. frames per second) ilości ramek obrazu rysowanych w ciągu jednej sekundy pozwala na jednoczesną weryfikację stosunku jakości generowanego obrazu do szybkości jego generowania. Wartość FPS jest obliczana po narysowaniu co tysięcznej ramki obrazu.
Przykładowe efekty działania programu przedstawiono na rysunkach 6 - 10. W celu ujednolicenia opisu pod każdym rysunkiem po nazwie rysowanej kwadryki umieszczono parametry z jaką została narysowana. Są to kolejno: wektory normalne, orientacja, styl oraz ilość elementów użytych do rysowania kwadryki (odpowiednio ilość sektorów lub południków i równoleżników figury). Nie należy tego mylić z ilością trójkątów lub innych prymitywów graficznych użytych do narysowania figury.
Plik materials.h
#ifndef __MATERIALS__H__
#define __MATERIALS__H__
#include <GL/gl.h>
const GLfloat BrassAmbient[ 4 ] =
{
0.329412, 0.223529, 0.027451, 1.000000
};
const GLfloat BrassDiffuse[ 4 ] =
{
0.780392, 0.568627, 0.113725, 1.000000
};
const GLfloat BrassSpecular[ 4 ] =
{
0.992157, 0.941176, 0.807843, 1.000000
};
const GLfloat BrassShininess = 27.8974;
const GLfloat BronzeAmbient[ 4 ] =
{
0.212500, 0.127500, 0.054000, 1.000000
};
const GLfloat BronzeDiffuse[ 4 ] =
{
0.714000, 0.428400, 0.181440, 1.000000
};
const GLfloat BronzeSpecular[ 4 ] =
{
0.393548, 0.271906, 0.166721, 1.000000
};
const GLfloat BronzeShininess = 25.6;
const GLfloat PolishedBronzeAmbient[ 4 ] =
{
0.250000, 0.148000, 0.064750, 1.000000
};
const GLfloat PolishedBronzeDiffuse[ 4 ] =
{
0.400000, 0.236800, 0.103600, 1.000000
};
const GLfloat PolishedBronzeSpecular[ 4 ] =
{
0.774597, 0.458561, 0.200621, 1.000000
};
const GLfloat PolishedBronzeShininess = 76.8;
const GLfloat ChromeAmbient[ 4 ] =
{
0.250000, 0.250000, 0.250000, 1.000000
};
const GLfloat ChromeDiffuse[ 4 ] =
{
0.400000, 0.400000, 0.400000, 1.000000
};
const GLfloat ChromeSpecular[ 4 ] =
{
0.774597, 0.774597, 0.774597, 1.000000
};
const GLfloat ChromeShininess = 76.8;
const GLfloat CopperAmbient[ 4 ] =
{
0.191250, 0.073500, 0.022500, 1.000000
};
const GLfloat CopperDiffuse[ 4 ] =
{
0.703800, 0.270480, 0.082800, 1.000000
};
const GLfloat CopperSpecular[ 4 ] =
{
0.256777, 0.137622, 0.086014, 1.000000
};
const GLfloat CopperShininess = 12.8;
const GLfloat PolishedCopperAmbient[ 4 ] =
{
0.229500, 0.088250, 0.027500, 1.000000
};
const GLfloat PolishedCopperDiffuse[ 4 ] =
{
0.550800, 0.211800, 0.066000, 1.000000
};
const GLfloat PolishedCopperSpecular[ 4 ] =
{
0.580594, 0.223257, 0.069570, 1.000000
};
const GLfloat PolishedCopperShininess = 51.2;
const GLfloat GoldAmbient[ 4 ] =
{
0.247250, 0.199500, 0.074500, 1.000000
};
const GLfloat GoldDiffuse[ 4 ] =
{
0.751640, 0.606480, 0.226480, 1.000000
};
const GLfloat GoldSpecular[ 4 ] =
{
0.628281, 0.555802, 0.366065, 1.000000
};
const GLfloat GoldShininess = 52.2;
const GLfloat PolishedGoldAmbient[ 4 ] =
{
0.247250, 0.224500, 0.064500, 1.000000
};
const GLfloat PolishedGoldDiffuse[ 4 ] =
{
0.346150, 0.314300, 0.090300, 1.000000
};
const GLfloat PolishedGoldSpecular[ 4 ] =
{
0.797357, 0.723991, 0.208006, 1.000000
};
const GLfloat PolishedGoldShininess = 83.2;
const GLfloat PewterAmbient[ 4 ] =
{
0.105882, 0.058824, 0.113725, 1.000000
};
const GLfloat PewterDiffuse[ 4 ] =
{
0.427451, 0.470588, 0.541176, 1.000000
};
const GLfloat PewterSpecular[ 4 ] =
{
0.333333, 0.333333, 0.521569, 1.000000
};
const GLfloat PewterShininess = 9.84615;
const GLfloat SilverAmbient[ 4 ] =
{
0.192250, 0.192250, 0.192250, 1.000000
};
const GLfloat SilverDiffuse[ 4 ] =
{
0.507540, 0.507540, 0.507540, 1.000000
};
const GLfloat SilverSpecular[ 4 ] =
{
0.508273, 0.508273, 0.508273, 1.000000
};
const GLfloat SilverShininess = 51.2;
const GLfloat PolishedSilverAmbient[ 4 ] =
{
0.231250, 0.231250, 0.231250, 1.000000
};
const GLfloat PolishedSilverDiffuse[ 4 ] =
{
0.277500, 0.277500, 0.277500, 1.000000
};
const GLfloat PolishedSilverSpecular[ 4 ] =
{
0.773911, 0.773911, 0.773911, 1.000000
};
const GLfloat PolishedSilverShininess = 89.6;
const GLfloat EmeraldAmbient[ 4 ] =
{
0.021500, 0.174500, 0.021500, 0.550000
};
const GLfloat EmeraldDiffuse[ 4 ] =
{
0.075680, 0.614240, 0.075680, 0.550000
};
const GLfloat EmeraldSpecular[ 4 ] =
{
0.633000, 0.727811, 0.633000, 0.550000
};
const GLfloat EmeraldShininess = 76.8;
const GLfloat JadeAmbient[ 4 ] =
{
0.135000, 0.222500, 0.157500, 0.950000
};
const GLfloat JadeDiffuse[ 4 ] =
{
0.540000, 0.890000, 0.630000, 0.950000
};
const GLfloat JadeSpecular[ 4 ] =
{
0.316228, 0.316228, 0.316228, 0.950000
};
const GLfloat JadeShininess = 12.8;
const GLfloat ObsidianAmbient[ 4 ] =
{
0.053750, 0.050000, 0.066250, 0.820000
};
const GLfloat ObsidianDiffuse[ 4 ] =
{
0.182750, 0.170000, 0.225250, 0.820000
};
const GLfloat ObsidianSpecular[ 4 ] =
{
0.332741, 0.328634, 0.346435, 0.820000
};
const GLfloat ObsidianShininess = 38.4;
const GLfloat PearlAmbient[ 4 ] =
{
0.250000, 0.207250, 0.207250, 0.922000
};
const GLfloat PearlDiffuse[ 4 ] =
{
1.000000, 0.829000, 0.829000, 0.922000
};
const GLfloat PearlSpecular[ 4 ] =
{
0.296648, 0.296648, 0.296648, 0.922000
};
const GLfloat PearlShininess = 11.264;
const GLfloat RubyAmbient[ 4 ] =
{
0.174500, 0.011750, 0.011750, 0.550000
};
const GLfloat RubyDiffuse[ 4 ] =
{
0.614240, 0.041360, 0.041360, 0.550000
};
const GLfloat RubySpecular[ 4 ] =
{
0.727811, 0.626959, 0.626959, 0.550000
};
const GLfloat RubyShininess = 76.8;
const GLfloat TurquoiseAmbient[ 4 ] =
{
0.100000, 0.187250, 0.174500, 0.800000
};
const GLfloat TurquoiseDiffuse[ 4 ] =
{
0.396000, 0.741510, 0.691020, 0.800000
};
const GLfloat TurquoiseSpecular[ 4 ] =
{
0.297254, 0.308290, 0.306678, 0.800000
};
const GLfloat TurquoiseShininess = 12.8;
const GLfloat BlackPlasticAmbient[ 4 ] =
{
0.000000, 0.000000, 0.000000, 1.000000
};
const GLfloat BlackPlasticDiffuse[ 4 ] =
{
0.010000, 0.010000, 0.010000, 1.000000
};
const GLfloat BlackPlasticSpecular[ 4 ] =
{
0.500000, 0.500000, 0.500000, 1.000000
};
const GLfloat BlackPlasticShininess = 32;
const GLfloat BlackRubberAmbient[ 4 ] =
{
0.020000, 0.020000, 0.020000, 1.000000
};
const GLfloat BlackRubberDiffuse[ 4 ] =
{
0.010000, 0.010000, 0.010000, 1.000000
};
const GLfloat BlackRubberSpecular[ 4 ] =
{
0.040000, 0.040000, 0.040000, 1.000000
};
const GLfloat BlackRubberShininess = 10;
#endif
Plik materlaly.cpp
#include <GL/glut.h>
#include <GL/glu.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "colors.h"
#include "materials.h"
enum
{
SPHERE,
CYLINDER,
DISK,
PARTIAL_DISK,
BRASS,
BRONZE,
POLISHED_BRONZE,
CHROME,
COPPER,
POLISHED_COPPER,
GOLD,
POLISHED_GOLD,
PEWTER,
SILVER,
POLISHED_SILVER,
EMERALD,
JADE,
OBSIDIAN,
PEARL,
RUBY,
TURQUOISE,
BLACK_PLASTIC,
BLACK_RUBBER,
NORMALS_SMOOTH,
NORMALS_FLAT,
NORMALS_NONE,
ORIENTATION_OUTSIDE,
ORIENTATION_INSIDE,
STYLE_POINT,
STYLE_LINE,
STYLE_FILL,
STYLE_SILHOUETTE,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
const GLdouble left = - 1.0;
const GLdouble right = 1.0;
const GLdouble bottom = - 1.0;
const GLdouble top = 1.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat scale = 1.05;
const GLfloat * ambient = BrassAmbient;
const GLfloat * diffuse = BrassDiffuse;
const GLfloat * specular = BrassSpecular;
GLfloat shininess = BrassShininess;
int object = SPHERE;
int normals = NORMALS_SMOOTH;
int orientation = GLU_OUTSIDE;
int style = GLU_FILL;
int segments = 10;
int frames = 0;
long start_time = 0;
char time_string[ 100 ] = "FPS:";
void DrawString( GLfloat x, GLfloat y, char * string )
{
glRasterPos2f( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void Display()
{
if( !frames++ )
start_time = clock();
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0, 0 );
glRotatef( rotatey, 0, 1.0, 0 );
glScalef( scale, scale, scale );
glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, ambient );
glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse );
glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, specular );
glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, shininess );
GLUquadricObj * quadobj = gluNewQuadric();
gluQuadricNormals( quadobj, normals );
gluQuadricDrawStyle( quadobj, style );
gluQuadricOrientation( quadobj, orientation );
switch( object )
{
case SPHERE:
gluSphere( quadobj, 1, segments, segments );
break;
case CYLINDER:
gluCylinder( quadobj, 0.5, 0.5, 1, segments, segments );
break;
case DISK:
gluDisk( quadobj, 0.5, 1.0, segments, segments );
break;
case PARTIAL_DISK:
gluPartialDisk( quadobj, 0.5, 1.0, segments, segments, 90, 180 );
break;
}
gluDeleteQuadric( quadobj );
glDisable( GL_LIGHTING );
glDisable( GL_DEPTH_TEST );
glLoadIdentity();
glTranslatef( 0, 0, - near );
glColor3fv( Black );
if( frames == 1000 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 1000 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
}
DrawString( left, bottom, time_string );
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( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
Display();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' )
scale += 0.05;
else
if( key == '-' && scale > 0.05 )
scale -= 0.05;
Display();
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_PAGE_UP:
segments += 10;
break;
case GLUT_KEY_PAGE_DOWN:
if( segments > 10 )
segments -= 10;
break;
}
Display();
}
void Menu( int value )
{
switch( value )
{
case SPHERE:
object = SPHERE;
Display();
break;
case CYLINDER:
object = CYLINDER;
Display();
break;
case DISK:
object = DISK;
Display();
break;
case PARTIAL_DISK:
object = PARTIAL_DISK;
Display();
break;
case BRASS:
ambient = BrassAmbient;
diffuse = BrassDiffuse;
specular = BrassSpecular;
shininess = BrassShininess;
Display();
break;
case BRONZE:
ambient = BronzeAmbient;
diffuse = BronzeDiffuse;
specular = BronzeSpecular;
shininess = BronzeShininess;
Display();
break;
case POLISHED_BRONZE:
ambient = PolishedBronzeAmbient;
diffuse = PolishedBronzeDiffuse;
specular = PolishedBronzeSpecular;
shininess = PolishedBronzeShininess;
Display();
break;
case CHROME:
ambient = ChromeAmbient;
diffuse = ChromeDiffuse;
specular = ChromeSpecular;
shininess = ChromeShininess;
Display();
break;
case COPPER:
ambient = CopperAmbient;
diffuse = CopperDiffuse;
specular = CopperSpecular;
shininess = CopperShininess;
Display();
break;
case POLISHED_COPPER:
ambient = PolishedCopperAmbient;
diffuse = PolishedCopperDiffuse;
specular = PolishedCopperSpecular;
shininess = PolishedCopperShininess;
Display();
break;
case GOLD:
ambient = GoldAmbient;
diffuse = GoldDiffuse;
specular = GoldSpecular;
shininess = GoldShininess;
Display();
break;
case POLISHED_GOLD:
ambient = PolishedGoldAmbient;
diffuse = PolishedGoldDiffuse;
specular = PolishedGoldSpecular;
shininess = PolishedGoldShininess;
Display();
break;
case PEWTER:
ambient = PewterAmbient;
diffuse = PewterDiffuse;
specular = PewterSpecular;
shininess = PewterShininess;
Display();
break;
case SILVER:
ambient = SilverAmbient;
diffuse = SilverDiffuse;
specular = SilverSpecular;
shininess = SilverShininess;
Display();
break;
case POLISHED_SILVER:
ambient = PolishedSilverAmbient;
diffuse = PolishedSilverDiffuse;
specular = PolishedSilverSpecular;
shininess = PolishedSilverShininess;
Display();
break;
case EMERALD:
ambient = EmeraldAmbient;
diffuse = EmeraldDiffuse;
specular = EmeraldSpecular;
shininess = EmeraldShininess;
Display();
break;
case JADE:
ambient = JadeAmbient;
diffuse = JadeDiffuse;
specular = JadeSpecular;
shininess = JadeShininess;
Display();
break;
case OBSIDIAN:
ambient = ObsidianAmbient;
diffuse = ObsidianDiffuse;
specular = ObsidianSpecular;
shininess = ObsidianShininess;
Display();
break;
case PEARL:
ambient = PearlAmbient;
diffuse = PearlDiffuse;
specular = PearlSpecular;
shininess = PearlShininess;
Display();
break;
case RUBY:
ambient = RubyAmbient;
diffuse = RubyDiffuse;
specular = RubySpecular;
shininess = RubyShininess;
Display();
break;
case TURQUOISE:
ambient = TurquoiseAmbient;
diffuse = TurquoiseDiffuse;
specular = TurquoiseSpecular;
shininess = TurquoiseShininess;
Display();
break;
case BLACK_PLASTIC:
ambient = BlackPlasticAmbient;
diffuse = BlackPlasticDiffuse;
specular = BlackPlasticSpecular;
shininess = BlackPlasticShininess;
Display();
break;
case BLACK_RUBBER:
ambient = BlackRubberAmbient;
diffuse = BlackRubberDiffuse;
specular = BlackRubberSpecular;
shininess = BlackRubberShininess;
Display();
break;
case NORMALS_SMOOTH:
normals = GLU_SMOOTH;
Display();
break;
case NORMALS_FLAT:
normals = GLU_FLAT;
Display();
break;
case NORMALS_NONE:
normals = GLU_NONE;
Display();
break;
case ORIENTATION_OUTSIDE:
orientation = GLU_OUTSIDE;
Display();
break;
case ORIENTATION_INSIDE:
orientation = GLU_INSIDE;
Display();
break;
case STYLE_POINT:
style = GLU_POINT;
Display();
break;
case STYLE_LINE:
style = GLU_LINE;
Display();
break;
case STYLE_FILL:
style = GLU_FILL;
Display();
break;
case STYLE_SILHOUETTE:
style = GLU_SILHOUETTE;
Display();
break;
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 | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Materiały" );
#else
glutCreateWindow( "Materialy" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
int MenuObject = glutCreateMenu( Menu );
glutAddMenuEntry( "Kula", SPHERE );
glutAddMenuEntry( "Cylinder", CYLINDER );
glutAddMenuEntry( "Dysk", DISK );
#ifdef WIN32
glutAddMenuEntry( "Częściowy dysk", PARTIAL_DISK );
#else
glutAddMenuEntry( "Czesciowy dysk", PARTIAL_DISK );
#endif
int MenuMaterial = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Mosiądz", BRASS );
glutAddMenuEntry( "Brąz", BRONZE );
glutAddMenuEntry( "Polerowany brąz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedź", COPPER );
glutAddMenuEntry( "Polerowana miedź", POLISHED_COPPER );
glutAddMenuEntry( "Złoto", GOLD );
glutAddMenuEntry( "Polerowane złoto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perła", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#else
glutAddMenuEntry( "Mosiadz", BRASS );
glutAddMenuEntry( "Braz", BRONZE );
glutAddMenuEntry( "Polerowany braz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedz", COPPER );
glutAddMenuEntry( "Polerowana miedz", POLISHED_COPPER );
glutAddMenuEntry( "Zloto", GOLD );
glutAddMenuEntry( "Polerowane zloto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perla", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#endif
int MenuNormals = glutCreateMenu( Menu );
glutAddMenuEntry( "GLU_SMOOTH", NORMALS_SMOOTH );
glutAddMenuEntry( "GLU_FLAT", NORMALS_FLAT );
glutAddMenuEntry( "GLU_NONE", NORMALS_NONE );
int MenuOrientation = glutCreateMenu( Menu );
glutAddMenuEntry( "GLU_OUTSIDE", ORIENTATION_OUTSIDE );
glutAddMenuEntry( "GLU_INSIDE", ORIENTATION_INSIDE );
int MenuStyle = glutCreateMenu( Menu );
glutAddMenuEntry( "GLU_POINT", STYLE_POINT );
glutAddMenuEntry( "GLU_LINE", STYLE_LINE );
glutAddMenuEntry( "GLU_FILL", STYLE_FILL );
glutAddMenuEntry( "GLU_SILHOUETTE", STYLE_SILHOUETTE );
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
glutAddSubMenu( "Obiekt", MenuObject );
#ifdef WIN32
glutAddSubMenu( "Materiał", MenuMaterial );
#else
glutAddSubMenu( "Material", MenuMaterial );
#endif
glutAddSubMenu( "Wektory normalne", MenuNormals );
glutAddSubMenu( "Orientacja", MenuOrientation );
glutAddSubMenu( "Styl", MenuStyle );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutIdleFunc( Display );
glutMainLoop();
return 0;
}
Kolejny program przykładowy (plik wektory normalne.cpp) przedstawia dwa opisane wcześniej sposoby generowania wektorów normalnych. Ale zanim przejdziemy do opisu obliczania wektorów normalnych popatrzmy na sposób opisu bryły wyświetlanej przez program - dwudziestościanu foremnego.
Dwudziestościan opisany jest przez dwie tablice. Pierwsza tablica vertex opisuje współrzędne położenia wierzchołków figury. Druga tablica triangles zawiera opis poszczególnych trójkątów, z których składa się dwudziestościan. Jedną z cech takiego rodzaju opis bryły jest łatwość obliczania wektorów normalnych. W przypadku, gdy do obliczamy wektor normalny dla ściany, wystarczy skorzystać z iloczynu wektorowego, przy czym składniki tego iloczynu stanowią wektory utworzone z dwóch boków trójkąta. W przykładowym programie realizuje to funkcja Normal. Natomiast w przypadku, gdy obliczamy wektor normalny dla każdego wierzchołka, program przegląda listę ścian (tablica triangles) wybierając te ściany, w skład których wchodzi bieżący wierzchołek i dokonuje sumowania wektorów normalnych każdej z wybranych ścian. Wykorzystany do tego celu algorytm jest wprawdzie prosty, ale także bardzo czasochłonny. Najprostszym sposobem jego optymalizacji jest jednokrotne obliczenie wektora normalnego każdej ze ścian i zapamiętanie wyników w tablicy.
Normalizacja wektorów normalnych realizowana jest w programie na dwa spsoby. Jeżeli implementacja biblioteki OpenGL obsługuje rozszerzenie EXT rescale normal, lub jest to wersja co najmniej 1.2, program korzysta z opisanego wcześniej mechanizmu automatycznego skalowania jednostkowych wektorów normalnych. W przeciwnym wypadku całość obliczeń związanych z normalizacją wykonuje biblioteka OpenGL, a funkcja Normalize, która wykonuje normalizację, nie jest wykorzystywana.
Efekty działania programu przedstawiają dwa rysunki. Pierwszy z nich (rysunek nr 11) przedstawia obiekt z wektorami normalnymi generowanymi dla każdego wierzchołka. Na rysunku łatwo można dostrzec charakterystyczne linie pokrywające się z krawędziami obiektu. Nieciągłości te, zwane pasmami Macha, są jedną z wad cieniowania Gourauda zastosowanego w bibliotece OpenGL. Na rysunku nr 12 przedstawiono bryłę w tym samym powiększeniu i położeniu, ale z wektorami normalnymi generowanymi dla każdej ściany.
Plik wektory_normalne.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "materials.h"
bool rescale_normal = false;
enum
{
BRASS,
BRONZE,
POLISHED_BRONZE,
CHROME,
COPPER,
POLISHED_COPPER,
GOLD,
POLISHED_GOLD,
PEWTER,
SILVER,
POLISHED_SILVER,
EMERALD,
JADE,
OBSIDIAN,
PEARL,
RUBY,
TURQUOISE,
BLACK_PLASTIC,
BLACK_RUBBER,
NORMALS_SMOOTH,
NORMALS_FLAT,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 1.0;
const GLdouble right = 1.0;
const GLdouble bottom = - 1.0;
const GLdouble top = 1.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat scale = 1.0;
const GLfloat * ambient = BrassAmbient;
const GLfloat * diffuse = BrassDiffuse;
const GLfloat * specular = BrassSpecular;
GLfloat shininess = BrassShininess;
int normals = NORMALS_FLAT;
GLfloat vertex[ 12 * 3 ] =
{
0.000, 0.667, 0.500,
0.000, 0.667, - 0.500,
0.000, - 0.667, - 0.500,
0.000, - 0.667, 0.500,
0.667, 0.500, 0.000,
0.667, - 0.500, 0.000,
- 0.667, - 0.500, 0.000,
- 0.667, 0.500, 0.000,
0.500, 0.000, 0.667,
- 0.500, 0.000, 0.667,
- 0.500, 0.000, - 0.667,
0.500, 0.000, - 0.667
};
int triangles[ 20 * 3 ] =
{
2, 10, 11,
1, 11, 10,
1, 10, 7,
1, 4, 11,
0, 1, 7,
0, 4, 1,
0, 9, 8,
3, 8, 9,
0, 7, 9,
0, 8, 4,
3, 9, 6,
3, 5, 8,
2, 3, 6,
2, 5, 3,
2, 6, 10,
2, 11, 5,
6, 7, 10,
6, 9, 7,
4, 5, 11,
4, 8, 5
};
void Normal( GLfloat * n, int i )
{
GLfloat v1[ 3 ], v2[ 3 ];
v1[ 0 ] = vertex[ 3 * triangles[ 3 * i + 1 ] + 0 ] - vertex[ 3 * triangles[ 3 * i + 0 ] + 0 ];
v1[ 1 ] = vertex[ 3 * triangles[ 3 * i + 1 ] + 1 ] - vertex[ 3 * triangles[ 3 * i + 0 ] + 1 ];
v1[ 2 ] = vertex[ 3 * triangles[ 3 * i + 1 ] + 2 ] - vertex[ 3 * triangles[ 3 * i + 0 ] + 2 ];
v2[ 0 ] = vertex[ 3 * triangles[ 3 * i + 2 ] + 0 ] - vertex[ 3 * triangles[ 3 * i + 1 ] + 0 ];
v2[ 1 ] = vertex[ 3 * triangles[ 3 * i + 2 ] + 1 ] - vertex[ 3 * triangles[ 3 * i + 1 ] + 1 ];
v2[ 2 ] = vertex[ 3 * triangles[ 3 * i + 2 ] + 2 ] - vertex[ 3 * triangles[ 3 * i + 1 ] + 2 ];
n[ 0 ] = v1[ 1 ] * v2[ 2 ] - v1[ 2 ] * v2[ 1 ];
n[ 1 ] = v1[ 2 ] * v2[ 0 ] - v1[ 0 ] * v2[ 2 ];
n[ 2 ] = v1[ 0 ] * v2[ 1 ] - v1[ 1 ] * v2[ 0 ];
}
void Normalize( GLfloat * v )
{
GLfloat d = sqrt( v[ 0 ] * v[ 0 ] + v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] );
if( d )
{
v[ 0 ] /= d;
v[ 1 ] /= d;
v[ 2 ] /= d;
}
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0, 0 );
glRotatef( rotatey, 0, 1.0, 0 );
glScalef( scale, scale, scale );
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glMaterialfv( GL_FRONT, GL_AMBIENT, ambient );
glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse );
glMaterialfv( GL_FRONT, GL_SPECULAR, specular );
glMaterialf( GL_FRONT, GL_SHININESS, shininess );
if( rescale_normal == true )
glEnable( GL_RESCALE_NORMAL );
else
glEnable( GL_NORMALIZE );
glBegin( GL_TRIANGLES );
if( normals == NORMALS_SMOOTH )
for( int i = 0; i < 20; i++ )
{
GLfloat n[ 3 ];
n[ 0 ] = n[ 1 ] = n[ 2 ] = 0.0;
for( int j = 0; j < 20; j++ )
if( 3 * triangles[ 3 * i + 0 ] == 3 * triangles[ 3 * j + 0 ] ||
3 * triangles[ 3 * i + 0 ] == 3 * triangles[ 3 * j + 1 ] ||
3 * triangles[ 3 * i + 0 ] == 3 * triangles[ 3 * j + 2 ] )
{
GLfloat nv[ 3 ];
Normal( nv, j );
n[ 0 ] += nv[ 0 ];
n[ 1 ] += nv[ 1 ];
n[ 2 ] += nv[ 2 ];
}
if( rescale_normal == true )
Normalize( n );
glNormal3fv( n );
glVertex3fv( & vertex[ 3 * triangles[ 3 * i + 0 ] ] );
n[ 0 ] = n[ 1 ] = n[ 2 ] = 0.0;
for( int j = 0; j < 20; j++ )
if( 3 * triangles[ 3 * i + 1 ] == 3 * triangles[ 3 * j + 0 ] ||
3 * triangles[ 3 * i + 1 ] == 3 * triangles[ 3 * j + 1 ] ||
3 * triangles[ 3 * i + 1 ] == 3 * triangles[ 3 * j + 2 ] )
{
GLfloat nv[ 3 ];
Normal( nv, j );
n[ 0 ] += nv[ 0 ];
n[ 1 ] += nv[ 1 ];
n[ 2 ] += nv[ 2 ];
}
if( rescale_normal == true )
Normalize( n );
glNormal3fv( n );
glVertex3fv( & vertex[ 3 * triangles[ 3 * i + 1 ] ] );
n[ 0 ] = n[ 1 ] = n[ 2 ] = 0.0;
for( int j = 0; j < 20; j++ )
if( 3 * triangles[ 3 * i + 2 ] == 3 * triangles[ 3 * j + 0 ] ||
3 * triangles[ 3 * i + 2 ] == 3 * triangles[ 3 * j + 1 ] ||
3 * triangles[ 3 * i + 2 ] == 3 * triangles[ 3 * j + 2 ] )
{
GLfloat nv[ 3 ];
Normal( nv, j );
n[ 0 ] += nv[ 0 ];
n[ 1 ] += nv[ 1 ];
n[ 2 ] += nv[ 2 ];
}
if( rescale_normal == true )
Normalize( n );
glNormal3fv( n );
glVertex3fv( & vertex[ 3 * triangles[ 3 * i + 2 ] ] );
}
else
for( int i = 0; i < 20; i++ )
{
GLfloat n[ 3 ];
Normal( n, i );
if( rescale_normal == true )
Normalize( n );
glNormal3fv( n );
glVertex3fv( & vertex[ 3 * triangles[ 3 * i + 0 ] ] );
glVertex3fv( & vertex[ 3 * triangles[ 3 * i + 1 ] ] );
glVertex3fv( & vertex[ 3 * triangles[ 3 * i + 2 ] ] );
}
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( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
Display();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' )
scale += 0.05;
else
if( key == '-' && scale > 0.05 )
scale -= 0.05;
Display();
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_LEFT:
rotatey -= 1;
break;
case GLUT_KEY_UP:
rotatex -= 1;
break;
case GLUT_KEY_RIGHT:
rotatey += 1;
break;
case GLUT_KEY_DOWN:
rotatex += 1;
break;
}
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void Menu( int value )
{
switch( value )
{
case BRASS:
ambient = BrassAmbient;
diffuse = BrassDiffuse;
specular = BrassSpecular;
shininess = BrassShininess;
Display();
break;
case BRONZE:
ambient = BronzeAmbient;
diffuse = BronzeDiffuse;
specular = BronzeSpecular;
shininess = BronzeShininess;
Display();
break;
case POLISHED_BRONZE:
ambient = PolishedBronzeAmbient;
diffuse = PolishedBronzeDiffuse;
specular = PolishedBronzeSpecular;
shininess = PolishedBronzeShininess;
Display();
break;
case CHROME:
ambient = ChromeAmbient;
diffuse = ChromeDiffuse;
specular = ChromeSpecular;
shininess = ChromeShininess;
Display();
break;
case COPPER:
ambient = CopperAmbient;
diffuse = CopperDiffuse;
specular = CopperSpecular;
shininess = CopperShininess;
Display();
break;
case POLISHED_COPPER:
ambient = PolishedCopperAmbient;
diffuse = PolishedCopperDiffuse;
specular = PolishedCopperSpecular;
shininess = PolishedCopperShininess;
Display();
break;
case GOLD:
ambient = GoldAmbient;
diffuse = GoldDiffuse;
specular = GoldSpecular;
shininess = GoldShininess;
Display();
break;
case POLISHED_GOLD:
ambient = PolishedGoldAmbient;
diffuse = PolishedGoldDiffuse;
specular = PolishedGoldSpecular;
shininess = PolishedGoldShininess;
Display();
break;
case PEWTER:
ambient = PewterAmbient;
diffuse = PewterDiffuse;
specular = PewterSpecular;
shininess = PewterShininess;
Display();
break;
case SILVER:
ambient = SilverAmbient;
diffuse = SilverDiffuse;
specular = SilverSpecular;
shininess = SilverShininess;
Display();
break;
case POLISHED_SILVER:
ambient = PolishedSilverAmbient;
diffuse = PolishedSilverDiffuse;
specular = PolishedSilverSpecular;
shininess = PolishedSilverShininess;
Display();
break;
case EMERALD:
ambient = EmeraldAmbient;
diffuse = EmeraldDiffuse;
specular = EmeraldSpecular;
shininess = EmeraldShininess;
Display();
break;
case JADE:
ambient = JadeAmbient;
diffuse = JadeDiffuse;
specular = JadeSpecular;
shininess = JadeShininess;
Display();
break;
case OBSIDIAN:
ambient = ObsidianAmbient;
diffuse = ObsidianDiffuse;
specular = ObsidianSpecular;
shininess = ObsidianShininess;
Display();
break;
case PEARL:
ambient = PearlAmbient;
diffuse = PearlDiffuse;
specular = PearlSpecular;
shininess = PearlShininess;
Display();
break;
case RUBY:
ambient = RubyAmbient;
diffuse = RubyDiffuse;
specular = RubySpecular;
shininess = RubyShininess;
Display();
break;
case TURQUOISE:
ambient = TurquoiseAmbient;
diffuse = TurquoiseDiffuse;
specular = TurquoiseSpecular;
shininess = TurquoiseShininess;
Display();
break;
case BLACK_PLASTIC:
ambient = BlackPlasticAmbient;
diffuse = BlackPlasticDiffuse;
specular = BlackPlasticSpecular;
shininess = BlackPlasticShininess;
Display();
break;
case BLACK_RUBBER:
ambient = BlackRubberAmbient;
diffuse = BlackRubberDiffuse;
specular = BlackRubberSpecular;
shininess = BlackRubberShininess;
Display();
break;
case NORMALS_SMOOTH:
normals = NORMALS_SMOOTH;
Display();
break;
case NORMALS_FLAT:
normals = NORMALS_FLAT;
Display();
break;
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 );
}
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifndef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 2 )
rescale_normal = true;
else
if( glutExtensionSupported( "GL_EXT_rescale_normal" ) )
rescale_normal = true;
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "Wektory normalne" );
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
int MenuMaterial = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Mosiądz", BRASS );
glutAddMenuEntry( "Brąz", BRONZE );
glutAddMenuEntry( "Polerowany brąz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedź", COPPER );
glutAddMenuEntry( "Polerowana miedź", POLISHED_COPPER );
glutAddMenuEntry( "Złoto", GOLD );
glutAddMenuEntry( "Polerowane złoto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perła", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#else
glutAddMenuEntry( "Mosiadz", BRASS );
glutAddMenuEntry( "Braz", BRONZE );
glutAddMenuEntry( "Polerowany braz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedz", COPPER );
glutAddMenuEntry( "Polerowana miedz", POLISHED_COPPER );
glutAddMenuEntry( "Zloto", GOLD );
glutAddMenuEntry( "Polerowane zloto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perla", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#endif
int MenuNormals = glutCreateMenu( Menu );
#ifndef WIN32
glutAddMenuEntry( "Jeden wektor normalny na wierzcholek", NORMALS_SMOOTH );
glutAddMenuEntry( "Jeden wektor normalny na sciane", NORMALS_FLAT );
#else
glutAddMenuEntry( "Jeden wektor normalny na wierzchołek", NORMALS_SMOOTH );
glutAddMenuEntry( "Jeden wektor normalny na ścianę", NORMALS_FLAT );
#endif
int MenuAspect = glutCreateMenu( Menu );
#ifndef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddSubMenu( "Materiał", MenuMaterial );
#else
glutAddSubMenu( "Material", MenuMaterial );
#endif
glutAddSubMenu( "Wektory normalne", MenuNormals );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
#ifndef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
glutMainLoop();
return 0;
}
Czwarty program przykładowy (plik swiatlo kierunkowe.cpp) prezentuje podstawową właściwość światła kierunkowego, czyli możliwość zmiany kierunku, z którego padają promienie światła. Wektor kierunku źródła światła jest modyfikowany zgodnie z przekształceniami macierzy modelowania, stąd, aby zmiana kierunku źródła światła odbywała się w sposób niezależny od obrotu obiektu umieszczonego na scenie, macierz modelowania należy odłożyć na stos korzystając z funkcji glPushMatrix. Po wykonaniu niezbędnych przekształceń (w przypadku światła kierunkowego będą to oczywiście tylko obroty), przywracamy odłożoną na stos macierz modelowania, wywołując funkcję glPopMatrix. Aby przekształcenie kierunku światła zostały wykonane trzeba jeszcze dla wybranego źródła światła wywołać funkcję glLightfv (lub inną z tej grupy) podając przy tym tablicę zawierającą wektor z kierunkiem źródła światła.
W odróżnieniu od drugiego programu przykładowego do rysowania obiektów w omawianym programie użyto funkcji z biblioteki GLUT. Dwie z nich (rysujące kulę i sześcian) omówiliśmy już wcześniej. Pozostałe funkcje nie różną się parametrami od ich wcześniej opisywanych wersji rysujących obiekty w postaci siatek krawędzi.
Stożek (rysunek 15), składający się z równoległych do podstawy „południków” i tworzących biegnących od wierzchołka do krawędzi podstawy stożka, rysuje funkcja:
void glutSolidCone( GLdouble base, GLdouble height, GLint slices, GLint stacks )
której parametry oznaczaja:
Torus (rysunek 16), składający się z serii walców o nierównoległych podstawach, rysuje funkcja:
void glutSolidTorus( GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings )
której parametry oznaczają odpowiednio:
Czajnik o wielkości regulowanej parametrem size rysuje funkcja:
void glutSolidTeapot( GLdouble size )
Pozostałe użyte w programie funkcje rysują bryły Platońskie. Są to kolejno: dwunastościan (rysunek 17), ośmiościan, czworościan i dwudziestościan:
void glutSolidDodecahedron()
void glutSolidOctahedron()
void glutSolidTetrahedron()
void glutSolidIcosahedron()
Ponadto w programie wykorzystano funkcję glWindowPos2i wprowadzoną w wersji 1.4 biblioteki OpenGL, a wcześniej dostępną w opisywanym już rozszerzeniu ARB window pos. Funkcja ta została zastosowana do pozycjonowania tekstów wyświetlanych w oknie programu. A są to współrzędne określające kierunek źródła światła oraz kąty obrotu kierunku źródła światła.
Plik swiatlo_kierunkowe.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "colors.h"
#include "materials.h"
PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL;
enum
{
SPHERE,
TEAPOT,
CONE,
TORUS,
CUBE,
DODECAHEDRON,
OCTAHEDRON,
TETRAHEDRON,
ICOSAHEDRON,
BRASS,
BRONZE,
POLISHED_BRONZE,
CHROME,
COPPER,
POLISHED_COPPER,
GOLD,
POLISHED_GOLD,
PEWTER,
SILVER,
POLISHED_SILVER,
EMERALD,
JADE,
OBSIDIAN,
PEARL,
RUBY,
TURQUOISE,
BLACK_PLASTIC,
BLACK_RUBBER,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat scale = 1.0;
const GLfloat * ambient = BrassAmbient;
const GLfloat * diffuse = BrassDiffuse;
const GLfloat * specular = BrassSpecular;
GLfloat shininess = BrassShininess;
int object = SPHERE;
GLfloat light_position[ 4 ] =
{
0.0, 0.0, 2.0, 0.0
};
GLfloat light_rotatex = 0.0;
GLfloat light_rotatey = 0.0;
void DrawString( GLint x, GLint y, char * string )
{
glWindowPos2i( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0, 0 );
glRotatef( rotatey, 0, 1.0, 0 );
glScalef( scale, scale, scale );
glMaterialfv( GL_FRONT, GL_AMBIENT, ambient );
glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse );
glMaterialfv( GL_FRONT, GL_SPECULAR, specular );
glMaterialf( GL_FRONT, GL_SHININESS, shininess );
glPushMatrix();
glLoadIdentity();
glRotatef( light_rotatex, 1.0, 0, 0 );
glRotatef( light_rotatey, 0, 1.0, 0 );
glLightfv( GL_LIGHT0, GL_POSITION, light_position );
glPopMatrix();
switch( object )
{
case SPHERE:
glutSolidSphere( 1.0, 50, 40 );
break;
case TEAPOT:
glutSolidTeapot( 1 );
break;
case CONE:
glutSolidCone( 1, 1, 50, 40 );
break;
case TORUS:
glutSolidTorus( 0.3, 1, 40, 50 );
break;
case CUBE:
glutSolidCube( 1 );
break;
case DODECAHEDRON:
glutSolidDodecahedron();
break;
case OCTAHEDRON:
glutSolidOctahedron();
break;
case TETRAHEDRON:
glutSolidTetrahedron();
break;
case ICOSAHEDRON:
glutSolidIcosahedron();
break;
}
char string[ 200 ];
GLfloat vec[ 4 ];
glColor3fv( Black );
glGetLightfv( GL_LIGHT0, GL_POSITION, vec );
sprintf( string, "GL_POSITION = (%f,%f,%f,%f)", vec[ 0 ], vec[ 1 ], vec[ 2 ], vec[ 3 ] );
DrawString( 2, 2, string );
sprintf( string, "light_rotatex = %f", light_rotatex );
DrawString( 2, 16, string );
sprintf( string, "light_rotatey = %f", light_rotatey );
DrawString( 2, 30, string );
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( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void Keyboard( unsigned char key, int x, int y )
{
switch( key )
{
case '+':
scale += 0.05;
break;
case '-':
if( scale > 0.05 )
scale -= 0.05;
break;
}
DisplayScene();
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_LEFT:
light_rotatey -= 5;
break;
case GLUT_KEY_RIGHT:
light_rotatey += 5;
break;
case GLUT_KEY_DOWN:
light_rotatex += 5;
break;
case GLUT_KEY_UP:
light_rotatex -= 5;
break;
}
DisplayScene();
}
void Menu( int value )
{
switch( value )
{
case SPHERE:
object = SPHERE;
DisplayScene();
break;
case TEAPOT:
object = TEAPOT;
DisplayScene();
break;
case CONE:
object = CONE;
DisplayScene();
break;
case TORUS:
object = TORUS;
DisplayScene();
break;
case CUBE:
object = CUBE;
DisplayScene();
break;
case DODECAHEDRON:
object = DODECAHEDRON;
DisplayScene();
break;
case OCTAHEDRON:
object = OCTAHEDRON;
DisplayScene();
break;
case TETRAHEDRON:
object = TETRAHEDRON;
DisplayScene();
break;
case ICOSAHEDRON:
object = ICOSAHEDRON;
DisplayScene();
break;
case BRASS:
ambient = BrassAmbient;
diffuse = BrassDiffuse;
specular = BrassSpecular;
shininess = BrassShininess;
DisplayScene();
break;
case BRONZE:
ambient = BronzeAmbient;
diffuse = BronzeDiffuse;
specular = BronzeSpecular;
shininess = BronzeShininess;
DisplayScene();
break;
case POLISHED_BRONZE:
ambient = PolishedBronzeAmbient;
diffuse = PolishedBronzeDiffuse;
specular = PolishedBronzeSpecular;
shininess = PolishedBronzeShininess;
DisplayScene();
break;
case CHROME:
ambient = ChromeAmbient;
diffuse = ChromeDiffuse;
specular = ChromeSpecular;
shininess = ChromeShininess;
DisplayScene();
break;
case COPPER:
ambient = CopperAmbient;
diffuse = CopperDiffuse;
specular = CopperSpecular;
shininess = CopperShininess;
DisplayScene();
break;
case POLISHED_COPPER:
ambient = PolishedCopperAmbient;
diffuse = PolishedCopperDiffuse;
specular = PolishedCopperSpecular;
shininess = PolishedCopperShininess;
DisplayScene();
break;
case GOLD:
ambient = GoldAmbient;
diffuse = GoldDiffuse;
specular = GoldSpecular;
shininess = GoldShininess;
DisplayScene();
break;
case POLISHED_GOLD:
ambient = PolishedGoldAmbient;
diffuse = PolishedGoldDiffuse;
specular = PolishedGoldSpecular;
shininess = PolishedGoldShininess;
DisplayScene();
break;
case PEWTER:
ambient = PewterAmbient;
diffuse = PewterDiffuse;
specular = PewterSpecular;
shininess = PewterShininess;
DisplayScene();
break;
case SILVER:
ambient = SilverAmbient;
diffuse = SilverDiffuse;
specular = SilverSpecular;
shininess = SilverShininess;
DisplayScene();
break;
case POLISHED_SILVER:
ambient = PolishedSilverAmbient;
diffuse = PolishedSilverDiffuse;
specular = PolishedSilverSpecular;
shininess = PolishedSilverShininess;
DisplayScene();
break;
case EMERALD:
ambient = EmeraldAmbient;
diffuse = EmeraldDiffuse;
specular = EmeraldSpecular;
shininess = EmeraldShininess;
DisplayScene();
break;
case JADE:
ambient = JadeAmbient;
diffuse = JadeDiffuse;
specular = JadeSpecular;
shininess = JadeShininess;
DisplayScene();
break;
case OBSIDIAN:
ambient = ObsidianAmbient;
diffuse = ObsidianDiffuse;
specular = ObsidianSpecular;
shininess = ObsidianShininess;
DisplayScene();
break;
case PEARL:
ambient = PearlAmbient;
diffuse = PearlDiffuse;
specular = PearlSpecular;
shininess = PearlShininess;
DisplayScene();
break;
case RUBY:
ambient = RubyAmbient;
diffuse = RubyDiffuse;
specular = RubySpecular;
shininess = RubyShininess;
DisplayScene();
break;
case TURQUOISE:
ambient = TurquoiseAmbient;
diffuse = TurquoiseDiffuse;
specular = TurquoiseSpecular;
shininess = TurquoiseShininess;
DisplayScene();
break;
case BLACK_PLASTIC:
ambient = BlackPlasticAmbient;
diffuse = BlackPlasticDiffuse;
specular = BlackPlasticSpecular;
shininess = BlackPlasticShininess;
DisplayScene();
break;
case BLACK_RUBBER:
ambient = BlackRubberAmbient;
diffuse = BlackRubberDiffuse;
specular = BlackRubberSpecular;
shininess = BlackRubberShininess;
DisplayScene();
break;
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 );
}
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifndef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 4 )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress( "glWindowPos2i" );
}
else
if( glutExtensionSupported( "GL_ARB_window_pos" ) )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress
( "glWindowPos2iARB" );
}
else
{
printf( "Brak rozszerzenia ARB_window_pos!\n" );
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Światło kierunkowe" );
#else
glutCreateWindow( "Swiatlo kierunkowe" );
#endif
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
int MenuObject = glutCreateMenu( Menu );
glutAddMenuEntry( "Kula", SPHERE );
glutAddMenuEntry( "Czajnik", TEAPOT );
#ifdef WIN32
glutAddMenuEntry( "Stożek", CONE );
glutAddMenuEntry( "Torus", TORUS );
glutAddMenuEntry( "Sześcian", CUBE );
glutAddMenuEntry( "Dwunastościan", DODECAHEDRON );
glutAddMenuEntry( "Ośmiościan", OCTAHEDRON );
glutAddMenuEntry( "Czworościan", TETRAHEDRON );
glutAddMenuEntry( "Dwudziestościan", ICOSAHEDRON );
#else
glutAddMenuEntry( "Stozek", CONE );
glutAddMenuEntry( "Torus", TORUS );
glutAddMenuEntry( "Szescian", CUBE );
glutAddMenuEntry( "Dwunastoscian", DODECAHEDRON );
glutAddMenuEntry( "Osmioscian", OCTAHEDRON );
glutAddMenuEntry( "Czworoscian", TETRAHEDRON );
glutAddMenuEntry( "Dwudziestoscian", ICOSAHEDRON );
#endif
int MenuMaterial = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Mosiądz", BRASS );
glutAddMenuEntry( "Brąz", BRONZE );
glutAddMenuEntry( "Polerowany brąz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedź", COPPER );
glutAddMenuEntry( "Polerowana miedź", POLISHED_COPPER );
glutAddMenuEntry( "Złoto", GOLD );
glutAddMenuEntry( "Polerowane złoto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perła", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#else
glutAddMenuEntry( "Mosiadz", BRASS );
glutAddMenuEntry( "Braz", BRONZE );
glutAddMenuEntry( "Polerowany braz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedz", COPPER );
glutAddMenuEntry( "Polerowana miedz", POLISHED_COPPER );
glutAddMenuEntry( "Zloto", GOLD );
glutAddMenuEntry( "Polerowane zloto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perla", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#endif
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
glutAddSubMenu( "Obiekt", MenuObject );
#ifdef WIN32
glutAddSubMenu( "Materiał", MenuMaterial );
#else
glutAddSubMenu( "Material", MenuMaterial );
#endif
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
glutMainLoop();
return 0;
}
Ostatni program przykładowy (plik reflektor.cpp) prezentuje sposób wykorzystania punktowego światła kierunkowego, tzw. reflektora. Ponadto program umożliwia zmianę wybranych parametrów pracy reflektora. Wartości wszystkich modyfikowanych parametrów źródła światła wyświetlane są na dole okna programu.
W celu łatwiejszej interpretacji uzyskanego efektu oświetlenia obiektu w miejscu położenia źródła światła program rysuje niewielką czerwoną kulę (barwa kuli nie ma nic wspólnego z kolorem źródła światła). Jednym ze sposobów aby kula otrzymała tę barwę jest chwilowe wyłączenie źródła światła
GL_LIGHT0. Najłatwiej jest to zrealizować odkładając na stos zmienne stanu dotyczące oświetlenia, a następnie po modyfikacji wybranych parametrów materiału i narysowaniu kuli pobrać ze stosu wcześniej odłożone zmienne.
Odłożenie na stos wybranych grup zmiennych stanu realizuje funkcja:
void glPushAttrib( GLbitfield mask )
Parametr mask to maska bitowa określająca jaka grupa lub grupy stanu mają zostać odłożone na stos. Grupy zmiennych stanu określają następujące stałe:
Stałe te można w razie potrzeby łączyć przy pomocy bitowych operatorów logicznych. Zauważmy, że dwie spośród wymienionych stałych używamy już od dłuższego czasu w każdym przykładowym programie. Są to stałe:
GL_COLOR_BUFFER_BIT i
GL_DEPTH_BUFFER_BIT.
Przywrócenie wartości zmiennych stanu wcześniej odłożonych na stos realizuje funkcja:
Dodajmy jeszcze, że stała
GL_MULTISAMPLE_BIT została wprowadzona w wersji 1.3 biblioteki OpenGL. Wcześniej technikę wielopróbkowania obsługiwały rozszerzenia ARB multisample i SGIS multisample, przy czym opisywana stała miała odpowiednio oznaczenia:
GL_MULTISAMPLE_BIT_ARB i
GL_MULTISAMPLE_BIT_EXT.
Efekty działania programu ilustrują trzy rysunki. Pierwszy rysunek (18) przedstawia oświetlenie obiektu silnie skupioną i zarazem wąską wiązką światła. Efekt jest zgodny z oczekiwaniami. Także drugi rysunek (19), przedstawiający oświetlenie obiektu wąską ale słabo skupiona wiązką światła, daje spodziewany efekt. Jednak na trzecim rysunku (20) widzimy, że próba oświetlenia światłem kierunkowym obiektu posiadającego małą ilość wierzchołków może dać w efekcie brak oświetlenia. Jest to związane z modelem oświetlenia Gourauda przyjętym w bibliotece OpenGL, który wylicza oświetlenie wyłącznie dla wierzchołków. Jeżeli zatem w wiązce światła reflektora nie znajdzie się żaden wierzchołek oświetlanego obiektu, OpenGL nie wykona dla tego obiektu obliczeń związanych z oświetleniem.
Choć opisanej wady modelu oświetlenia biblioteki OpenGL nie można usunąć, to stosunkowo łatwo można uniknąć związanych z tym objawów. Najprostszą i najabardziej skuteczną metodą jest zwiększenie ilości wierzchołków, z których składa się obiekt.
Plik reflektor.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "colors.h"
#include "materials.h"
PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL;
enum
{
SPHERE,
TEAPOT,
CONE,
TORUS,
CUBE,
DODECAHEDRON,
OCTAHEDRON,
TETRAHEDRON,
ICOSAHEDRON,
BRASS,
BRONZE,
POLISHED_BRONZE,
CHROME,
COPPER,
POLISHED_COPPER,
GOLD,
POLISHED_GOLD,
PEWTER,
SILVER,
POLISHED_SILVER,
EMERALD,
JADE,
OBSIDIAN,
PEARL,
RUBY,
TURQUOISE,
BLACK_PLASTIC,
BLACK_RUBBER,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat scale = 1.0;
const GLfloat * ambient = BrassAmbient;
const GLfloat * diffuse = BrassDiffuse;
const GLfloat * specular = BrassSpecular;
GLfloat shininess = BrassShininess;
int object = SPHERE;
GLfloat light_position[ 4 ] =
{
0.0, 0.0, 2.0, 1.0
};
GLfloat light_rotatex = 0.0;
GLfloat light_rotatey = 0.0;
GLfloat spot_direction[ 3 ] =
{
0.0, 0.0, - 1.0
};
GLfloat spot_cutoff = 180.0;
GLfloat spot_exponent = 128.0;
GLfloat constant_attenuation = 1.0;
GLfloat linear_attenuation = 0.0;
GLfloat quadratic_attenuation = 0.0;
void DrawString( GLint x, GLint y, char * string )
{
glWindowPos2i( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0, 0 );
glRotatef( rotatey, 0, 1.0, 0 );
glScalef( scale, scale, scale );
glMaterialfv( GL_FRONT, GL_AMBIENT, ambient );
glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse );
glMaterialfv( GL_FRONT, GL_SPECULAR, specular );
glMaterialf( GL_FRONT, GL_SHININESS, shininess );
glLightf( GL_LIGHT0, GL_SPOT_CUTOFF, spot_cutoff );
glLightf( GL_LIGHT0, GL_SPOT_EXPONENT, spot_exponent );
glLightf( GL_LIGHT0, GL_CONSTANT_ATTENUATION, constant_attenuation );
glLightf( GL_LIGHT0, GL_LINEAR_ATTENUATION, linear_attenuation );
glLightf( GL_LIGHT0, GL_QUADRATIC_ATTENUATION, quadratic_attenuation );
glPushMatrix();
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( light_rotatex, 1.0, 0, 0 );
glRotatef( light_rotatey, 0, 1.0, 0 );
glTranslatef( light_position[ 0 ], light_position[ 1 ], light_position[ 2 ] );
glLightfv( GL_LIGHT0, GL_POSITION, light_position );
glLightfv( GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction );
glPushAttrib( GL_LIGHTING_BIT );
glDisable( GL_LIGHT0 );
glMaterialfv( GL_FRONT, GL_EMISSION, Red );
glutSolidSphere( 0.1, 30, 20 );
glPopAttrib();
glPopMatrix();
switch( object )
{
case SPHERE:
glutSolidSphere( 1.0, 50, 40 );
break;
case TEAPOT:
glutSolidTeapot( 1 );
break;
case CONE:
glutSolidCone( 1, 1, 50, 40 );
break;
case TORUS:
glutSolidTorus( 0.3, 1, 40, 50 );
break;
case CUBE:
glutSolidCube( 1 );
break;
case DODECAHEDRON:
glutSolidDodecahedron();
break;
case OCTAHEDRON:
glutSolidOctahedron();
break;
case TETRAHEDRON:
glutSolidTetrahedron();
break;
case ICOSAHEDRON:
glutSolidIcosahedron();
break;
}
char string[ 200 ];
GLfloat vec[ 4 ];
glColor3fv( Black );
glGetLightfv( GL_LIGHT0, GL_POSITION, vec );
sprintf( string, "GL_POSITION = (%f,%f,%f,%f)", vec[ 0 ], vec[ 1 ], vec[ 2 ], vec[ 3 ] );
DrawString( 2, 2, string );
glGetLightfv( GL_LIGHT0, GL_SPOT_DIRECTION, vec );
sprintf( string, "GL_SPOT_DIRECTION = (%f,%f,%f)", vec[ 0 ], vec[ 1 ], vec[ 2 ] );
DrawString( 2, 16, string );
glGetLightfv( GL_LIGHT0, GL_SPOT_CUTOFF, vec );
sprintf( string, "GL_SPOT_CUTOFF = %f", vec[ 0 ] );
DrawString( 2, 30, string );
glGetLightfv( GL_LIGHT0, GL_SPOT_EXPONENT, vec );
sprintf( string, "GL_SPOT_EXPONENT = %f", vec[ 0 ] );
DrawString( 2, 44, string );
glGetLightfv( GL_LIGHT0, GL_CONSTANT_ATTENUATION, vec );
sprintf( string, "GL_CONSTANT_ATTENUATION = %f", vec[ 0 ] );
DrawString( 2, 58, string );
glGetLightfv( GL_LIGHT0, GL_LINEAR_ATTENUATION, vec );
sprintf( string, "GL_LINEAR_ATTENUATION = %f", vec[ 0 ] );
DrawString( 2, 72, string );
glGetLightfv( GL_LIGHT0, GL_QUADRATIC_ATTENUATION, vec );
sprintf( string, "GL_QUADRATIC_ATTENUATION = %f", vec[ 0 ] );
DrawString( 2, 86, string );
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( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void Keyboard( unsigned char key, int x, int y )
{
switch( key )
{
case '+':
scale += 0.05;
break;
case '-':
if( scale > 0.05 )
scale -= 0.05;
break;
case 'S':
if( spot_cutoff == 90 )
spot_cutoff = 180;
else
if( spot_cutoff < 90 )
spot_cutoff++;
break;
case 's':
if( spot_cutoff == 180 )
spot_cutoff = 90;
else
if( spot_cutoff > 0 )
spot_cutoff--;
break;
case 'E':
if( spot_exponent < 128 )
spot_exponent++;
break;
case 'e':
if( spot_exponent > 0 )
spot_exponent--;
break;
case 'C':
constant_attenuation += 0.1;
break;
case 'c':
if( constant_attenuation > 0 )
constant_attenuation -= 0.1;
break;
case 'L':
linear_attenuation += 0.1;
break;
case 'l':
if( linear_attenuation > 0 )
linear_attenuation -= 0.1;
break;
case 'Q':
quadratic_attenuation += 0.1;
break;
case 'q':
if( quadratic_attenuation > 0 )
quadratic_attenuation -= 0.1;
break;
}
DisplayScene();
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_LEFT:
light_rotatey -= 5;
break;
case GLUT_KEY_RIGHT:
light_rotatey += 5;
break;
case GLUT_KEY_DOWN:
light_rotatex += 5;
break;
case GLUT_KEY_UP:
light_rotatex -= 5;
break;
}
DisplayScene();
}
void Menu( int value )
{
switch( value )
{
case SPHERE:
object = SPHERE;
DisplayScene();
break;
case TEAPOT:
object = TEAPOT;
DisplayScene();
break;
case CONE:
object = CONE;
DisplayScene();
break;
case TORUS:
object = TORUS;
DisplayScene();
break;
case CUBE:
object = CUBE;
DisplayScene();
break;
case DODECAHEDRON:
object = DODECAHEDRON;
DisplayScene();
break;
case OCTAHEDRON:
object = OCTAHEDRON;
DisplayScene();
break;
case TETRAHEDRON:
object = TETRAHEDRON;
DisplayScene();
break;
case ICOSAHEDRON:
object = ICOSAHEDRON;
DisplayScene();
break;
case BRASS:
ambient = BrassAmbient;
diffuse = BrassDiffuse;
specular = BrassSpecular;
shininess = BrassShininess;
DisplayScene();
break;
case BRONZE:
ambient = BronzeAmbient;
diffuse = BronzeDiffuse;
specular = BronzeSpecular;
shininess = BronzeShininess;
DisplayScene();
break;
case POLISHED_BRONZE:
ambient = PolishedBronzeAmbient;
diffuse = PolishedBronzeDiffuse;
specular = PolishedBronzeSpecular;
shininess = PolishedBronzeShininess;
DisplayScene();
break;
case CHROME:
ambient = ChromeAmbient;
diffuse = ChromeDiffuse;
specular = ChromeSpecular;
shininess = ChromeShininess;
DisplayScene();
break;
case COPPER:
ambient = CopperAmbient;
diffuse = CopperDiffuse;
specular = CopperSpecular;
shininess = CopperShininess;
DisplayScene();
break;
case POLISHED_COPPER:
ambient = PolishedCopperAmbient;
diffuse = PolishedCopperDiffuse;
specular = PolishedCopperSpecular;
shininess = PolishedCopperShininess;
DisplayScene();
break;
case GOLD:
ambient = GoldAmbient;
diffuse = GoldDiffuse;
specular = GoldSpecular;
shininess = GoldShininess;
DisplayScene();
break;
case POLISHED_GOLD:
ambient = PolishedGoldAmbient;
diffuse = PolishedGoldDiffuse;
specular = PolishedGoldSpecular;
shininess = PolishedGoldShininess;
DisplayScene();
break;
case PEWTER:
ambient = PewterAmbient;
diffuse = PewterDiffuse;
specular = PewterSpecular;
shininess = PewterShininess;
DisplayScene();
break;
case SILVER:
ambient = SilverAmbient;
diffuse = SilverDiffuse;
specular = SilverSpecular;
shininess = SilverShininess;
DisplayScene();
break;
case POLISHED_SILVER:
ambient = PolishedSilverAmbient;
diffuse = PolishedSilverDiffuse;
specular = PolishedSilverSpecular;
shininess = PolishedSilverShininess;
DisplayScene();
break;
case EMERALD:
ambient = EmeraldAmbient;
diffuse = EmeraldDiffuse;
specular = EmeraldSpecular;
shininess = EmeraldShininess;
DisplayScene();
break;
case JADE:
ambient = JadeAmbient;
diffuse = JadeDiffuse;
specular = JadeSpecular;
shininess = JadeShininess;
DisplayScene();
break;
case OBSIDIAN:
ambient = ObsidianAmbient;
diffuse = ObsidianDiffuse;
specular = ObsidianSpecular;
shininess = ObsidianShininess;
DisplayScene();
break;
case PEARL:
ambient = PearlAmbient;
diffuse = PearlDiffuse;
specular = PearlSpecular;
shininess = PearlShininess;
DisplayScene();
break;
case RUBY:
ambient = RubyAmbient;
diffuse = RubyDiffuse;
specular = RubySpecular;
shininess = RubyShininess;
DisplayScene();
break;
case TURQUOISE:
ambient = TurquoiseAmbient;
diffuse = TurquoiseDiffuse;
specular = TurquoiseSpecular;
shininess = TurquoiseShininess;
DisplayScene();
break;
case BLACK_PLASTIC:
ambient = BlackPlasticAmbient;
diffuse = BlackPlasticDiffuse;
specular = BlackPlasticSpecular;
shininess = BlackPlasticShininess;
DisplayScene();
break;
case BLACK_RUBBER:
ambient = BlackRubberAmbient;
diffuse = BlackRubberDiffuse;
specular = BlackRubberSpecular;
shininess = BlackRubberShininess;
DisplayScene();
break;
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 );
}
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifndef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 4 )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress( "glWindowPos2i" );
}
else
if( glutExtensionSupported( "GL_ARB_window_pos" ) )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress
( "glWindowPos2iARB" );
}
else
{
printf( "Brak rozszerzenia ARB_window_pos!\n" );
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "Reflektor" );
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
int MenuObject = glutCreateMenu( Menu );
glutAddMenuEntry( "Kula", SPHERE );
glutAddMenuEntry( "Czajnik", TEAPOT );
#ifdef WIN32
glutAddMenuEntry( "Stożek", CONE );
glutAddMenuEntry( "Torus", TORUS );
glutAddMenuEntry( "Sześcian", CUBE );
glutAddMenuEntry( "Dwunastościan", DODECAHEDRON );
glutAddMenuEntry( "Ośmiościan", OCTAHEDRON );
glutAddMenuEntry( "Czworościan", TETRAHEDRON );
glutAddMenuEntry( "Dwudziestościan", ICOSAHEDRON );
#else
glutAddMenuEntry( "Stozek", CONE );
glutAddMenuEntry( "Torus", TORUS );
glutAddMenuEntry( "Szescian", CUBE );
glutAddMenuEntry( "Dwunastoscian", DODECAHEDRON );
glutAddMenuEntry( "Osmioscian", OCTAHEDRON );
glutAddMenuEntry( "Czworoscian", TETRAHEDRON );
glutAddMenuEntry( "Dwudziestoscian", ICOSAHEDRON );
#endif
int MenuMaterial = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Mosiądz", BRASS );
glutAddMenuEntry( "Brąz", BRONZE );
glutAddMenuEntry( "Polerowany brąz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedź", COPPER );
glutAddMenuEntry( "Polerowana miedź", POLISHED_COPPER );
glutAddMenuEntry( "Złoto", GOLD );
glutAddMenuEntry( "Polerowane złoto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perła", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#else
glutAddMenuEntry( "Mosiadz", BRASS );
glutAddMenuEntry( "Braz", BRONZE );
glutAddMenuEntry( "Polerowany braz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedz", COPPER );
glutAddMenuEntry( "Polerowana miedz", POLISHED_COPPER );
glutAddMenuEntry( "Zloto", GOLD );
glutAddMenuEntry( "Polerowane zloto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perla", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#endif
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
glutAddSubMenu( "Obiekt", MenuObject );
#ifdef WIN32
glutAddSubMenu( "Materiał", MenuMaterial );
#else
glutAddSubMenu( "Material", MenuMaterial );
#endif
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
glutMainLoop();
return 0;
}