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: mgr inż. Sulej Wojciech
Wykonał: Marcin Szczepaniak
Grupa: I8Y4S1
Data wykonania ćwiczenia: 26.01.2010
Opis zadania
Zadanie 3.
Wykorzystując biblioteki OpenGl i Glut napisać program przedstawiający perspektywiczny obraz obiektu o następujących parametrach:
Typ obiektu: ½ stożka ściętego w 2/3 wysokości o zmiennej parzystej liczbie podziałów pionowych i poziomych,
Właściwości materiału nr 1: żółty błyszczący (widziany w białym świetle),
Właściwości materiału nr 2: czerwony matowy (widziany w białym świetle),
Sposób przyporządkowania materiałów do obiektu zgodnie ze wzorem: szachownica z uwzględnieniem podziałów pionowych i 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: biały,
- natężenie: 1,
- kąt odcięcia: 250 ,
- położenie: zmienna 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,
Kąta nachylenia orbity do osi OX,
Kąta nachylenia orbity do osi OZ,
kierunek świecenia: na obiekt.
Źródło nr 2:
typ: kierunkowe,
kolor: fioletowy,
natężenie: 0.8,
położenie: stałe w punkcie P(10, 10, 10) układu współrzędnych sceny,
kierunek świecenia: na obiekt.
Program powinien umożliwiać interaktywne, niezależne włączenie i wyłączenie ź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,
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.
Rozwiązanie zadania
Powyższe zadnia można podzielić na 3 główne części: zamodelowanie źródeł światła, zamodelowanie powierzchni oraz wyliczenie wektorów normalnych aby te powierzchnie oświetlić. Do każdej części wykorzystywaliście zestawy funkcji, które oferują nam biblioteki OpenGL i GLUT. Do modelowania oświetlenia posługiwaliśmy się funkcjami:
glLight{if}[v](GLenum light, GLenum pname, TYPE param) – służy do określenia właściwości źródła światła. Argument light określa numer źródła, argument pname – modyfikowany parametr, param – wartość, która ma zostać przypisana.
Spośród parametrów pomocne okazały się: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, które określają kolejno składowe otoczenia, rozproszenia i lustrzaną światła; GL_POSITION określającą położenie światła; GL_SPOT_DIRECTION – wektor kierunku padania światła pozycyjnego oraz GL_SPOT_CUTOFF – kąt odcięcia światła pozycyjnego.
glEnable(GL_LIGHTx) i glDisable(GL_LIGHTx) – włączanie i wyłączanie światła. Argumentem jest numer źródła.
glEnable(GL_LIGHTING) – zezwolenie na wykonywanie obliczeń związanych z oświetleniem sceny.
Do definiowania właściwości materiału użyliśmy następujących funkcji void glMaterial{if}[v](GLenum face, GLenum pname, TYPE param) – funkcja służąca do określenia właściwości materiału w celu modelowania oświetlenia. Parametr face określa, do której strony obiektu odnoszą się definiowane ustawienia. Może przyjmować wartości: GL_FRONT, GL_BACK, GL_FRONT_AND_BACK. Parametr pname określa modyfikowany parametr, a param wartość, która ma zostać mu przypisana.
Do definicji wektorów normalnych wykorzystaliśmy funkcję:
-void glNormal3{bsidf}(TYPE nx, ny, nz);
-void glNormal3{bsidf}v(cont TYPE *v);
Funkcja ustala aktualny wektor normalny. Argumentami mogą być trzy współrzędne wektora [nx, ny, nz] lub wskaźnik do tablicy te współrzędne zawierające(v).
Projektowanie modelu i oświetlenia rozpocząłem od zdefiniowania właściwości materiałów i świateł:
//materiały
//żółty błyszczący
const GLfloat ambient_m1[4] = { 1, 1, 0, 1 }; //współczynnik odbicia
światła otoczenia
const GLfloat diffuse_m1[4] = { 0.5, 0.5, 0.3, 1 }; //współczynnik światła
rozproszonego
const GLfloat specular_m1[4] = { 1, 1, 0, 1 }; //współczynnik odbicie
światła odbitego
const GLfloat ambient_m2[4] = { 1, 0, 0, 1 }; // czerwony matowy
const GLfloat diffuse_m2[4] = { 0, 0, 0, 1 };
const GLfloat specular_m2[4] = { 0, 0, 0, 1 };
//swiatla
const GLfloat ambient_s1[4] = { 1, 1, 1, 1 }; //reflektor
const GLfloat diffuse_s1[4] = { 1, 1, 1, 1 };
GLfloat specular_s1[4] = { 1, 1, 1, 1 };
GLfloat position_s1[] = { 0, 0, 0, 1 };
GLfloat direction_s1[] = { 0, -1, 0 };
const GLfloat ambient_s2[4] = {1, 0, 1, 1 }; //kierunkowe
const GLfloat diffuse_s2[4] = {0.8, 0.8, 0.8, 1 };
GLfloat specular_s2[4] = { 1, 1, 1, 1 };
GLfloat position_s2[] = { 10, 10, 10, 0 };
W tej części zadania należało tak dobrać współczynniki RGB aby uzyskać odpowiedni efekt.
Dla materiału mamy zmienne: ambient, diffuse i specular, które określają kolejno składowe otoczenia, rozproszenia i lustrzaną światła. W zmiennej ambient ustalamy kolor, pozostałe parametry odpowiadają za ustawienia błyszczący/matowy. Aby uzyskać opowiedni rezultat , w materiale pierwszym należało ustawić małe wartości diffuse, a większe specular – dzięki temu otrzymujemy błyszczący materiał (dla drugiego materiału odwotnie). Współczynniki do definiowania światła są podobne, jednak występują dodatkowo zmienne position i direction. position służy do określenia pozycji światła w przestrzeni, a direction to kierunek w jakim świeci światło.
Następnie należy napisać funkcje, które ustawiają materiały i światła według naszych ustawień.
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,100);
}
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, 0);
}
void Swiatlo1() {
glPushMatrix();
glEnable(GL_LIGHTING);
glRotatef(kat_x, 1, 0, 0);
glRotatef(kat_z, 0, 0, 1);
glTranslatef(0, R_L1, 0);
glPushMatrix();
glutWireSphere(0.25, 20, 20);
glPopMatrix();
glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_s1);
glLightfv(GL_LIGHT1, GL_SPECULAR, specular_s1);
glLightfv(GL_LIGHT1, GL_POSITION, position_s1);
glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 25);
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);
}
Takie definiowanie umożliwiają nam funkcje glMaterial() i glLight(). W funkcji światło znajdują się dodatkowo elementy odpowiedzialne za ruch reflektora.
Kolejnym etapem było zaprojektowanie ½ stożka ściętego w 2/3 wysokości. Funkcja realizujące to zadanie wygląda następująco:
void RysujSzescian(double a) {
glBegin(GL_LINES);
float alfa = 0, teta = 0, x, y, z, j;
float R = 6, H = 5, temp_y = 0, temp_x = 0, temp_z = 0;
int parzystosc = 0, i;
glPushMatrix();
for (i = 0; i < ((((float)2/3))*pion); i++) {
if (i%2 == 1)
parzystosc = 1;
else
parzystosc = 0;
for (j = 0; j < Lpoziome/2; j++) {
if (parzystosc%2 == 0) {
Material1();
parzystosc = 1;
} else {
Material2();
parzystosc = 0;
}
glBegin(GL_QUADS);
x = (R-R*i/pion)*cos(2*pi*j/Lpoziome);
y = H*i/pion;
z = (R-R*i/pion)*sin(2*pi*j/Lpoziome);
glVertex3f(x, y, z);
temp_x = x;
temp_y = y;
temp_z = z;
x = (R-R*(i+1)/pion)*cos(2*pi*j/Lpoziome);
y = H*(i+1)/pion;
z = (R-R*(i+1)/pion)*sin(2*pi*j/Lpoziome);
glVertex3f(x, y, z);
wektor1[0] = x - temp_x;
wektor1[1] = y - temp_y;
wektor1[2] = z - temp_z;
x = (R-R*(i+1)/pion)*cos(2*pi*(j+1)/Lpoziome);
y = H*(i+1)/pion;
z = (R-R*(i+1)/pion)*sin(2*pi*(j+1)/Lpoziome);
glVertex3f(x, y, z);
x = (R-R*i/pion)*cos(2*pi*(j+1)/Lpoziome);
y = H*i/pion;
z = (R-R*i/pion)*sin(2*pi*(j+1)/Lpoziome);
glVertex3f(x, y, z);
wektor2[0] = x - temp_x;
wektor2[1] = y - temp_y;
wektor2[2] = z - temp_z;
IloczynWektorowy();
glNormal3f(wektor3[0], wektor3[1], wektor3[2]);
glEnd();
}
}
for (i = 0; i < Lpoziome/2; i++) { //podstawa dolna
if (i%2 == 1) {
parzystosc = 1;
} else
parzystosc = 0;
for (j = 0; j < pion; j++) {
if (parzystosc%2 == 0) {
Material1();
parzystosc = 1;
} else {
Material2();
parzystosc = 0;
}
glBegin(GL_QUADS);
glNormal3f(0, -1, 0);
glVertex3f(R*j/pion*cos(2*pi*i/Lpoziome), 0, R*j/pion*sin(2*pi*i/Lpoziome));
glVertex3f(R*(j+1)/pion*cos(2*pi*i/Lpoziome), 0, R*(j+1)/pion*sin(2*pi*i/Lpoziome));
glVertex3f(R*(j+1)/pion*cos(2*pi*(i+1)/Lpoziome), 0, R*(j+1)/pion*sin(2*pi*(i+1)/Lpoziome));
glVertex3f(R*j/pion*cos(2*pi*(i+1)/Lpoziome), 0, R*j/pion*sin(2*pi*(i+1)/Lpoziome));
glEnd();
}
}
for (i = 0; i < Lpoziome/2; i++) { //podstawa górna
if (i%2 == 1) {
parzystosc = 1;
} else
parzystosc = 0;
for (j = 0; j < pion; j++) {
if (parzystosc%2 == 0) {
Material1();
parzystosc = 1;
} else {
Material2();
parzystosc = 0;
}
glBegin(GL_QUADS);
glNormal3f(0, 1, 0);
glVertex3f(1.8*j/pion*cos(2*pi*i/Lpoziome), 3.5, 1.8*j/pion*sin(2*pi*i/Lpoziome));
glVertex3f(1.8*(j+1)/pion*cos(2*pi*i/Lpoziome),3.5, 1.8*(j+1)/pion*sin(2*pi*i/Lpoziome));
glVertex3f(1.8*(j+1)/pion*cos(2*pi*(i+1)/Lpoziome), 3.5, 1.8*(j+1)/pion*sin(2*pi*(i+1)/Lpoziome));
glVertex3f(1.8*j/pion*cos(2*pi*(i+1)/Lpoziome), 3.5, 1.8*j/pion*sin(2*pi*(i+1)/Lpoziome));
glEnd();
}
}
for (i = 0; i < ((((float)2/3))*pion); i++) {
if (i%2 == 1)
parzystosc = 1;
else
parzystosc = 0;
for (j = 0; j < Lpoziome/2; j++) {
if (parzystosc%2 == 0) {
Material1();
parzystosc = 1;
} else {
Material2();
parzystosc = 0;
}
glBegin(GL_QUADS);
x = (R-R*i/pion)*cos(2*pi*j/Lpoziome);
y = H*i/pion;
glVertex3f(x, y, 0);
x = (R-R*(i+1)/pion)*cos(2*pi*j/Lpoziome);
y = H*(i+1)/pion;
glVertex3f(x, y, 0);
x = (R-R*(i+1)/pion)*cos(2*pi*(j+1)/Lpoziome);
y = H*(i+1)/pion;
glVertex3f(x, y, 0);
x = (R-R*i/pion)*cos(2*pi*(j+1)/Lpoziome);
y = H*i/pion;
glVertex3f(x, y, 0);
glNormal3f(0, 0, -1);
glEnd();
}
}
Swiatlo1();
Swiatlo2();
glPopMatrix();
}
W powyższej części kodu została użyta funkcja odpowiedzialna za wyliczanie wektorów normalnych. Wektor normalny to wektor prostopadły do powierzchni. Określa on kierunek padania światła na dany obszar. Określenie wektorów normalnych padających na powierzchnie płaskie nie stanowi problemu, trudniejsze jest wyznaczenie wektorów na powierzchnie łamane. Należy się wtedy posłużyć iloczynem wektorowym i dopiero tak wyliczony wektor zdefiniować w funkcji glNormal3().Dzięki temu zabiegowi, światło padające na obiekt jest realistyczne, tzn. oświetlone tam gdzie pada strumień światła, jednocześnie cieniując obiekt. Funkcja, która realizuje operację iloczynu wektorowego wygląda następująco:
void IloczynWektorowy() {
wektor3[0] = wektor1[1]*wektor2[2] - wektor2[1]*wektor1[2];
wektor3[1] = wektor1[2]*wektor2[0] - wektor2[2]*wektor1[0];
wektor3[2] = wektor1[0]*wektor2[1] - wektor2[0]*wektor1[1];
}
Na koniec zapewniamy użytkownikowi zmianę położenia obserwatora. Odbywa się to w funkcji WyswietlObraz() poprzez zdefiniowanie odpowiednich rotacji. W kolejnej funkcji zdefiniowane są klawisze dzięki którym możemy zmienić zmienne tu występujące.
glTranslatef(0, 0, -OBSERWATOR_ODLEGLOSC);
glRotatef(atan(OBSERWATOR_WYSOKOSC/OBSERWATOR_ODLEGLOSC) * 180/pi, 1, 0, 0);
glRotatef(OBSERWATOR_OBROT_Y, 0, 1, 0);
Zrzuty ekranu:
Wnioski
Podczas ćwiczenia zadaniem było zamodelowanie figury o zadanym w treści stylu, materiale i oświetleniu. Celem było zaprzyjaźnienie się z bardzo ważną według mnie rolą światła oraz oświetlenia w programowaniu obiektów graficznych. Umiejętne posługiwanie się oświetleniem pozwala ożywić i stworzyć scenę bliższą rzeczywistemu odpowiednikowi danego obiektu. Najtrudniejszą rzeczą okazało się wyliczenie wektorów normalnych, jednak jest to bardzo istotna rzecz, o czym przekonałem się wielokrotnie próbując ustawić odpowiednie parametry. Niewłaściwe ustawienie wektorów może spowodować to, że światło pada z zupełnie innej strony niż tego oczekujemy. Laboratorium pokazało nam kolejne możliwości jakie daje nam OpenGL.