WOJSKOWA AKADEMIA TECHNICZNA
LABORATORIUM Z
GRAFIKI KOMPUTEROWEJ
SPRAWOZDANIE
Z ĆWICZENIA 4
Temat: Modelowanie oświetlenia.
Wykonał: Albin Sadowski
Grupa: I9X6S1
Prowadzący: mgr inż. Wojciech Sulej
Data: 17.01.2011 r.1. Cel ćwiczenia
Zadanie 3
Wykorzystując biblioteki OpenGL i GLUT napisać program przedstawiający perspektywistyczny obraz obiektu o następujących parametrach:
1. Typ obiektu: ½ elipsoidy o stosunku osi 2:3:5 ciętej wzdłuż osi najdłuższej zmiennej parzystej liczbie podziałów pionowych i poziomych,
2. Właściwości materiału nr 1: zielony matowy (widziany w białym świetle),
3. Właściwości materiału nr 2: fioletowy błyszczący (widziany w białym świetle),
4. Sposób przyporządkowania materiałów do obiektu zgodnie ze wzorem: pasy poziome względem osi najdłuższej z uwzględnieniem podziałów poziomych.
Obiekt należy oświetlić dwoma źródłami światłą o następujących parametrach:
Światło białe sceny (włączenie/wyłączenie)
Źródło nr 1:
- typ: reflektor (ang. spot)
- kolor: czerwony,
- 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ą interaktywne
zmiany następujących parametrów:
- promienia orbity,
- kąta nachylenia orbity do osi OX,
- kąta nachylenia orbity do osi OZ,
- kierunek świecenia: na obiekt.
3) Źródło nr 2:
- typ: kierunkowe
- kolor: żółty
- natężenie: 0.7
- położenie: stałe w punkcie P(-10, 10, 10) układu współrzędnych sceny.
- kierunek świecenia: na obiekt
Program powinien umożliwić interaktywne, niezależne włączenie i wyłączanie źródeł światła.
Należy również zapewnić użytkownikowi możliwość zmiany położenia obserwator poprzez podanie następujących parametrów:
- odległości obserwatora od środka układu współrzędnych sceny,
- wysokości względem płaszczyzny XZ,
- 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.
W programie uwzględnić możliwość interakcyjnej zmiany położenia obserwatora poprzez podanie następujących parametrów:
1. Odległości obserwatora od obiektu,
2. Wysokości obserwatora względem płaszczyzny, na której położony jest obiekt,
3. Kąta obrotu wokół obiektu w zakresie [0, 360] z krokiem 1.
UWAGA: Obserwator jest zawsze zwrócony przodem w kierunku obiektu.
2. Sposób rozwiązania zadania
Zadanie należało rozpocząć od zamodelowania elipsoidy. W tym celu należało zastosować współrzędne sferyczne:
W pierwszej pętli następuje przejście od 0 do KATb - 0.01 (- 0.01 ze względu na błąd
w przybliżenia liczb typu float), następnie w zagnieżdżonej pętli od j = 0 do KATa - 0.01 znajdują
się instrukcje odpowiedzialne za wyznaczanie czterech punktów, które będą łączone.
Wyznaczone punkty należało podać w odpowiedniej kolejności by zostały dobrze połączone w trybie GL_QUADS. Dodatkowo dla każdego wierzchołka należało wyznaczyć wektor normalny przy pomocy funkcji glNormal3f().
for (i = 0; i <= KATb - 0.01; i = i + KATb/(nh))
{
for (j = 0.0; j <= KATa - 0.01; j = j + (KATa/nv))
{
x = A*cos(j*M_PI/180)*sin(i*M_PI/180);
y = C*cos(i*M_PI/180);
z = B*sin(j*M_PI/180)*sin(i*M_PI/180);
tmpj = j + (KATa/nv);
tmpi = i + (KATb/(nh));
tmpx = A*cos(tmpj*M_PI/180)*sin(i*M_PI/180);
tmpy = C*cos(i*M_PI/180);
tmpz = B*sin(tmpj*M_PI/180)*sin(i*M_PI/180);
kolex = A*cos(j*M_PI/180)*sin(tmpi*M_PI/180);
koley = C*cos(tmpi*M_PI/180);
kolez = B*sin(j*M_PI/180)*sin(tmpi*M_PI/180);
koletmpx = A*cos(tmpj*M_PI/180)*sin(tmpi*M_PI/180);
koletmpy = C*cos(tmpi*M_PI/180);
koletmpz = B*sin(tmpj*M_PI/180)*sin(tmpi*M_PI/180);
glBegin(GL_QUADS);
glNormal3f(tmpx, tmpy, tmpz);
glVertex3f(tmpx, tmpy, tmpz);
glNormal3f(x, y, z);
glVertex3f(x, y, z);
glNormal3f(kolex, koley, kolez);
glVertex3f(kolex, koley, kolez);
glNormal3f(koletmpx, koletmpy, koletmpz);
glVertex3f(koletmpx, koletmpy, koletmpz);
glEnd();
}
}
Z tego względu, że jest to ½ elipsoidy trzeba było wyrysować ściany boczne ścięcia bryły.
Ze względu na położenie tej ściany bocznej, każdy punkt powinien posiadać wektor normalny prostopadły do tej ściany, który jest równy glNormal3f(0, 0, -1).
// pierwsza sciana boczna
if (j == 0)
{
glBegin(GL_QUADS);
glNormal3f(0, y, -1);
glVertex3f(0, y, 0);
glNormal3f(0, y, -1);
glVertex3f(x, y, z);
glNormal3f(0, koley, -1);
glVertex3f(kolex, koley, kolez);
glNormal3f(0, koley, -1);
glVertex3f(0, koley, 0);
glEnd();
}
// druga sciana boczna
glBegin(GL_QUADS);
glNormal3f(0, tmpy, -1);
glVertex3f(0, tmpy, 0);
glNormal3f(0, tmpy, -1);
glVertex3f(tmpx, tmpy, tmpz);
glNormal3f(0, koletmpy, -1);
glVertex3f(koletmpx, koletmpy, koletmpz);
glNormal3f(0, koletmpy, -1);
glVertex3f(0, koletmpy, 0);
glEnd();
Rys. 1 Rys. 2
Rys. 1, 2. Narysowana ½ elipsoidy.
3. Ustawienie odpowiedniego materiału
W celu zaprojektowanie odpowiedniego materiału należało wpierw się zapoznać
z właściwościami funkcji tj. ambient, diffuse, specular jak również sprawdzić przy jakim zmieszaniu barw RGB możemy otrzymać poszukiwany kolor. Po wykonaniu tego zabiegu otrzymałem taki kod:
GLfloat ambient_m1[4] = {0, 0, 0, 1}; // zielony matowy
GLfloat diffuse_m1[4] = {0, 1, 0, 1};
GLfloat specular_m1[4] = {0, 0, 0, 1};
GLfloat ambient_m2[4] = {0.5, 0.0, 0.5, 1}; // fioletowy błyszczący
GLfloat diffuse_m2[4] = {0.5, 0.0, 0.5, 1};
GLfloat specular_m2[4] = {0.5, 0, 0.5, 1};
Po przypisaniu zmiennym odpowiednich parametrów należało napisać funkcje, które będą ładowały konkretne parametry i tym sposobem tworzyły odpowiedni materiał, będący pokryciem bryły.
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,0);
}
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, 128);
}
Materiał 2 miał być błyszczący więc należało dopisać parametr i odpowiednio ustawić, który jest odpowiedzialny za błyszczenie materiału (GL_SHININESS).
Kolejnym krokiem związanym z wyświetlaniem materiału było napisanie funkcji, która na zmianę będzie wybierała jeden z nich.
void kolorowanie(int i)
{
switch(i)
{
case 0:
Material1();
break;
case 1:
Material2();
break;
}
}
Teraz należało napisać ciąg instrukcji przed każdym wyrysowaniem czworokąta w trakcie rysowania elipsoidy, które umożliwiają wywołanie funkcji o odpowiednim argumencie do nakładania materiału na bryłę.
kolorowanie(kolor);
kolor++;
if (kolor == 2)
kolor = 0;
Rys. 3. Bryła z odpowiednio nałożonymi materiałami.
4. Modelowanie oświetlenia
Modelowanie oświetlenie podobnie jak w przypadku modelowania materiału należało wpierw zaznajomić się z funkcjami, które są odpowiedzialne za otoczenie światła, rozproszenie, lustrzane odbicie, położenie, kierunek świecenia, tłumienie kątowe, stałe tłumienie, tłumienie liniowe i tłumienie kwadratowe.
GLfloat param_swiatla1[10][4] = {
{0.0, 0, 0, 1.0}, // [0] otoczenie
{1.0, 0.0, 0.0, 1.0}, // [1] rozproszenie
{1.0, 0, 0, 1.0}, // [2] lustrzane
{0.0, 0.0, 1.0, 1.0}, // [3] polozenie
{0.0, 0.0, -1.0, 0.0}, // [4] kierunek swiecenia
{0.0, 0.0, 0.0, 0.0}, // [5] tlumienie katowe swiatla
{30.0, 0.0, 0.0, 0.0},// [6] kat odciecia swiatla
{1.0, 0.0, 0.0, 0.0}, // [7] stale tlumienie
{0.0, 0.0, 0.0, 0.0}, // [8] tlumienie liniowe
{0.0, 0.0, 0.0, 0.0}}; // [9] tlumienie kwadratowe
GLfloat param_swiatla2[5][4] = {
{0.0, 0.0, 0.0, 0.0}, // [0] otoczenie
{1.0, 1.0, 0, 0.7}, // [1] rozproszenie
{1.0, 1.0, 0, 7 }, // [2] lustrzane
{-10, 10, 10, 0}, // [3] polozenie
{0.0, 0.0, -1.0, 0.0}, // [4] kierunek swiecenia
};}
Następnie zostały te właściwości przekopiowane do tablic:
memcpy(swiatlo1, param_swiatla1, LPOZ_MENU_SWIATLA*4*sizeof(GLfloat));
memcpy(swiatlo2, param_swiatla2, LPOZ_MENU_SWIATLA*4*sizeof(GLfloat));
Kolejnym krokiem było napisanie funkcji, które będą odpowiednie do wyświetlania poszczególnych świateł. Dla źródła światła nr 1:
void Swiatlo1Reflektor()
{
glPushMatrix();
glRotatef(kat_x, 1, 0, 0);
glRotatef(kat_y, 0, 1, 0);
glRotatef(kat_z, 0, 0, 1);
glTranslatef(r_reflektor, 0, 0);
glLightfv(GL_LIGHT1, GL_AMBIENT, swiatlo1[0]);
glLightfv(GL_LIGHT1, GL_DIFFUSE, swiatlo1[1]);
glLightfv(GL_LIGHT1, GL_SPECULAR, swiatlo1[2]);
glLightfv(GL_LIGHT1, GL_POSITION, swiatlo1[3]);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, swiatlo1[4]);
glLightfv(GL_LIGHT1, GL_SPOT_CUTOFF, swiatlo1[5]);
glutWireSphere(0.5, 8, 8);
glPopMatrix();
}
Wpierw następuje odłożenie na stos (glPushMatrix()), kolejno jest ona odpowiednio obracana (glRotatef()), oddalana (glTranslatef()) a w końcu następuje załadowanie parametrów światła przy pomocy odpowiednich funkcji, które przyjmują wybrane argumenty. Dodatkowo jest umieszczona instrukcja za wyrysowanie sfery (glutWireSphere()) by można było zobaczyć, w którym miejscu znajduje się źródło światła.
Rys. 4 Rys. 5
Rys. 4, 5. Bryła podczas załączonego światła nr 1.
Kolejno funkcja dla źródła światła nr 2:
void Swiatlo2Kierunkowe()
{
glPushMatrix();
glLightfv(GL_LIGHT2, GL_AMBIENT, swiatlo2[0]);
glLightfv(GL_LIGHT2, GL_DIFFUSE, swiatlo2[1]);
glLightfv(GL_LIGHT2, GL_SPECULAR, swiatlo2[2]);
glLightfv(GL_LIGHT2, GL_POSITION, swiatlo2[3]);
glPopMatrix();
}
Jest ona wpierw odkładana na stos, a następnie wykonywane instrukcje odpowiedzialne
za wyświetlenie światła. Zostało to tak zrobione, ponieważ bez względu na zmianę położenia obserwatora to źródło światła będzie zawsze znajdowało się w tym samym miejscu
Rys. 6 Rys. 7
Rys. 6, 7. Bryła podczas załączonego światła nr 2.
5. Interakcyjna zmiana położenia użytkownika
W programie należało umożliwić interakcyjną zmianę położenia obserwatora. W tym celu zostały napisane takie instrukcje w funkcji WyswietlObraz().
glTranslatef(0, 0, -odleglosc_obs);
glRotatef( (180*(asin(wysokosc_obs/odleglosc_obs))/M_PI), 1, 0, 0);
glRotatef(katY, 0, 1, 0);
glTranslatef(0, -wysokosc_obs, 0);
Pierwsze instrukcja jest odpowiedzialna za ustawienie odpowiedniej odległości obserwatora od obiektu względem osi OZ.
Druga instrukcja odpowiada za zmianę wysokości obserwatora w zależności od odległości
od obiektu. W tym celu wykonuje rotacje o kąt równy arcsin, który jako argument przyjmuje stosunek wysokości do odległości. Funkcja asin() zwraca wartość w radianach, wiec należało przejść
na kąt poprzez pomnożenie wyniku przez 180 i podzielenie przez M_PI.
Instrukcja glRotatef() jest odpowiedzialna za możliwość obrotu wokół obiektu. Ostatni glTranslatef() odpowiada za wysokość obserwatora względem płaszczyzny, na której położony jest obiekt.
6. Obsługa obiektu przez użytkownika
Program miał za zadanie umożliwić użytkownikowi zmiany położenia obserwatora, załączanie świateł, zmienianie liczby podziałów poziomych i pionowych poprzez naciśnięcie odpowiedniego klawisza. Zostały one zrealizowane przy pomocy funkcji ObslugaKlawiatury(), która pobiera wciśnięty klawisz a kolejno sprawdza, czy została mu przypisana konkretna akcja. Oprócz wcześniejszych instrukcji dopisałem kilka innych, które są potrzebne do zrealizowania zadań opisanych we wstępie.
case '-':
katY = katY - 2;
break;
case '=':
katY = katY + 2;
break;
case '[':
odleglosc_obs = odleglosc_obs + 1;
break;
case ']':
odleglosc_obs = odleglosc_obs - 1;
break;
case ';':
wysokosc_obs = wysokosc_obs + 1;
break;
case '\'':
wysokosc_obs = wysokosc_obs - 1;
break;
case 't':
r_reflektor = r_reflektor+1;
break;
case 'T':
r_reflektor = r_reflektor-1;
break;
case 's':
kat_y = kat_y-1;
break;
case 'f':
kat_y = kat_y+1;
break;
case 'z':
kat_x = kat_x+1;
break;
case 'x':
kat_x = kat_x-1;
break;
case 'e':
kat_z = kat_z+1;
break;
case 'd':
kat_z = kat_z-1;
break;
case '8':
wlswiatlo0 = -wlswiatlo0;
if (wlswiatlo0 == 1)
glEnable(GL_LIGHT0);
else
glDisable(GL_LIGHT0);
break;
case '9':
wlswiatlo1 = -wlswiatlo1;
if (wlswiatlo1 == 1)
glEnable(GL_LIGHT1);
else
glDisable(GL_LIGHT1);
break;
case '0':
wlswiatlo2 = -wlswiatlo2;
if (wlswiatlo2 == 1)
glEnable(GL_LIGHT2);
else
glDisable(GL_LIGHT2);
break;
Oprócz zmiany by były tylko parzysta liczba podziałów poziomych i pionowych bryły, ważne były funkcje odpowiedzialne za włączanie świateł, np.:
case '8':
wlswiatlo0 = -wlswiatlo0;
if (wlswiatlo0 == 1)
glEnable(GL_LIGHT0);
else
glDisable(GL_LIGHT0);
break;
Domyślnie zmienna wlswiatlo0 = -1 czyli światło GL_LIGHT0 jest wyłączone. Każde naciśnięcie klawisza „8” zmienia stan zmiennej na przeciwny, więc albo włącza albo wyłącza dane światło.
Rys. 8 Rys. 9
Rys. 8. Bryła bez załączonych źródeł światła.
Rys. 9. Bryła z załączonymi wszystkimi źródłami światła.
7. Wnioski
Narysowanie ½ elipsoidy i zamodelowanie materiałów i oświetlenia zostało wykonane zgodnie z oczekiwaniami. Ćwiczenie wymagało zapoznanie się z podstawowymi właściwościami atrybutów materiałów i świateł i odpowiednim ich ustawieniu. Dużą rolę odgrywało tutaj poprawne zamodelowanie elipsoidy i wyznaczenie wektorów normalnych jej wierzchołków.
Podczas ćwiczenia laboratoryjnego popełniłem błąd w wyznaczaniu wektorów normalnych ścian bocznych wycięcia bryły, lecz zostały one poprawione w tym sprawozdaniu.
6