WOJSKOWA AKADEMIA TECHNICZNA
im. Jarosława Dąbrowskiego
Laboratorium z przedmiotu
GRAFIKA KOMPUTEROWA
SPRAWOZDANIE
Z ĆWICZENIA LABORATORYJNEGO
Nr 4
Temat ćwiczenia: Modelowanie oświetlenia
Prowadzący: dr inż. Salamon Marek
Wykonał: Sebastian Cygert
Grupa: I8X4S1
Data wykonania ćwiczenia: 21.12.2009
Treść zadania
W ramach ćwiczenia laboratoryjnego otrzymałem do wykonania zestaw 2:
Wykorzystując biblioteki OpenGL i GLUT napisać program przedstawiający perspektywiczny obraz obiektu o następujących parametrach:
Typ obiektu: walec o zmiennej parzystej liczbie podziałów pionowych i poziomych,
Właściwości materiału 1: brązowy błyszczący (widziany w białym świetle),
Właściwości materiału 2: fioletowy emitujący (widziany w białym świetle),
Sposób przyporządkowywania materiałów do obiektu zgodnie ze wzorem: pasy poziome z uwzględnieniem podziałów poziomych.
Obiekt należy oświetlić dwoma źródłami światła o następujących parametrach:
Źródło nr 1:
typ: reflektor (ang. spot),
kolor: źółty,
natężenie: 1,
kąt odcięcia: 300,
położenie: zmienne po orbicie kołowej o środku w punkcie S(0, 0, 0) z możliwością interaktywnej zmiany następujących parametrów:
promienia orbity,
prędkości kątowej (3 różne prędkości),
kąta nachylenia orbity do osi OX,
kierunek świecenia: na obiekt.
Źródło nr 2:
typ: kierunkowe,
kolor: niebieski,
natężenie: 0.8,
położenie: stałe w punkcie P(-10, -5, 10) układu współrzędnych sceny,
kierunek świecenia: na obiekt.
Program powinien umożliwiać:
interaktywne, niezależne włączanie i wyłączanie źródeł światła;
interaktywną zmianę liczby podziałów pionowych i poziomych bryły;
interaktywną zmianę wielkości bryły;
interaktywną zmianę położenia obserwatora poprzez podanie następujących parametrów:
- odległości obserwatora od środka układu współrzędnych sceny;
- kąta obrotu wokół osi OY w zakresie [00, 3600] z krokiem 10.
Oświetlony obiekt powinien zawsze znajdować się w centralnej części okna.
Sposób rozwiązania zadania.
Zadania zostały zrealizowane w środowisku programistycznym Microsoft Visual Studio przy pomocy języka C#. Do poprawnego rozwiązania konieczna była umiejętność budowania brył połączona z interaktywną zmianą położenia obserwatora, liczbą podziałów pionowych i poziomych poznane na wcześniejszych zajęciach laboratoryjnych. Ponadto koniecznie było ustawienie odpowiednich właściwości oświetlenia oraz materiału.
Użyte metody oraz kod źródłowy do poszczególnych poleceń.
Realizację treści zadania rozpocząłem od zdefiniowania parametrów materiałów:
//brazowy blyszczacy
const GLfloat ambient_m1[4] = { 0.8f, 0.3f, 0.3f, 1.0f };
const GLfloat diffuse_m1[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
const GLfloat specular_m1[4] = { 0.8f, 0.3f, 0.3f, 1.0f };
// material nr 2 fioletowy emitujacy
const GLfloat ambient_m2[4] = { 1.0f, 0.0f, 1.0f, 1.0f };
const GLfloat diffuse_m2[4] = { 1.0f, 0.0f, 1.0f, 1.0f };
const GLfloat specular_m2[4] = { 1.0f, 0.0f, 1.0f, 1.0f };
const GLfloat emission_m2[4] = { 0.1f, 0.0f, 0.1f, 1.0f } ;
Aby odpowiednio ustawić właściwości materiału należało odpowiednio ustawiać zmienne definiowane przed 4 wartości RGBA
a) ambient określa stopień odbicia światła otaczającego;
b) diffuse - stopień rozproszenia światła rozproszonego,
c) specular - stopień odbicia światła odbitego,
d) emission -określają światło emitowane przez obiekt. Ten ostatni parametr domyślnie jest ustawiony tak, że materiał jest emitujący. Dlatego pierwsza tablica nie zawiera ustawień tego parametru.
Dobranie powyższych parametrów wymagało znajomości powyższych zmiennych. Przy znajdowaniu ustawień dla pierwszego materiału kierowałem się zasadą, która mówi, że powierzchnie błyszczące powinny mieć małe składowe parametru diffuse, zaś spore parametru specular. Materiał drugi wymagał jedynie dobrania odpowiednich wartości dla parametru emission.
Następnym krokiem było napisanie funkcji, które będą korzystały ze zdefiniowanych materiałów w celu ustawienia ich w odpowiednim momencie modelowania obiektu. Funkcje te przedstawiają się następująco:
void Material1() {
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_m1);
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_m1);
glMaterialfv(GL_FRONT, GL_SPECULAR, specular_m1);
glMaterialf(GL_FRONT, GL_SHININESS, 128.0f);
}
void Material2() {
glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_m2);
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_m2);
glMaterialfv(GL_FRONT, GL_SPECULAR, specular_m2);
glMaterialf(GL_FRONT, GL_SHININESS, 10.0f);
glMaterialfv(GL_FRONT, GL_EMISSION, emission_m2);
}
Został tutaj zdefiniowany nowy parametr : GL_SHININESS. Jest on definiowany przez pojedyńczą wartość z zakresu [0, 128], która określa w jakim stopniu kąt, pod jakim obserwator widzi powierzchnię musi być zgodny z kątem lustrzanego odbicia od niej promieni świetlnych, aby widoczny był efekt odbłysku. Duże wartości powodują zawężenie obszaru występowania odbłysku. Domyślna wartość 0 sprawia, że zjawisko zachodzi zawsze.
Gdy zdefiniowane zostały już materiały mogłem przystąpic do zbudowania walca o zmiennej parzystej liczbie podziałów poziomych i pionowych ponieważ było to zadanie podobne do tego z poprzednich zajęć laboratoryjnych i nie przysporzyło większych problemów.
double dH, dAlfa;
int i, j,parzystosc=1;
// Wyznaczenie kata wyznaczajacego pojedynczy wycinek pionowy
dAlfa = 360.0L/(double)nh;
// Wyznaczenie wysokosci pojedynczego wycinka poziomego
dH = h/(double)nv;
// Wyznaczanie wierzcholkow i wektorow normalnych powierzchni bocznych
for (i = 0; floor((i+1)*dH*1.0E10) <= floor(h*1.0E10); i++)
{
glBegin(GL_TRIANGLE_STRIP);
glNormal3f(0.0, 0.0, 1.0);
glVertex3f(0.0, (i + 1)*dH, r);
glVertex3f(0.0, i*dH, r);
if (i%2 == 0) {
Material1();
parzystosc = 1;
}
if (i%2 == 1) {
Material2();
parzystosc = 0;
}
for (j = 1; j*dAlfa <= 360.0L + dAlfa; j++)
{
glNormal3f(sin(DEG2RAD(j*dAlfa)), 0.0, cos(DEG2RAD(j*dAlfa)));
glVertex3f(r*sin(DEG2RAD(j*dAlfa)), (i + 1)*dH, r*cos(DEG2RAD(j*dAlfa)));
glVertex3f(r*sin(DEG2RAD(j*dAlfa)), i*dH, r*cos(DEG2RAD(j*dAlfa)));
}
glEnd();
}
// Wyznaczenie wierzcholkow i wektorow normalnych dolnej podstawy
glBegin(GL_TRIANGLE_FAN);
glNormal3f(0.0, -1.0, 0.0);
glVertex3f(0.0, 0.0, 0.0);
for (i = 0; i * dAlfa <= 360.0L + dAlfa; i++)
{
glVertex3f(r*sin(DEG2RAD(i*dAlfa)), 0.0, r*cos(DEG2RAD(i*dAlfa)));
}glEnd();
// Wyznaczenie wierzcholkow i wektorow normalnych gornej podstawy
glBegin(GL_TRIANGLE_FAN);
glNormal3f(0.0, 1.0, 0.0);
glVertex3f(0.0, h, 0.0);
for (i = 0; i * dAlfa <= 360.0L + dAlfa; i++){
glVertex3f(r*sin(DEG2RAD(i*dAlfa)), h, r*cos(DEG2RAD(i*dAlfa)));
}
Jak widać z powyższego listingu, modelowanie walca odbywa się w trzech etapach. Najpierw tworzona jest ściana boczna. Na samym początku zewnętrznej pętli przechodzącej przez wszystkie podziały poziome, ustawiany jest materiał. To jaki zostanie wybrany, zależy od aktualnego ustawienia zmiennej mat. Wewnątrz pętli wewnętrznej, przechodzącej przez wszystkie podziały pionowe, definiowane są odpowiednie punkty (wierzchołki prostokątów). Jest ich 4, gdyż zdecydowałem się na tryb GL_QUADS.
Jednak 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). Tym zajmuje się zestaw instrukcji pod koniec wewnętrznej pętli (wykorzystany jest iloczyn wektorowy).
Rysowanie podstaw odbywa się w bardzo podobny sposób. Na koniec funkcji rysującej walec znajduje się wywołanie funkcji ustawiającej światło. Jest ważne, aby wszystkie parametry ustawić tuż po narysowaniu obiektu.
Następnie należało zdefiniować parametry światła:
/reflektor
const GLfloat diffuse_s1[4] = { 1.0f, 1.0f, 0.0f, 1.0f};
const GLfloat ambient_s1[4] = { 1, 1, 0, 1 };
const GLfloat specular_s1[4] = { 1.0f, 1.0f, 0.0f, 1.0f};
const GLfloat position_s1[4] = { 0.0f, 0.0f, 1.0f, 1.0f};
const GLfloat direction_s1[4] = { 0.0f, 0.0f, -1.0f};
const GLfloat spot_direction_s1[4] = { 0.0f, 0.0f, -1.0f};
const GLfloat ambient_s2[4] = {0.8, 0, 0, 1 }; //kierunkowe
const GLfloat diffuse_s2[4] = { 0.0f, 0.0f, 1.0f, 0.8f};
const GLfloat specular_s2[4] ={ 0.0f, 0.0f, 1.0f, 0.8f};
const GLfloat position_s2[4] = { -10.0f, -5.0f, 10.0f, 1.0f};
const GLfloat spot_direction_s2[3] ={ 10.0f, 5.0f, -10.0f}; // spot direction
Zmienne boolowskie określają, czy dane źródło światła jest włączone - domyślnie oba źródła są wyłączone.
Parametr position określa położenie źródła światła, jak się okaże składowe tego parametru podane są w układzie współrzędnych sceny. Parametr spot direction to znormalizowany, trójwspółrzędny wektor określający kierunek reflektora.
Tym razem odpowiednie dobranie powyższych parametrów nie było już takie trudne. Zatem odbyło się to szybko i przeszedłem do realizacji funkcji ustawiających wyżej zdefiniowane źródła światła:
void Swiatlo1() {
glPushMatrix();
glEnable(GL_LIGHTING);
glRotatef(kat_y, 1, 0, 0);
glRotatef(kat_x, 0, 1, 0);
glRotatef(kat_z, 0, 0, 1);
glTranslatef(R_L2, R_L1, 0);
glPushMatrix();
glDisable(GL_LIGHTING);
glColor3f(1.0f, 1.0f, 0.0f);
glutSolidSphere(0.4f, 30, 30);
glEnable(GL_LIGHTING);
glPopMatrix();
glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_s1);
glLightfv(GL_LIGHT1, GL_SPECULAR, specular_s1);
glLightfv(GL_LIGHT1, GL_POSITION, position_s1);
glLightf (GL_LIGHT0, GL_SPOT_CUTOFF, 30.0f); //kat odciecia
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction_s1);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, direction_s1);
glPopMatrix();
}
void Swiatlo2() {
glLightfv(GL_LIGHT2, GL_DIFFUSE, diffuse_s2);
glLightfv(GL_LIGHT2, GL_SPECULAR, specular_s2);
glLightfv(GL_LIGHT2, GL_POSITION, position_s2);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, spot_direction_s2);
}
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órych zastosowanie widać na powyższym listingu.
Słowo komentarza należy się pierwszej funkcji. Mianowicie jak widać położenie źródła światła nr 1 zależy od pewnych zmiennych, które użytkownik może interaktywnie zmieniać. Z powyższego listingu wynika także, że reprezentacją tegoż źródła jest kula o kolorze żółtym. Parametr spot exponent to wykładnik tłumienia kątowego reflektora, zaś parametr spot cutoff to po prostu kąt odcięcia reflektora.
Na końcu przystąpiłem do obsługi funkcji klawiatury realizującej :
a) zmiana odległości obserwatora
switch(klawisz)
{
case '+': OBSERWATOR_ODLEGLOSC++;
break;
case '-': OBSERWATOR_ODLEGLOSC--;
break;
b) obrót obserwatora wokól osi OY
case 'a': OBSERWATOR_OBROT_Y++;
break;
case 'd': OBSERWATOR_OBROT_Y--;
break;
c) odległość reflektora
case 'q':
R_L2+=0.1;
break;
case 'Q':
R_L2-=0.1;
break;
d) ilośc podziałów pionowych oraz poziomych
case '3':
lPionowych = (lPionowych == LPION_MAX)? LPION_MAX : lPionowych + 2;
break;
case '#':
lPionowych = (lPionowych == LPION_MIN)? LPION_MIN : lPionowych - 2;
break;
case '4':
lPoziomych = (lPoziomych == LPOZ_MAX)? LPOZ_MAX : lPoziomych + 2;
break;
case '$':
lPoziomych = (lPoziomych == LPOZ_MIN)? LPOZ_MIN : lPoziomych - 2;
break;
e) wysokość walca
case 'w':
wysokosc = (wysokosc == WYS_MAX) ? WYS_MAX : wysokosc + 1;
break;
case 'W':
wysokosc = (wysokosc == 1) ? wysokosc : wysokosc - 1;
break;
f) promień walca
case 'p':
promien = (promien == R_MAX) ? R_MAX : promien + 1;
break;
case 'P':
promien = (promien == 1) ? promien : promien - 1;
break;
case 'r':
UstawDomyslneWartosciParametrow();
break;
case '5':
g) prędkość relfektora
if(roznica<2) roznica=roznica+0.5;
break;
case '%' :
if(roznica>1) roznica=roznica-0.5;
break;
h) kąt nachylenia reflektora
case '6':
kat_y+=0.5;
break;
case '^':
kat_y-=0.5;
break;
i) włączenie I wyłaczenie reflektora
case '2': glEnable(GL_LIGHT1);
break;
case '@': glDisable(GL_LIGHT1);
break;
j) właczenie I wyłaczęnie światła kierunkowego;
case '1': glEnable(GL_LIGHT2);
break;
case '!': glDisable(GL_LIGHT2);
break;
case 27:
exit(0);
}Jak widać obsługuje ona jedynie położenie obserwatora. Zmienne phi oraz theta wykorzystywane są w obliczeniach położenia obserwatora. Należy bowiem zauważyć, że obserwator zrealizowany został w układzie współrzędnych sferycznych (porusza się po sferze, w której środku jest obserwowany obiekt), zatem pojawienie się tych dwóch zmiennych jest naturalne.
- 7 -