OpenGL TROKUT
Trokut je osnova matematičkih domena poput geometrije i trigonometrije, a mogu reći i 3D grafike. Točka u matematici je zapravo mjesto u 2D/3D prostoru određeno specifičnim koordinatama na X, Y ili Z osi, a 3D aplikacija koristi se istom logikom. Jedina specifična razlika između 3D matematike i matematike jest da se točke nazivaju vertex (vertices u množini). Teoriju u stranu, kôd na zaslon...
Code:
// header file-ovi koje je potrebno pohraniti
#include <iostream>
#include <GL/glut.h>
using namespace std;
// prototipi funkcija
void initRenderer(void);
void resize(int, int);
void render(void);
void update(int);
//globalne deklaracije
float _angle = 30.f;
int main(int argc, char** argv) {
//inicijalizacija GLUTa
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(400, 400); // postavi win32 prozor
// napravi prozor
glutCreateWindow("Ucionica");
initRenderer(); //inicijalizacija iscrtavanja
// postaviti ručice za funkcije koje manipuliraju scenom i prozorom
glutDisplayFunc(render);
glutReshapeFunc(resize);
glutTimerFunc(25, update, 0);
glutMainLoop(); // pokreće glavnu petlju
return 0; // simbolično, jer nikad se do nje neće doći
}
void initRenderer(void)
{
// omogućuje renderiranje dubine tj. ako se objekt nađe ispred nekog drugog
glEnable(GL_DEPTH_TEST);
glEnable(GL_COLOR_MATERIAL);
glClearColor(0.7f, 0.9f, 1.0f, 1.0f);
}
// poziva se pri mijenjanju visine i širine prozora
void resize(int w, int h) {
// postavljanje viewporta, kako pretvarati podatke u pixele
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION); // perspektiva kamere
// postavljanje perspektive kamere
glLoadIdentity(); // resetiraj kameru
gluPerspective(45.0, // kut kamere
(double)w / (double)h, // aspect ratio ( podijeljena visina sa širinom )
1.0, // ono što je preblizu, ne renderiraj
200.0); // ono što je predaleko, ne renderiraj
}
// iscrtava scenu
void render() {
// očisti ekran od posljednjeg iscrtavanja
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW); // perspektiva za iscrtavanje
glLoadIdentity(); // resetiraj perspektivu iscrtavanja
glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(_angle, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
// trokut, doduše, nabubane koordinate
glVertex3f(0.5f, 00.5f, 0.0f);
glVertex3f(0.0f, 1.5f, 1.0f);
glVertex3f(-1.5f, -0.5f, 0.0f);
glEnd(); // završi upisivanje koordinata verticesa trokuta
glutSwapBuffers(); // zamijeni back buffer sa front bufferom
}
void update (int value)
{
_angle += 2.0f;
if( _angle > 360 )
{
_angle -= 360;
}
glutPostRedisplay();
glutTimerFunc(25, update, 0);
}
Gledajući sam kôd, kao zadrti C++ programeri, i ne izgleda jako strašno. Pa krenimo kao compiler, od vrha prema dolje!
Code:
#include <iostream>
#include <GL/glut.h>
using namespace std;
Dakle, prvo uključujemo iostream header file za kontrolu inputa i outputa. GL/glut.hr je header file kojeg koristimo da utjeramo OpenGL u naš skromni c++ program, kao što vidite onaj GL/ mora biti uključen jer je to putanja nakon što ga compiler dođe do GLUT/include... using namespace std; služi jednostavno tome da ne moramo koristiti std::nešto stalno.
Code:
// prototipi funkcija
void initRenderer(void);
void resize(int, int);
void render(void);
void update(int);
Kao što i sam komentar u programu kaže, ovo su prototipi funkcija, naših naravno, ne onih iz GLUTa.
void initRenderer(void);
Ova funkcija pokreće osnovne rendering parametre o kojima ćemo govoriti više kad dođemo do funkcije.
void resize(int, int);
Funkcija koja će se aktivirati kada korisnik promijeni širinu ili visinu prozora aplikacije.
void render(void);
Funkcija koja će se naći ravno u main loopu i stalno se ponavljati, ovo je mjesto gdje se sva čarolija iscrtavanja dešava.
void update(int);
Ova funkcija služi da ažurira scenu... Kasnije slijedi bolje pojašnjenje.
Code:
//globalne deklaracije
float _angle = 30.f;
Ovdje idu globalne deklaracije našeg programa, a specifično _angle varijabla kako joj i samo ime kaže jest kut kojeg ćemo koristiti da bi rotirali naše 2D/3D objekte.
Code:
int main(int argc, char** argv) {
//inicijalizacija GLUTa
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(400, 400); // postavi win32 prozor
// napravi prozor
glutCreateWindow("Ucionica");
initRenderer(); //inicijalizacija iscrtavanja
// postaviti ručice za funkcije koje manipuliraju scenom i prozorom
glutDisplayFunc(render);
glutReshapeFunc(resize);
glutTimerFunc(25, update, 0);
glutMainLoop(); // pokreće glavnu petlju
return 0; // simbolično, jer nikad se do nje neće doći
}
Ovo je ulazna točka programa. Volio bih istaknuti potrebu za argumentima u funkciji koje ćemo u narednim linijama kôda proslijediti OpenGLu da može dalje manipulirati prozorom.
Code:
//inicijalizacija GLUTa
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(400, 400); // postavi win32 prozor
Kao što komentar ističe, ovdje se događa inicijalizacija samog GLUTa i njegovo ugrađivanje u naš program.
glutInit(&argc, argv);
glutInit() je funkcija koja koristeći argumente iz main() funkcije tj. ulaza u naš program omogućuje ugradnju GLUTa u naš program.
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitDisplayMode() služi za postavljanje osnovnih paramatera potrebnih za bilo koji program pogonjen OpenGL-om. Ubacivanjem par zastavica tj. flag-ova dajemo GLUTu "ideju" kako bi početni display mode trebao izgledati.
glutInitWindowSize(400, 400); // postavi win32 prozor
Postavlja veličinu prozora davajući joj width i height argumente.
Code:
// napravi prozor
glutCreateWindow("Ucionica");
initRenderer(); //inicijalizacija iscrtavanja
Ovaj dio kôda govori GLUT-u da napravi prozor nazvan Ucionica, te poziva funkciju za inicijalizaciju iscrtavanja čiji smo prototip maloprije spominjali.
Code:
// postaviti ručice za funkcije koje manipuliraju scenom i prozorom
glutDisplayFunc(render);
glutReshapeFunc(resize);
Ove dvije funkcije govore GLUTu koju funkciju da koristi za iscrtavanje, a koju za resizing.
Code:
glutTimerFunc(25, update, 0);
glutMainLoop(); // pokreće glavnu petlju
return 0; // simbolično, jer nikad se do nje neće doći
glutTimerFunc(25, update, 0)
Ova funckija omogućuje nam da radimo transformacije u stvarnom vremenu dok protječe vrijeme, najlakše rečeno, animiramo prijelaz iz jedne pozicije u drugu. Prvi argument su milisekunde koje moraju proći prije prvog poziva navedene funkcije u programu, naš odabir je 25, drugi argument je ime funkcije koju koristimo da ažuriramo scenu (update, remember?), a treći argument jednostavno ostavite na 0.
glutMainLoop()
Ovo bi bila OpenGL-ova točka iza koje nema povrata... Dakle, pošto je OpenGL dizajniran proceduralno potrebno je definirati sve redom i onda pokrenuti glavni loop koji u biti nakon aktiviranja se ne vraća. Dakle, onaj return 0; iza njega je čisto simbolično, jer do njega program neće ni doći.
Code:
void initRenderer(void)
{
// omogućuje renderiranje dubine tj. ako se objekt nađe ispred nekog drugog
glEnable(GL_DEPTH_TEST);
glEnable(GL_COLOR_MATERIAL);
glClearColor(0.7f, 0.9f, 1.0f, 1.0f);
}
Kao što vidite, ovo je funkcija koja postavlja osnovu za renderiranje u OpenGLu. Možete ju zamisliti kao paljenje motora u automobilu. A sad je vrijeme da zavirimo ispod haube...
glEnable(GL_DEPTH_TEST)
Ova funckija je prilično jednostavna... Njen zadatak je osigurati mogućnost renderiranja dubine, tj. ako se neki objekt nađe ispred nekog drugog da se dogodi ono što je fizički točno, dakle, ovaj bliži se nalazi ispred, a ovaj drugi negdje iza.
glEnable(GL_COLOR_MATERIAL);
Vrlo jednostavna funkcija, jednostavno postavlja zastavicu da možemo bojiti objekte/pozadinu...
glClearColor(0.7f, 0.9f, 1.0f, 1.0f);
Ovo je boja kojom ćemo čistiti zaslon tj. boja pozadine, a trenutne specifikacije su neka svjetlo plava boja. Radi se o standardnom RGBA sustavu. (A stoji za Alpha, tj. prozirnost, 1.0f znači nema prozirnosti, a kako se vrijednost kreće prema dolje dobivamo finu prozirnost)
Code:
// poziva se pri mijenjanju visine i širine prozora
void resize(int w, int h) {
// postavljanje viewporta, kako pretvarati podatke u pixele
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION); // perspektiva kamere
// postavljanje perspektive kamere
glLoadIdentity(); // resetiraj kameru
gluPerspective(45.0, // kut kamere
(double)w / (double)h, // aspect ratio ( podijeljena visina sa širinom )
1.0, // ono što je preblizu, ne renderiraj
200.0); // ono što je predaleko, ne renderiraj
}
Spominjali smo već prototip, radi se o funkciji koja kontrolira promjenu širine ili visine prozora.... Ima dva argumenta, naravno, radi se o širini i visini.
glViewport(0, 0, w, h)
Ova funkcija služi za postavljanje osnove viewporta, dakle bez potrebe za daljnim kompliciranjem, objašnjenje iz komentara je više nego dovoljno. Ova funkcija koristi se da bi transformirala podatke koje dobiva u vidljive grafičke točkice na zaslonu a.k.a (also known as) pixels.
glMatrixMode(GL_PROJECTION)
Ovo je najjednostavnije rečeno perspektiva kamere.
glLoadIdentity() koju sam preskočio služi za resetiranje kamere
gluPerspective()
Funkcija gluPerspective() postavlja perspektivu prilagođenu ljudskom oku. Ima par argumenata, prvi je 45.0f, a to je kut kojeg korisnikovo oko može vidjeti, sljedeći argument je aspect ratio tj. kako se modeli trebaju skalirati tj. prilagoditi korisnikovu monitoru... To se radi tako da podijelimo širinu sa visinom, no bitna je stvar da castamo varijable w i h kao double data tipove jer tako možemo dobiti preciznije podatke. Zadnja dva argumenta su near i far clipping, near postavljamo na 1.0f, a far na 200.0f... Oni se koriste da izbacimo ono što korisnikovo oko ne može vidjeti (jer je preblizu ili predaleko).
Code:
// iscrtava scenu
void render() {
// očisti ekran od posljednjeg iscrtavanja
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW); // perspektiva za iscrtavanje
glLoadIdentity(); // resetiraj perspektivu iscrtavanja
glTranslatef(0.0f, 0.0f, -5.0f);
glRotatef(_angle, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
// trokut, doduše, nabubane koordinate
glVertex3f(0.5f, 00.5f, 0.0f);
glVertex3f(0.0f, 1.5f, 1.0f);
glVertex3f(-1.5f, -0.5f, 0.0f);
glEnd(); // završi upisivanje koordinata verticesa trokuta
glutSwapBuffers(); // zamijeni back buffer sa front bufferom
}
Polako ali sigurno došli smo do dijela gdje se sva čarolija događa. Ova funkcija iscrtava trenutni frame i stalno se ponavlja u main loopu. Vrijeme je da ju rastavimo na uvozne dijelove.
glClear()
Ovom malenom funkcijom čistimo zaslon od prošle sličice te postavljamo boju pozadine koristeći dvije malene zastavice, a ovo ćete vjerojatno koristiti u većini programa.
glMatrixMode(GL_MODELVIEW)
Ova funkcija jednostavno postavlja MatrixMode na sređivanje transformacija na točkama (verticesima) na sceni.
glLoadIdentity()
U ovoj lekciji zadržat ćemo se na najjednostavnijem objašnjenju, a ono glasi za trenutni segment kôda: "resetiraj perspektivu iscrtavanja".
glTranslatef(0.0f, 0.0f, -5.0f)
Ovu funkciju koristimo da bi pomicali objekte tj. točke pojedinačno okolo/naokolo po prozoru. Od argumenata imamo x, y, z koordinate i forsiramo floating point zbog preciznosti ( 0.0f)
glRotatef(_angle, 0.0f, 0.0f, 1.0f)
Ovo je funkcija koja rotira objekte ili točke na sceni prema određenim pravilima koje utvrđujemo među 4 ljepuškasta argumenta funkcije. Prvi je kut koji koristimo, a njega smo deklarirali gore (30° remember?) i jednostavno ga ubacujemo na ovo mjesto, i onda imamo x, y, z os argumente, pa biramo oko kojih osi želimo rotirati određeni objekt.
Code:
glBegin(GL_TRIANGLES);
// trokut, doduše, nabubane koordinate
glVertex3f(0.5f, 00.5f, 0.0f);
glVertex3f(0.0f, 1.5f, 1.0f);
glVertex3f(-1.5f, -0.5f, 0.0f);
glEnd(); // završi upisivanje koordinata verticesa trokuta
Ovaj segment sam odlučio zasebno obraditi. Dakle, prvo što radimo jest poručimo OpenGLu da želimo iscrtavati stuff. To radimo sa funkcijom glBegin() kojoj pridružujemo zastavicu GL_TRIANGLES čime govorimo OGLu da radimo sa trokutima, tako da automatski nakon svaka 3 vertexa krene na sljedeći trokut. Koordinate su nabubane ali lijepo izgleda u konačnici. glEnd() funkcija kaže OGLu da smo završili iscrtavanje te da krene dalje u avanture iscrtavanja.
glutSwapBuffers()
Prvo da pojasnim dva termina, back buffer i front buffer (eng. buffer == spremnik). Tehnologija iza animiranih serija, 3D igara, 2D igara bazira se na mani ljudskog oka, a to je tromost. Dakle, ljudsko ne može u jednoj sekundi vidjeti više od 20-ak sličica pa se one pričinjavaju kao da su zapravo "žive" da se kreću. E tako kroz mainloop stalno se obavljaju mali milijun operacija stvarajući hrpetinu sličica po sekundi, ovisno koliko je računalo sposobno obraditi. Optimalno je negdje 35-40 sličica u sekundi u današnjim hitovima. Ono što trenutačno vidite na zaslonu je front buffer, a u pozadini se vrti back buffer u kojeg se spremaju slike koje će uskoro biti na zaslonu... Swap buffer, ako niste shvatili dosad, zamjenjuje front buffer sa novim back bufferom koji postaje front buffer... I tako u nedogled tj. dok se program ne prekine. Zanimljivo, ha?
Code:
void update (int value)
{
_angle += 2.0f;
if( _angle > 360 )
{
_angle -= 360;
}
glutPostRedisplay();
glutTimerFunc(25, update, 0);
}
Ovo je poprilično jednostavno... Prvo povećamo kut za 2°, a pošto se ovo konstantno vrti brzo će doći do 360°, pa mi zbog preciznosti floating point varijabli koristeći if statement provjerimo stanje _angle varijable. Ako se nalazi na 360°, oduzmemo 360°, i time dobivajući na efikasnosti kôda. glutPostRedisplay() funkcija služi tome da obavijesti OGL da smo ažurirali scenu te da ju ponovno iscrta. I onda pozivamo već poznatu glutTimerFunc() funkciju. Argumenti su, dakako, isti...
To je to, imate maleni trokut pred vama sada.
Da biste uspješno pokrenuli vaš kompilirani projekt, potrebno je kopirati glut32.dll iz vašeg GLUT direktorija (specifično npr. C:\GLUT\lib). To je to za danas... Nadam se da ste uživali. Vidimo se sljedeći put!