WOJSKOWA AKADEMIA TECHNICZNA
Sprawozdanie laboratoryjne z przedmiotu
GRAFIKA KOMPUTEROWA
Laboratorium nr 4
Temat : Modelowanie oświetlenia powierzchni
Wykonujący ćwiczenie: Mariusz Gawroński
Prowadzący: dr inż. Marek Salamon
Data wykonania: 15.12.2011r
Grupa szkoleniowa: I0X4S1
Treść zadania (zestaw 5):
Wykorzystując biblioteki OpenGL i GLUT napisać program przedstawiający perspektywiczny obraz obiektu o następujących parametrach:
Typ obiektu: bryła z ćwicz. 2 o zmiennej parzystej liczbie podziałów pionowych i poziomych,
Właściwości materiału nr 1: brązowy matowy (widziany w białym świetle),
Właściwości materiału nr 2: szary błyszczący (widziany w białym świetle),
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:
Źródło nr 1:
typ: reflektor (ang. spot),
kolor: zielony,
natężenie: 1,
kąt odcięcia: 30o,
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: żółty,
natężenie: 0.5,
położenie: stałe w punkcie P(5, 5, 20) układu współrzędnych obserwatora,
kierunek świecenia: na obiekt.
c) interaktywną zmianę wielkości bryły;
odległości obserwatora od środka układu współrzędnych sceny;
kąta obrotu wokół osi OY w zakresie [0o, 360o] z krokiem 1o.
Oświetlony obiekt powinien zawsze znajdować się w centralnej części okna
Opis wykonania zadania:
Realizację treści zadania rozpocząłem od zdefiniowania parametrów materiałów, a następnie od napisania funkcji, które to będą korzystały zdefiniowane materiały w celu ustawienia ich w odpowiednim momencie modelowania obiektu:
GLfloat material_brazowy[5][4]=
{
{0.4, 0.2, 0.0, 1.0},// [0] wspolczynnik odbicia swiatla otoczenia
{0.4, 0.2, 0.0, 1.0},// [1] wspolczynnik odbicia swiatla rozproszonego
{0.0, 0.0, 0.0, 1.0},// [2] wspolczynnik odbicia swiatla lustrzanego
{0,0,0,0}, // [3] polysk
{0.0,0.0,0.0,1.0} // [4] kolor swiatla emitowanego
};
GLfloat material_szary[5][4]=
{
{0.4, 0.4, 0.4, 1.0},// [0] wspolczynnik odbicia swiatla otoczenia
{0.4, 0.4, 0.4, 1.0},// [1] wspolczynnik odbicia swiatla rozproszonego
{1.0, 1.0, 1.0, 1.0},// [2] wspolczynnik odbicia swiatla lustrzanego
{128,0,0,0}, // [3] polysk
{0.0,0.0,0.0,1.0} // [4] kolor swiatla emitowanego
};
void Material_brazowy()
{
glMaterialfv(GL_FRONT, GL_AMBIENT, material_brazowy[0]);
glMaterialfv(GL_FRONT, GL_DIFFUSE, material_brazowy[1]);
glMaterialfv(GL_FRONT, GL_SPECULAR, material_brazowy[2]);
glMaterialf(GL_FRONT, GL_SHININESS, material_brazowy[3][0]);
glMaterialfv(GL_FRONT, GL_EMISSION, material_brazowy[4]);
}
void Material_szary()
{
glMaterialfv(GL_FRONT, GL_AMBIENT, material_szary[0]);
glMaterialfv(GL_FRONT, GL_DIFFUSE, material_szary[1]);
glMaterialfv(GL_FRONT, GL_SPECULAR, material_szary[2]);
glMaterialf(GL_FRONT, GL_SHININESS, material_szary[3][0]);
glMaterialfv(GL_FRONT, GL_EMISSION, material_szary[4]);
}
Jak widać, całość sprowadza się do utworzenia tablic, które to zawierają odpowiednio ustawione składowe RGBA (Red Green Blue Alpha) poszczególnych parametrów.
Składowe parametru ambient określają stopień odbicia światła otaczającego, parametru diffuse – stopień rozproszenia światła rozproszonego, parametru specular – stopień odbicia światła odbitego, parametru shininess - połysk , zaś składowe RGBA parametru emission określają światło emitowane przez obiekt. Ten ostatni parametr domyślnie jest ustawiony tak, że materiał nie jest emitujący. Dlatego oba materiały mają tablicę emission ustawioną na 0.
Dobranie powyższych parametrów nie jest zadaniem łatwym i przysporzyło mi sporo kłopotów, jednakże po jakimś czasie udało się osiągnąć oczekiwany efekt.
Parametr AMBIENT określa w jakim stopniu zostaje odbite światło otoczenia, czyli tutaj ustawiamy jaki chcemy mieć kolor materiału.
Parametr DIFFUSE określa jaka ilość światła padającego na obiekt, ulega na jego powierzchni rozproszeniu, czyli po odbiciu rozchodzi równomiernie we wszystkich kierunkach. Ma to miejsce w tym większym stopniu im bardziej powierzchnia jest matowa. Parametr ten w głównej mierze określa kolor materiału.
Przy materiałach matowych, duże wartości powinny mieć składowe DIFFUSE przy małych wartościach składowych SPECULAR, zaś materiały błyszczące także maja wysokie składowe DIFFUSE zaś parametry SPECULAR niskie.
Parametr SPECULAR, głównie decydował o tym czy materiał jest matowy czy błyszczący. Wysoki współczynnik odbicia odpowiada materiałom błyszczącym natomiast niski matowym, gdyż wtedy nie zachodzi odbicie światła.
Parametr SHININESS 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 odblysku. Duże wartości powodują zawężenie obszaru występowania odbłysku, zaś domyślna wartość sprawia, że zjawisko zachodzi zawsze.
Parametr EMISSION określa światło emitowane, my nie mamy takiego typu materiały więc wszędzie ustawiamy go na 0.
Po ustawieniu kolorów, którymi to będzie pokolorowana nasza przyszła bryła przystąpiłem do ustawienia parametrów świateł oraz zdefiniowania ich za pomocą odpowiednich funkcji:
// Tablica parametrow swiatla
GLfloat swiatlo_reflektor[5][4]=
{
{0,1,0,1}, // [0] światło otoczenia
{0,1,0,1}, // [1] światło rozproszone
{0,1,0,1}, // [2] światło zwierciadlane
{0,0,0,1}, // [3] położenie
{-1,0,0} // [4] kierunek świecenia
};
GLfloat swiatlo_glowne[5][4]=
{
{1,1,0,0.5}, // [0] światło otoczenia
{1,1,0,0.5}, // [1] światło rozproszone
{1,1,0,0.5}, // [2] światło zwierciadlane
{5,5,20,0.5}, // [3] położenie
{0,0,0} // [4] kierunek świecenia
};
void Swiatlo_reflektor(){
glPushMatrix();
glEnable(GL_LIGHTING);
glRotatef(kat,0,1,0);
glRotatef(kat_z,0,0,1);
glTranslatef(R_L1,0,0);
glPushMatrix();
glTranslatef(0,3,0);
glutSolidSphere(0.5,10,10);
glPopMatrix();
glLightfv(GL_LIGHT0, GL_DIFFUSE, swiatlo_reflektor[1]);
glLightfv(GL_LIGHT0, GL_SPECULAR, swiatlo_reflektor[2]);
glLightfv(GL_LIGHT0, GL_POSITION, swiatlo_reflektor[3]);
glLightf (GL_LIGHT0, GL_SPOT_CUTOFF, 30.0);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION,swiatlo_reflektor[4]);
glLightf (GL_LIGHT0, GL_SPOT_EXPONENT, 1);
glPopMatrix();
}
void Swiatlo_glowne(){
glLightfv(GL_LIGHT1, GL_DIFFUSE, swiatlo_glowne[1]);
glLightfv(GL_LIGHT1, GL_SPECULAR, swiatlo_glowne[2]);
glLightfv(GL_LIGHT1, GL_POSITION, swiatlo_glowne[3]);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION,swiatlo_glowne[4]);
}
Ustawianie świateł jest procesem podobnym do ustawiania materiałów. Dodatkowo występują tutaj parametry: position, który to określa położenie źródła światła oraz parametr direction, który jest trój-współrzędnym znormalizowanym wektorem określającym kierunek reflektora. Ponadto mamy tutaj okazję, wykorzystać dobrodziejstwo modeli przestrzeni barw RGBA. RGBA jest wzbogaconą wersją o kanał alfa znanego nam wszystkim modelu RGB. Wspomniany przeze mnie kanał używany jest jako odpowiednik współczynnika pochłaniania światła. Gdy kanał alfa skojarzony z danym elementem ma wartość 0, to taki piksel staje się całkowicie przezroczysty (czyli przybiera kolor tła pod nim - w rezultacie wydaje się nam, że nie widzimy go). Natomiast, gdy wartość zapisana we współczynniku alfa wyniesie 1, element będzie całkowicie widoczny (jak w zwykłym obrazie cyfrowym bez kanału alfa). Także ustawiając światło kierunkowe wartość alfa ustawiłem na 0.5. To jest nasze natężenie światła.
Parametr GL_SPOT_CUTOFF jest odpowiedzialny za kąt odbicia.
Funkcja Swiatlo_reflektor posiada zdolność zmiany parametrów promienia orbity światła, prędkości kątowej (3 różne prędkości) oraz kąta nachylenia orbity do osi OX. Symbolem naszego reflektora jest sfera o liczbie południków i równoleżników 10.
Następnie przystąpiłem do modelowania figury, jako że mieliśmy ta bryłę zamodelować na Laboratorium nr 2 to wystarczyło tylko dodać wektory normalne i pokolorować figurę danymi materiałami według polecenia.
Zastosowanie wektorów normalnych jest niezbędne ponieważ obliczenia związane z oświetleniem, OpenGL wykonuje dla każdego wierzchołka, stad każdy wierzchołek ma przypisany swój wektor normalny. Wektory normalne definiowane są za pomocą funkcji z grupy glNormal3.
Rysunek . Sposób wyznaczania wektorów normalnych.
Void RysujStozek(double h, double r, int nv, int nh)
{
double dH, dAlfa;
int i, j;
// Wyznaczenie kata wyznaczajacego pojedynczy wycinek poziomy
dAlfa = 270.0L/(double)nh;
// Wyznaczenie wysokosci pojedynczego wycinka pionowego
dH = h/(double)nv;
glPushMatrix();
glRotatef(OBSERWATOR_OBROT_Y, 0, 1, 0);
glRotatef(180,0,1,0);
// Wyznaczanie wierzcholkow i wektorow normalnych powierzchni bocznych
glBegin(GL_QUAD_STRIP);
for(i=0; i*dAlfa<=270.0; i++)
{
glNormal3f(0.0, -1.0, 0.0);
glVertex3f(0, 0, 0);
glVertex3f(r*cos(DEG2RAD(i*dAlfa)), 0, r*sin(DEG2RAD(i*dAlfa)));
}
glEnd();
//Powierzchnia boczna
for(j=0; j*dH*1.0E10<=(((h*1.0E10)+0.01)-(dH*1.0E10)); j++)
{
glBegin(GL_QUAD_STRIP);
if (j%2 == 0)
Material_brazowy();
else
Material_szary();
for(i=0; i*dAlfa<=(270.1-dAlfa); i++)
{
glNormal3f(cos(DEG2RAD(i*dAlfa)), 0.0, sin(DEG2RAD(i*dAlfa)));
glVertex3f(((r*(h-(j*dH)))/h)*cos(DEG2RAD(i*dAlfa)), j*dH, ((r*(h-(j*dH)))/h)*sin(DEG2RAD(i*dAlfa)));
glVertex3f(((r*(h-((j+1)*dH)))/h)*cos(DEG2RAD(i*dAlfa)), (j+1)*dH, ((r*(h-((j+1)*dH)))/h)*sin(DEG2RAD(i*dAlfa)));
glVertex3f(((r*(h-(j*dH)))/h)*cos(DEG2RAD((i+1)*dAlfa)), j*dH, ((r*(h-(j*dH)))/h)*sin(DEG2RAD((i+1)*dAlfa)));
glVertex3f(((r*(h-((j+1)*dH)))/h)*cos(DEG2RAD((i+1)*dAlfa)), (j+1)*dH, ((r*(h-((j+1)*dH)))/h)*sin(DEG2RAD((i+1)*dAlfa)));
}
glEnd();
}
glColor3f(0.0,0.0,1.0);
//sciana1
glBegin(GL_QUAD_STRIP);
for(j=0; j*dH<h; j++)
{
if (j%2 == 0)
Material_brazowy();
else
Material_szary();
glNormal3f(0.0, 0.0, -1.0);
glVertex3f(0, j*dH, 0);
glVertex3f((r*(h-(j*dH)))/h, j*dH, 0);
j++;
glNormal3f(0.0, 0.0, -1.0);
glVertex3f(0, j*dH, 0);
glVertex3f((r*(h-(j*dH)))/h, j*dH, 0);
j--;
}
glEnd();
//sciana2
glBegin(GL_QUAD_STRIP);
for(j=0; j*dH<h; j++)
{
if (j%2 == 0)
Material_brazowy();
else
Material_szary();
glNormal3f(1.0, 0.0, 0.0);
glVertex3f(0, j*dH, 0);
glVertex3f(0, j*dH, -(r*(h-(j*dH)))/h);
j++;
glNormal3f(1.0, 0.0, 0.0);
glVertex3f(0, j*dH, 0);
glVertex3f(0, j*dH, -(r*(h-(j*dH)))/h);
j--;
}
glEnd();
glPopMatrix();
Swiatlo_reflektor();
Swiatlo_glowne();
}
Obsługa klawiatury:
void ObslugaKlawiatury(unsigned char klawisz, int x, int y)
{switch(klawisz)
{
- zwiększanie i zmniejszanie liczby pionowych podziałów
case 'v':
lPionowych = (lPionowych == LPION_MAX)? LPION_MAX : lPionowych + 2;break;
case 'V':
lPionowych = (lPionowych == LPION_MIN)? LPION_MIN : lPionowych - 2;break;
- zwiększanie i zmniejszanie liczby poziomych podziałów
case 'h':
lPoziomych = (lPoziomych == LPOZ_MAX)? LPOZ_MAX : lPoziomych + 2;break;
case 'H':
lPoziomych = (lPoziomych == LPOZ_MIN)? LPOZ_MIN : lPoziomych - 2;break;
- zwiększanie oraz zmniejszanie wysokości bryły
case 'w':
wysokosc = (wysokosc == WYS_MAX) ? WYS_MAX : wysokosc + 1;break;
case 'W':
wysokosc = (wysokosc == 1) ? wysokosc : wysokosc - 1;break;
- zwiększanie oraz zmniejszanie promienia bryły
case 'p':
promien = (promien == R_MAX) ? R_MAX : promien + 1;break;
case 'P':
promien = (promien == 1) ? promien : promien - 1;break;
- resetowanie wszystkich parametrów, czyli powrót do stanu początkowego
case 'r':
UstawDomyslneWartosciParametrow();break;
- włączanie i wyłączanie reflektora
case 'q' :
glEnable(GL_LIGHT0);lampka = 1;break;
case 'Q' :
glDisable(GL_LIGHT0);lampka = 0;break;
- włączanie i wyłączanie światła kierunkowego
case 'a' :
glEnable(GL_LIGHT1);gswiatlo = 1;break;
case 'A' :
glDisable(GL_LIGHT1);gswiatlo = 0; break;
- zmiana promienia orbity reflektora
case 'z' : R_L1 += 0.1;break;
case 'Z' : R_L1 -= 0.1;break;
- przyszpieszanie i spowalnianie naszego reflektora(nadanie brędkości)
case 'x' : kat_x = (kat_x == 3) ? 3 : kat_x+1;break;
case 'X' : kat_x = (kat_x == -3) ? 3 : kat_x-1;break;
- zmiana kąta nachylenia orbity reflektora do osi OX
case 'c' : kat_z++;break;
case 'C' : kat_z--;break;
- zmiana odległości od obserwatora
case '+' : OBSERWATOR_ODLEGLOSC++;break;
case '-' : OBSERWATOR_ODLEGLOSC--;break;
- obrót bryły wokół osi OY
case '.' : OBSERWATOR_OBROT_Y = (OBSERWATOR_OBROT_Y == 360) ? 0 : OBSERWATOR_OBROT_Y + 1;break;
case ',' : OBSERWATOR_OBROT_Y = (OBSERWATOR_OBROT_Y == 0) ? 360 : OBSERWATOR_OBROT_Y - 1;break;
// Wcisniecie klawisza ESC powoduje wyjscie z programu
case 27:
exit(0);
}}
Ekran z parametrami:
Widok ilości parametrów takich jak ilość podziałów pionowych i poziomych, wysokość oraz promień stożka, parametry odpowiedzialne za ustawienie reflektora pomagają nam, gdyż wiemy o danym stanie naszego obiektu oraz o jego oświetleniu. W każdej chwili możemy wszystkie te parametry modyfikować.
void RysujNakladke(void)
{
char buf[255];
// Zmiana typu rzutu z perspektywicznego na ortogonalny
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.0, szerokoscOkna, 0.0, wysokoscOkna,-100.0, 100.0);
// Modelowanie sceny 2D (zawartosci nakladki)
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// Zablokowanie oswietlenia (mialoby ono wplyw na kolor tekstu)
glDisable(GL_LIGHTING);
// Okreslenie koloru tekstu
glColor3f(0.0, 0.0, 0.0);
// RYSOWANIE MENU PARAMETROW ZRODLA SWIATLA reflektora
sprintf(buf, "Lampka");
glRasterPos2i(X_OFFSET_SWIATLO, Y_OFFSET_SWIATLO);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - katowa predkosc (%.1f) x,X", kat_x);
glRasterPos2i(X_OFFSET_SWIATLO, Y_OFFSET_SWIATLO - 10);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - promien orbity (%.1f) z,Z", R_L1);
glRasterPos2i(X_OFFSET_SWIATLO, Y_OFFSET_SWIATLO - 20);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - kat nachylenia do osOX (%.1f) up,down", kat_z);
glRasterPos2i(X_OFFSET_SWIATLO, Y_OFFSET_SWIATLO - 30);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - wlaczona lampka (%s)", (lampka == 1)? "tak":"nie");
glRasterPos2i(X_OFFSET_SWIATLO, Y_OFFSET_SWIATLO - 40);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
// RYSOWANIE MENU PARAMETROW polozenia
glColor3f(0.0, 0.0, 0.0);
sprintf(buf, "polozenie");
glRasterPos2i(X_OFFSET_MAT, Y_OFFSET_MAT);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - swiatlo (%s)", (gswiatlo == 1)? "tak": "nie");
glRasterPos2i(X_OFFSET_MAT, Y_OFFSET_MAT - 10);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - odleglosc od obserwatora (%.1f)", OBSERWATOR_ODLEGLOSC);
glRasterPos2i(X_OFFSET_MAT, Y_OFFSET_MAT - 20);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - obrot wokol OY (%.1f)", OBSERWATOR_OBROT_Y);
glRasterPos2i(X_OFFSET_MAT, Y_OFFSET_MAT - 30);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
// RYSOWANIE MENU PARAMETROW WALCA
glColor3f(0.0, 0.0, 0.0);
sprintf(buf, "Stożek:");
glRasterPos2i(X_OFFSET_OBIEKT, Y_OFFSET_OBIEKT);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - promien (%.1f)", promien);
glRasterPos2i(X_OFFSET_OBIEKT, Y_OFFSET_OBIEKT - 10);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - wysokosc (%.1f)", wysokosc);
glRasterPos2i(X_OFFSET_OBIEKT, Y_OFFSET_OBIEKT - 20);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - podzialow pionowych (%d)", lPionowych);
glRasterPos2i(X_OFFSET_OBIEKT, Y_OFFSET_OBIEKT - 30);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
sprintf(buf, " - podzialow poziomych (%d)", lPoziomych);
glRasterPos2i(X_OFFSET_OBIEKT, Y_OFFSET_OBIEKT - 40);
RysujTekstRastrowy(GLUT_BITMAP_8_BY_13, buf);
// Przywrocenie macierzy sprzed wywolania funkcji
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
// Odblokowanie oswietlenia
glEnable(GL_LIGHTING);
}
Zrzuty ekranu:
Rysunek . Podstawowy widok figury.
Rysunek . Powiększona figura, z zwiększoną ilością podziałów pionowych i poziomych oraz z włączonym światłem kierunkowym.
Rysunek . Figura podobnie jak wyżej tylko tym razem reflektor.
Rysunek . Figura z dwoma rodzajami oświetlenia oraz zmienionymi parametrami reflektora, prędkość kątowa oraz kąt nachylenia do osi OX.
Rysunek 6. Podobnie jak wyżej tylko minimalna ilość podziałów pionowych.
Z zrzutów ekranu można wnioskować, że dobrze dobrałem parametry ustawienia światła oraz materiałów. Materiał brązowy miał być matowy i jest, gdyż pod wpływem oświetlenia, jego barwa nie ulega drastycznej zmianie. Co do koloru szarego, też uważam że został on poprawnie zaprojektowany, gdyż przyjmuje kolor oświetlenia w czasie padania na niego światła. Jest to efekt błyszczącego materiału, który odbija padające na nie światło.
Wnioski:
Podczas ćwiczenia, mieliśmy za zadanie zaprojektować dwa rodzaje oświetlenia oraz pokolorować naszą figurę dwoma rodzajami materiału w sposób określony w treści zadania. Zadanie, zostało zrealizowane w pełni pomyślnie. Powyższe ćwiczenie nauczyło nas technik, które są niezbedne do zamodelowania oświetlania przy pomocy biblioteki OpenGL. Do poprawnego wykonania tego zadania była potrzebna wiedza oraz umiejętności zdobyte na popszednich laboratoriach. Teraz dzięki zdobytym umiejętnościom potrafimy zaprojektować bardziej realistyczną scenę. Oświetlenie oraz projektowanie materiałów daje naprawdę realistyczny efekt, możemy jednocześnie stworzyć kilka źródeł światła oraz różnorodne materiały co daje jeszcze bardziej fascynujący efekt. Mimo wszystko, to było nasze pierwsze zetknięcie z tego typu modelowaniem i uważam, że odpowiedni dobór parametrów oświetlenia oraz materiałów nie jest w cale taki łatwy. Zajęło mi to sporo czasu, ale w końcu udało mi się usiągnąc zamierzony efekt, z czego jestem bardzo zadowolony. Nabyta przez nas więdza na tym przedmiocie na pewno przyda nam się w przyszłości. Jako informatycy będziemy mieć nieptrzerwany kontakt z jezykiem programowania C++, a znajomość biblioteki OpenGL będzie podnosić nasze kwalifikacje.