WOJSKOWA AKADEMIA TECHNICZNA
Laboratorium NR 4 z przedmiotu:
Grafika komputerowa
Autor:
Michał Popławski
I9X6S1
Prowadzący:
mgr inż. Wojciech Sulej
Treść wykonywanych zadań.
Zadanie 1
Wykorzystując biblioteki OpenGL i GLUT napisać program przedstawiający perspektywiczny obraz obiektu o następujących parametrach:
1. Typ obiektu: 2/3 walca o zmiennej parzystej liczbie podziałów pionowych i poziomych,
2. Właściwości materiału nr 1: żółty emitujący (widziany w białym świetle),
3. Właściwości materiału nr 2: zielony matowy (widziany w białym świetle),
4. Sposób przyporządkowania materiałów do obiektu zgodnie ze wzorem: pasy pionowe
z uwzględnieniem podziałów pionowych.
Obiekt należy oświetlić dwoma źródłami światła o następujących parametrach:
1) Światło białe sceny (włączanie/wyłączanie)
2) Źródło nr 1:
− typ: reflektor (ang. spot),
− kolor: fioletowy,
− natężenie: 0.9,
− kat odcięcia: 15 stopni,
− 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,
* kata nachylenia orbity do osi OX,
* kata nachylenia orbity do osi OZ,
− kierunek świecenia: na obiekt.
3) Źródło nr 2:
− typ: kierunkowe,
− kolor: zielony,
− natężenie: 0.7,
− 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.
Należy również zapewnić użytkownikowi możliwość zmiany położenia obserwatora 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,
− kata obrotu wokół osi OY w zakresie [0o, 360o] z krokiem 1stopnia.
Oświetlony obiekt powinien zawsze znajdować sie w centralnej części okna.
Wstęp teoretyczny:
Aby przystąpić do wykonania zadania powinniśmy znać dobrze funkcje pochodzące z bibliotek OpenGL i GLUT, które umożliwiają nam rysowanie obiektów za pomocą prymitywów oraz przekształcenia przestrzeni (obroty, przesunięcia, operacje na macierzach). Wszystkie te rzeczy wykonywaliśmy na 2 i 3 laboratoriach dlatego też nie będę opisywać dokładnie tych funkcji, a jedynie zajmę się tymi, które są nowe, a zarazem niezbędne do wykonania ćwiczenia.
Do pokrycia zbudowanej przez nas bryły materiałem musimy określić parametry materiału:
1.GL_AMBIENT - współczynnik odbicia światła otoczenia
2.GL_DIFFUSE -współczynnik odbicia światła rozproszonego
3.GL_SPECULAR - współczynnik odbicia światła lustrzanego
4.GL_SHININESS – połysk
5.GL_EMISSION – kolor światła emitowanego
Każdy z tych parametrów przedstawiamy w postaci wektora wartości, który przedstawia wartości RGBA, gdzie R – red, G – green, B – blue, A – współczynnik określający natężenie barwy (im mniejszy współczynnik, tym bardziej przezroczysty materiał, gdy jest równy 0, to materiał przyjmuje barwę otoczenia). Każdy ze współczynników oprócz połysku przyjmuje wartości od [0.0-1.0]. Połysk przyjmuje wartości [0.0-128.0].
Inicjowanie materiału dokonuje się, np. za pomocą funkcji:
glMaterialfv(GL_FRONT, GL_AMBIENT, material2[0]);
Gdzie 1 parametr określa, że materiałem ma być pokryta widoczna część bryły, 2 parametr mówi o tym, który rodzaj współczynnika będziemy określać i ostatni parametr jest wektorem określającym współczynniki RGBA.
Materiał błyszczący: aby go uzyskać musimy podać większe wartości GL_SPECULAR, a mniejsze GL_DIFFUSE. Dzięki temu uzyskamy efekt dużego odbicia światła i małego pochłaniania, czyli materiał będzie wydawał się błyszczący.
Materiał matowy: do uzyskania tego efektu podajemy większe wartości GL_DIFFUSE, a mniejsze GL_SPECULAR. Dzięki temu materiał będzie dużą cześć światła pochłaniał, odbijając małą jego część i materiał będzie się wydawał matowy.
Materiał emitujący: aby uzyskać taki efekt musimy ustawić w odpowiedni sposób parametr GL_EMISSION, tak aby kolor światła emitowanego był odpowiedni. (W poprzednich przypadkach parametr ten mógł nie być używany).
Po określeniu materiałów należało określić w odpowiedni sposób światła, które będą oświetlały nasz obiekt. Robimy to w podobny sposób jak w przypadku materiałów:
1.GL_AMBIENT - światło otoczenia (brak określonego kierunku),
2.GL_DIFFUSE – światło rozproszone (główny składnik światła),
3.GL_SPECULAR – światło zwierciadlane,
4.GL_POSITION – określa pozycje światła (4 składowa 1.0 dla reflektora, 0.0 dla światła kierunkowego),
5.GL_SPOT_DIRECTION – kierunek świecenia reflektora,
6.GL_SPOT_CUTOFF – połowa kąta rozwarcia światła reflektora.
Światło jest inicjowane za pomocą funkcji:
glLightfv(GL_LIGHT2, GL_DIFFUSE, swiatlo2[1]);
Gdzie 1 parametr mówi o tym, które światło będziemy określać, 2 – który rodzaj współczynnika będziemy określać i ostatni wskazuje na wektor wartości RGBA.
Do poprawnego oświetlania obiektu niezbędne jest określenie wektorów normalnych powierzchni. W naszym przypadku określamy wektory normalne w każdym punkcie siatki bryły. Ze względu na to, że mamy do czynienia z walcem możemy posłużyć się następującą zależnością określającą wektor normalny po powierzchni bocznej:
Z rysunku widać, że wektor normalny do punktu (x,y,z) określamy jako [x,0,z]. Wektory normalne do powierzchni podstaw walca są zawsze równoległe do którejś z osi układu współrzędnych, a kierunek tego wektora określamy wstawiając bądź nie znak ‘-‘ przed współrzędną na osi.
Wykonanie zadania:
Pierwszą rzeczą jaką należy wykonać, jest zdefiniowanie odpowiednich zmiennych globalnych:
// Zmienne globalne
double kat = 0.0; // Kat obrotu obserwatora wokol osi OY
double kat2 = 0.0; // Kat obrotu obserwatora wokol osi OX
int a=0,b=0, c=0; //włacznieki oświetlenia
int lPionowych; // Liczba podzialow pionowych
int lPoziomych; // Liczba podzialow poziomych
double promien; // Promien walca
double wysokosc; // Wysokosc walca
int szerokoscOkna = 800;
int wysokoscOkna = 600;
float R_L1 = 3; // promien orbity reflektora
float wysXZ = 0; // wysokości względem płaszczyzny XZ,
double katRX=0.0; // kat nachylenia orbity reflektora wzgledem osi OX
double katRZ=0.0; // kat nachylenia orbity reflektora wzgledem osi OZ
int obrot; // obrot reflektora po orbicie
float odleglosc = 20; // odleglosc obserwatora od srodka ukladu wspolrzednych
Następnie określamy prototypy funkcji używanych do określania parametrów świateł oraz materiałów (dla każdego materiału oraz światła oddzielna funkcja).
void Material1(void);
void Material2(void);
void Swiatlo1(void);
void Swiatlo2(void);
Macierze materiałów:
żółty emitujący (aby uzyskać taki efekt musimy ustawić w odpowiedni sposób parametr GL_EMISSION)
// Tablica parametrow materialu
GLfloat material1[5][4] = {
{0.0, 0.0, 0.0, 1.0}, // [0] wspolczynnik odbicia swiatla otoczenia
{0.2, 0.2, 0.0, 1.0}, // [1] wspolczynnik odbicia swiatla rozproszonego
{0.2, 0.2, 0.0, 1.0}, // [2] wspolczynnik odbicia swiatla lustrzanego
{0.0, 0.0, 0.0, 0.0}, // [3] polysk
{1.0, 1.0, 0.0, 1.0}}; // [4] kolor swiatla emitowanego (GL_EMISSION)
zielony matowy (do uzyskania tego efektu podajemy większe wartości odbicia światła rozproszonego a mniejsze odbicia światła lustrzanego)
// Tablica parametrow materialu
GLfloat material2[5][4] = {
{0.0, 0.1, 0.0, 1.0}, // [0] wspolczynnik odbicia swiatla otoczenia
{0.1, 1.0, 0.1, 1.0}, // [1] wspolczynnik odbicia swiatla rozproszonego
{0.1, 0.2, 0.1, 1.0}, // [2] wspolczynnik odbicia swiatla lustrzanego
{0.0, 0.0, 0.0, 0.0}, // [3] polysk
{0.0, 0.0, 0.0, 1.0}}; // [4] kolor swiatla emitowanego
Teraz należy określić parametry świateł.
światło białe sceny:
// Tablica parametrow swiatla
GLfloat param_swiatla[10][4] = {
{1.0, 1.0, 1.0, 1.0}, // [0] otoczenie
{1.0, 1.0, 1.0, 1.0}, // [1] rozproszenie
{1.0, 1.0, 1.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
{180.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
światło fioletowe (reflektor):
Ma tak dobrane współczynniki, aby światło było fioletowe. Czwarta pozycja w trzech pierwszych wierszach odpowiada za natężenie oświetlenia, zgodnie z treścią zadania w reflektorze wynosi = 0.9. W czwartym wierszu jest określona pozycja początkowa, która będzie później zmieniana i uzależniana od współrzędnych biegunowych, tak by światło mogło poruszać się po orbicie kołowej. Kierunek jest odwrotny do położenia, dzięki czemu światło jest skierowane cały czas na obiekt.
GLfloat swiatlo1[5][4] = {
{0.2, 0.0, 0.2, 0.9}, // [0] otoczenie
{1.0, 0.0, 1.0, 0.9}, // [1] rozproszenie
{1.0, 0.0, 1.0, 0.9}, // [2] lustrzane
{0.0, 0.0, 1.0, 1.0}, // [3] pozycja
{0.0, 0.0, -1.0}}; //[4] kierunek
światło zielone (kierunkowe):
Aby światło było kierunkowe, należy ustawić ostatnią wartość z wektora pozycji na 0.0. Podobnie jak w świetle reflektora, tu przy oświetleniu kierunkowym należy ustawić natężenie, tym razem na = 0.7. Kierunek jest określany w ten sam sposób co w przypadku światła reflektora.
GLfloat swiatlo2[5][4] = {
{0.0, 0.3, 0.0, 0.7}, // [0] otoczenie
{0.0, 1.0, 0.0, 0.7}, // [1] rozproszenie
{0.0, 1.0, 0.0, 0.7}, // [2] lustrzane
{-10.0, -5.0, 10.0, 0.0}, // [3] pozycja
{10.0, 5.0, -10.0, 0.0}}; // [4] kierunek swiecenia
Funkcje ustawiające odpowiednie parametry materiałów i świateł:
void Material1(void)
{
glMaterialfv(GL_FRONT, GL_AMBIENT, material1[0]);
glMaterialfv(GL_FRONT, GL_DIFFUSE, material1[1]);
glMaterialfv(GL_FRONT, GL_SPECULAR, material1[2]);
glMaterialfv(GL_FRONT, GL_SHININESS, material1[3]);
glMaterialfv(GL_FRONT, GL_EMISSION, material1[4]);
}
void Material2(void)
{
glMaterialfv(GL_FRONT, GL_AMBIENT, material2[0]);
glMaterialfv(GL_FRONT, GL_DIFFUSE, material2[1]);
glMaterialfv(GL_FRONT, GL_SPECULAR, material2[2]);
glMaterialfv(GL_FRONT, GL_SHININESS, material2[3]);
glMaterialfv(GL_FRONT, GL_EMISSION, material2[4]);
}
W oświetleniu reflektora, zgodnie z poleceniem, należy ustawić wartość kąta odcięcia, wykonane to zostało w funkcji poniżej.
void Swiatlo1() {
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT1, GL_DIFFUSE, swiatlo1[1]);
glLightfv(GL_LIGHT1, GL_SPECULAR, swiatlo1[2]);
glLightfv(GL_LIGHT1, GL_POSITION, swiatlo1[3]);
glLightf(GL_LIGHT1, GL_SPOT_CUTOFF,15.0); //kąt odcięcia = 15
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, swiatlo1[4]);
}
W oświetleniu kierunkowym, nie mamy kąta odcięcia, dlatego nie musimy pisać formułki jak wyżej, stąd funkcja oświetlenia drugiego przyjmuje postać:
void Swiatlo2() {
glLightfv(GL_LIGHT2, GL_DIFFUSE, swiatlo2[1]);
glLightfv(GL_LIGHT2, GL_SPECULAR, swiatlo2[2]);
glLightfv(GL_LIGHT2, GL_POSITION, swiatlo2[3]);
glLightfv(GL_LIGHT2, GL_POSITION, swiatlo2[4]);
}
Gdy mamy już zadeklarowane i zdefiniowane wszystkie potrzebne nam zmienne i funkcje możemy przejść do głównej części naszego zadania, czyli zamodelowanie bryły, na którą trzeba nałożyć materiały.
void RysujWalec(double h, double r, int nv, int nh)
{
double dH, dAlfa;
double cut = 2.0L/3.0L; // 2/3 walca
int i, j;
double temp=0.0; // zmienna do budowy ścian przecieć
double krokr = r/(double)nh;
int licznik = 1;
// Wyznaczenie kata wyznaczajacego pojedynczy wycinek pionowy
dAlfa = (360.0L * cut)/(double)nh;
// Wyznaczenie wysokosci pojedynczego wycinka poziomego
dH = h/(double)nv;
Po określeniu odpowiednich zmiennych, można zacząć budować walec od podstaw.
//podstawa dolna
glBegin(GL_TRIANGLES);
for (j = 0; floor(((j)*dAlfa)*1.0E10) < floor((360.0L * cut)* 1.0E10); j++)
{
if (j % 2 == 0)
{
Material1();
}
else if (j % 2 == 1)
{
Material2();
}
glNormal3f(0.0, -1.0, 0.0);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(r*cos(DEG2RAD(j*dAlfa)), 0.0, r*sin(DEG2RAD(j*dAlfa)));
glVertex3f(r*cos(DEG2RAD((j+1)*dAlfa)), 0.0, r*sin(DEG2RAD((j+1)*dAlfa)));
}
glEnd();
Do zamodelowania podstawy dolnej użyłem metody GL_TRIANGLES. Pętla wykonuje swój krok obwodzie koła, wykorzystując funkcję floor();, która obcina część ułamkową. Pierwszą czynnością w tej pętli jest wyznaczenie jakiego materiału należy użyć w danym kroku pętli, wykorzystałem do tego prostą metodę if();, w której sprawdzam czy krok pętli jest parzysty, jeżeli tak to nadaję materiał1, jeśli nie, nakładam materiał2. Następnie trzeba określić wektor normalny. Dla podstawy położonej na początku układu współrzędnych wynosi on (0,-1,0), ponieważ światło, które padnie na podstawę, ma zostać odbite w dół. Po określeniu wektora normalnego należy zdefiniować trzy punkty, które będą tworzyć trójkąt. Pierwszy punkt jest zawsze położony w początku układu współrzędnych, drugi znajduje się na brzegu koła na wysokości równej 0. Trzeci punkt również będzie leżał na wysokości 0, z tą różnicą, że będzie on przesunięty po łuku zgodnie z współrzędnymi biegunowymi. Te trzy punkty zakreślą trójkąt. Po przejściu odpowiedniej ilości kroków zależnych od podziałów pionowych powstanie dolna podstawa walca, która będzie składała się z trójkątów na zmianę żółtych emitujących oraz zielonych matowych.
//podstawa gorna
glBegin(GL_TRIANGLES);
for (j = 0; floor(((j)*dAlfa)*1.0E10) < floor((360.0L * cut)* 1.0E10); j++)
{
if (j % 2 == 0)
{
Material1();
}
else if (j % 2 == 1)
{
Material2();
}
glNormal3f(0.0, 1.0, 0.0);
glVertex3f(0.0, h, 0.0);
glVertex3f(r*cos(DEG2RAD(j*dAlfa)), h, r*sin(DEG2RAD(j*dAlfa)));
glVertex3f(r*cos(DEG2RAD((j+1)*dAlfa)), h, r*sin(DEG2RAD((j+1)*dAlfa)));
}
glEnd();
W porównaniu z podstawą dolną, podstawa górna powstaje w identyczny sposób. Różnice jakie występują to:
- w określeniu wektora normalnego, mamy tu teraz (0,1,0), gdyż chcemy, żeby światło odbijało się od podstawy w górę,
- wszystkie punkty będą na wysokości h.
Po zamodelowaniu podstaw, przechodzimy do modelowania ściany bocznej budowanej w moim przypadku z kwadratów, metodą GL_QUADS.
//sciana boczna
for (i=0; floor((i+1)*dH*1.0E10)<=floor(h*1.0E10);i++)
{
for(j = 0; floor(((j+1)*dAlfa)*1.0E10) <= floor((360.0L * cut)* 1.0E10); j++)
{
if (j % 2 == 0)
{Material1();}
else if (j % 2 == 1)
{Material2();}
glBegin(GL_QUADS);
glNormal3f(cos(DEG2RAD(j*dAlfa)), 0.0, sin(DEG2RAD(j*dAlfa)));
glVertex3f(r*cos(DEG2RAD(j*dAlfa)), i*dH, r*sin(DEG2RAD(j*dAlfa)));
glVertex3f(r*cos(DEG2RAD(j*dAlfa)), (i+1)*dH, r*sin(DEG2RAD(j*dAlfa)));
glNormal3f(cos(DEG2RAD((j+1)*dAlfa)), 0.0, sin(DEG2RAD((j+1)*dAlfa)));
glVertex3f(r*cos(DEG2RAD((j+1)*dAlfa)), (i+1)*dH, r*sin(DEG2RAD((j+1)*dAlfa)));
glVertex3f(r*cos(DEG2RAD((j+1)*dAlfa)), i*dH, r*sin(DEG2RAD((j+1)*dAlfa)));
glEnd();
}
}
Tym razem trzeba wykonać pętlę w pętli, pierwsza zewnętrzna po wysokości, druga wewnętrzna po kącie, tak aby można się swobodnie po brzegu koła. Aby określi jaki materiał trzeba nałożyć, robię tę samą procedurę co w przypadku dolnej i górnej podstawy, sprawdzam parzystość wykonania pętli wewnętrznej. Następnym krokiem jest określenie wektora normalnego dla pierwszej pary punktów leżącej na niższym poziomie. Zgodnie ze wstępem teoretycznym dla punktu na ścianie bocznej walca (x,y,z), wektor normalny ma postać (x,0,z). Po określeniu wektora normalnego wybieram pierwszy punkt, na obrzeżu koła i wysokości takiej jak jest w danej chwili w zewnętrznej pętli . Następny punkt jest przesunięty względem poprzedniego o jednostkę wysokości. Przed następną parą punktów znowu określam wektor normalny, położony o krok dalej na obrzeżu walca. Kolejny punkt znajduje się w porównaniu do poprzedniego dalej na obrzeżu walca, a ostatni również jest o krok dalej na obrzeżu walca, ale znajduje się na wysokości takiej jak pierwszy punkt. W ten sposób powstaje pojedynczy prostokąt na ścianie bocznej walca. Po wykonaniu wszystkich kroków pętli, ściana boczna prezentuje się w paski pionowe, naprzemiennie w kolorze żółtym emitującym i zielonym matowym.
Poniżej pokazane jest jak wygląda oświetlona reflektorem ściana boczna oraz podstawa górna:
Po zamodelowaniu ściany bocznej, pozostało już tylko stworzyć ściany przecięć. Pierwsza będzie się znajdować na obrzeżu walca w kącie 0, druga w 2/3 koła, czyli na 240 stopniu. Tutaj również użyłem metody GL_QUADS.
for (i = 0; floor((i+1)*dH*1.0E10) <= floor(h*1.0E10); i++)
{
glBegin(GL_QUADS);
for (temp = 0; temp < r; temp+=krokr)
{
if((licznik%2)==0) {Material1();}
else if((licznik%2)==1) {Material2();}
glNormal3f(cos(DEG2RAD(0.0)), 0.0, sin(DEG2RAD(0.0)));
glVertex3f(temp*cos(DEG2RAD(0)), (i + 1)*dH, temp*sin(DEG2RAD(0)));
glVertex3f(temp*cos(DEG2RAD(0)), i*dH, temp*sin(DEG2RAD(0)));
glVertex3f((temp+krokr)*cos(DEG2RAD(0)), i*dH, (temp+krokr)*sin(DEG2RAD(0)));
glVertex3f((temp+krokr)*cos(DEG2RAD(0)), (i+1)*dH, (temp+krokr)*sin(DEG2RAD(0)));
licznik++;
}
glEnd();
glBegin(GL_QUADS);
for (temp = 0; temp < r; temp+=krokr)
{
if((licznik%2)==0) {Material1();}
else if((licznik%2)==1) {Material2();}
glNormal3f(cos(DEG2RAD(240.0L)), 0.0, sin(DEG2RAD(240.0L)));
glVertex3f(temp*cos(DEG2RAD(240.0L)), (i + 1)*dH, temp*sin(DEG2RAD(240.0L)));
glVertex3f(temp*cos(DEG2RAD(240.0L)), i*dH, temp*sin(DEG2RAD(240.0L)));
glVertex3f((temp+krokr)*cos(DEG2RAD(240.0L)),i*dH, (temp+krokr)*sin(DEG2RAD(240.0L)));
glVertex3f((temp+krokr)*cos(DEG2RAD(240.0L)),(i+1)*dH,(temp+krokr)*sin(DEG2RAD(240.0L)));
licznik++;
}
glEnd();
}
Tak jak przy ścianie bocznej pierwsza zewnętrzna pętla będzie po wysokości, lecz pętla wewnętrzna będzie tym razem po promieniu. Zaczynam od promienia równego 0, następnie zwiększam go z każdym krokiem, aż będzie on równy promieniowi figury. W pętli wewnętrznej sprawdzam parzystość jej wykonania, nie mogę tutaj użyć do porównania modulo zmiennej temp, gdyż jest ona typu double. Aby dokonać sprawdzania parzystości dodałem zmienną licznik, która po każdym kroku pętli będzie się zwiększała i dzięki niej będę mógł określić jakiego materiału w danym kroku pętli trzeba użyć. Określam wektor normalny dla całego prostokąta, następnie wybieram cztery punkty. W jednej pętli po wysokości tworzę obie ściany przecięć, zmieniając tylko kąt na jakim dana ściana ma się znajdować.
Efekt takiego modelowania, w świetle białym oraz w świetle zielonym kierunkowym:
Na zakończenie tworzenia figury wywołuję funkcję świateł:
Swiatlo1();
Swiatlo2();
}
W ten sposób kończę modelować figurę zadaną na laboratorium. Teraz przechodzimy do funkcji włączającej oświetlenie:
// Wlaczenie oswietlenia sceny
void WlaczOswietlenie()
{
// Odblokowanie oswietlenia
//glEnable(GL_LIGHTING);
// Odblokowanie zerowego zrodla swiatla
if (c == 1) glEnable(GL_LIGHT0);
else glDisable(GL_LIGHT0);
if(a==1){
glEnable(GL_LIGHT1);
swiatlo1[3][0]=R_L1*cos(DEG2RAD(obrot));
swiatlo1[3][1]=R_L1*sin(DEG2RAD(katRZ));
swiatlo1[3][2]=R_L1*sin(DEG2RAD(obrot));
swiatlo1[3][3]=1.0;
swiatlo1[4][0]=-R_L1*cos(DEG2RAD(obrot));
swiatlo1[4][1]=-R_L1*sin(DEG2RAD(katRZ));;
swiatlo1[4][2]=-R_L1*sin(DEG2RAD(obrot));
swiatlo1[4][3]=-1.0;
}
else (glDisable(GL_LIGHT1));
if(b==1){
glEnable(GL_LIGHT2);
}
else ( glDisable(GL_LIGHT2));
// Inicjowanie zrodla swiatla
glLightfv(GL_LIGHT0, GL_AMBIENT, swiatlo[0]);
glLightfv(GL_LIGHT0, GL_DIFFUSE, swiatlo[1]);
glLightfv(GL_LIGHT0, GL_SPECULAR, swiatlo[2]);
glLightfv(GL_LIGHT0, GL_POSITION, swiatlo[3]);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, swiatlo[4]);
glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, swiatlo[5][0]);
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, swiatlo[6][0]);
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, swiatlo[7][0]);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, swiatlo[8][0]);
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, swiatlo[9][0]);
}
Za pomocą współrzędnych biegunowych określamy na nowo położenie światła. Teraz będzie ono zależne od zmiennej obrót oraz od promienia orbity, po której będzie się poruszało. Podobnie modyfikujemy kierunek świecenia światła, przypisując odpowiednie wartości w zdefiniowanej wcześniej tablicy dwuwymiarowej (będą odwrotne do tych, które zamieściliśmy w wektorze określającym pozycję, aby światło świeciło zawsze na obiekt). Zmienne a, b i c będzie można zmieniać za pomocą klawiatury, dzięki czemu będzie można włączać i wyłączać światła zgodnie z własnym życzeniem.
W funkcji wyświetlającej obraz zamieszczamy wyrysowanie kuli symbolizującej źródło światła oraz wyliczanie współrzędnych związanych z pochyleniem osi obrotu do osi X oraz do osi Z.
void WyswietlObraz(void)
{
// Wyczyszczenie bufora ramki i bufora glebokosci
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
// Okreslenie wielkosci widoku, wlaczenie rzutowania perspektywicznego
// i zainicjowanie stosu macierzy modelowania
UstawParametryWidoku(szerokoscOkna, wysokoscOkna);
// Ustawienie oswietlenia sceny (polozenie zrodla swiatla wyznaczane jest w //ukladzie wspolrzednych obserwatora)
WlaczOswietlenie();
//===============ustawienie obserwatora===================
glTranslatef(0, wysXZ, 0);
glTranslatef(0, 0, -odleglosc);
glRotatef(kat2, 1, 0, 0);
glRotatef(kat, 0, 1, 0);
//========================================================
//===================operacje na swietle 1================
glPushMatrix();
glTranslatef(0.0, 0.0, 0.0);
glRotatef(katRX, 1,0,0);
glRotatef(katRZ, 0,0,1);
glDisable(GL_LIGHTING);
glTranslatef(R_L1*cos(DEG2RAD(obrot)), 0.0, R_L1*sin(DEG2RAD(obrot)));
glutWireSphere(0.25, 15, 15);
glEnable(GL_LIGHTING);
glPopMatrix();
// Generacja obrazu walca
RysujWalec(wysokosc, promien, lPoziomych, lPionowych);
// Przelaczenie buforow ramki
glutSwapBuffers();
}
Dokonujemy zatem translacji, aby zmieniać odległość obserwatora od obiektu, następnie określamy kąty nachylenia względem osi X (wraz ze zmianą tego kąta będziemy mogli zmieniać wysokość obserwatora nad obiektem) oraz kat obrotu wokół osi Y. Następnie odkładamy tą macierz modelowania na stos, aby móc później obracać osią obrotu orbity światła niezależnie od walca (położenie walca się nie zmienia, zmienia się jedynie położenie orbity kuli symbolizującej źródło światła). Gdy odłożymy macierz na stos możemy określić kąt nachylenia do osi X oraz kąt nachylenia do osi Z, gdyż te kąty będą zmieniane za pomocą klawiatury. Następnie wyłączamy oświetlenie, aby nie działało na kolejny tworzony przez nas obiekt. Teraz zmieniamy współrzędne położenia zgodnie ze wzorami na współrzędne biegunowe wyznaczając położenie źródła światła i kuli i możemy już wyrysować odpowiednią nieoświetloną kulę. Następnie włączamy światło i ustawiamy jego współrzędne na taki, aby zgadzały się z położeniem wyrysowanej wcześniej kuli (były one określone w funkcji WlaczOswietlenie).
Pozostaje już tylko skonstruowanie funkcji obsługi klawiatury, która będzie nam umożliwiała interaktywną zmianę poszczególnych parametrów.
// Funkcja obslugi klawiatury
void ObslugaKlawiatury(unsigned char klawisz, int x, int y)
{
switch(klawisz)
{
case 'w':
wysXZ -= 1.0;
break;
case 'W':
wysXZ += 1.0;
break;
case 'v':
lPionowych = (lPionowych == LPION_MAX)? LPION_MAX : lPionowych + 2; //zwiększenie liczby podziałów pionowych
break;
case 'V':
lPionowych = (lPionowych == LPION_MIN)? LPION_MIN : lPionowych - 2; //zmniejszenie liczby podziałów pionowych
break;
case 'h':
lPoziomych = (lPoziomych == LPOZ_MAX)? LPOZ_MAX : lPoziomych + 2; //zwiększenie liczby podziałów poziomych
break;
case 'H':
lPoziomych = (lPoziomych == LPOZ_MIN)? LPOZ_MIN : lPoziomych - 2; //zmniejszenie liczby podziałów poziomych
break;
case 'z' : R_L1 += 0.1;
break;
case 'Z' : R_L1 -= 0.1;
break;
case 'C' : (c=0);
break;
case 'c' : (c=1);
break;
case 'a' : (a=1);
break;
case 'A' : (a=0);
break;
case 'b' : (b=1);
break;
case 'B' : (b=0);
break;
case '-' : odleglosc++;
break;
case '+' : odleglosc--;
break;
case ',' : obrot = (obrot == 360) ? 0 : obrot + 1;
break;
case '.' : obrot = (obrot == 0) ? 360 : obrot - 1;
break;
case 'r':
UstawDomyslneWartosciParametrow();
break;
case 'i':
katRX=(katRX>-90) ? katRX-1 : -90; //zmniejszenie kąta nachylenia osi X orbity światła
break;
case 'I':
katRX=(katRX<90) ? katRX+1 : 90; //zwiększenie kąta nachylenia osi X orbity światła
break;
case 'o':
katRZ=(katRZ>-90) ? katRZ-1 : -90; //zmniejszenie kąta nachylenia osi Y orbity światła
break;
case 'O':
katRZ=(katRZ<90) ? katRZ+1 : 90;
break;
// Wcisniecie klawisza ESC powoduje wyjscie z programu
case 27:
exit(0);
}
}
// Funkcja klawiszy specjalnych
void ObslugaKlawiszySpecjalnych(int klawisz, int x, int y)
{
switch(klawisz)
{
case GLUT_KEY_UP:
kat2 = (kat2 == 360) ? 0 : kat2 + 1;
break;
case GLUT_KEY_DOWN:
kat2 = (kat2 == 0) ? 360 : kat2 - 1;
break;
case GLUT_KEY_RIGHT:
kat = (kat == 360) ? 0 : kat + 1;
break;
case GLUT_KEY_LEFT:
kat = (kat == 0) ? 360 : kat - 1;
break;
}
}
Wnioski
Za zadanie mieliśmy zamodelować odpowiednią bryłę i pokryć ją materiałem oraz w odpowiedni sposób oświetlić dwoma rodzajami światła. Okazało się to bardzie trudne i wymagało dużo czasu jeśli się nie miało odpowiedniej wprawy w tego typu ćwiczeniach. Na laboratorium miałem problem z nałożeniem materiałów, nie mogłem wyeliminować efektu zlewania się kolorów. W domu wyeliminowałem ten problem używając innej metody rysowania figury i tak z GL_TRIANGLES_FAN oraz GL_TRIANGLE_STRIP zamniełem na GL_TRIANGLES i GL_GUADS. Zmodyfikowałem program dodając do niego odpowiednie światła i wprowadzając znacznie więcej możliwości modyfikacji sceny z poziomu klawiatury. Ćwiczenie to miało na celu pokazanie nam jak ważną rolę w tworzeniu grafiki trójwymiarowej mają materiały oraz światła. Wykorzystanie ich znacznie ożywia tworzone przez nas obiekty i sprawia, że stają się one bardziej rzeczywiste.