wiki:opengl-intro

Računalniška grafika z OpenGL

Leon Kos

Predstavljeni so bistveni prijemi pri programiranju računalniške grafike z grafično knjižnico OpenGL. Ta dokument naj bi bil osnova za vaje pri predmetih RPK, OPK, PK in izdelavo seminarjev s tega področja. Ker študenti FS pridobijo znanje programiranja v jeziku Fortran, so primeri podani za ta jezik. To pa ne pomeni, da je teorija omejena le na ta jezik, saj brez bistvenih popravkov kode lahko pišemo tudi v jeziku C in C++.

Uvod

Za modeliranje posebnih modelov običajno ni možno uporabiti splošno namenskih grafičnih orodij. To se pokaže predvsem pri vizualizaciji inženirskih preračunov. Rezultati modeliranja običajno niso le funkcije ampak kompleksni objekti kot so grafi, vodi, hierarhične strukture, animacije gibanja, mehanizmi, kontrola poti, volumski modeli posebnih oblik, ...

Skozi razvoj računalnikov so se uvajali različni standardi za grafiko. Od začetnikov kot je GKS in njegovega naslednika PHIGS je ostal le še spomin. To pa predvsem zaradi zahtevnosti implementacije in zaprtosti kode. Kot edini odprti standard obstaja le OpenGL, ki ga je najprej uvedel SGI na svojih grafično podprtih delovnih postajah. Poleg OpenGL obstaja tudi Microsoftov Direct3D, ki pa je omejen na PC računalnike z Windows in ni tako enostaven za uporabo kot OpenGL, ki se je zaradi svoje odprtosti in zmogljivosti uveljavil na vseh operacijskih sistemih in strojnih platformah.

Jezik OpenGL je konstruiran kot strojno-neodvisen vmesnik med programsko kodo in grafičnim pospeševalnikom. Strojna neodvisnost jezika OpenGL poneni tudi to, da v specifikaciji jezika ni podpore za nadzor okenskega sistema in dogodkov (events) pri interaktivnem programiranju. Za tak nadzor so za vsak operacijski sistem izdelani vmesniki, ki povezujejo OpenGL stroj z okenskim sistemom.

Zaradi specifičnosti različnih okenskih sistemov (Windows, Xwindow, MacOS, BeOS) je potrebno za vsak sistem uporabiti posebne prijeme pri klicanje OpenGL ukazov. Da bi vseeno lahko pisali programe, s sicer omejeno funkcionalnostjo uporabniškega vmesnika, se je izdelala knjižnica GLUT (GL UTility), ki vse razlike med operacijskimi sistemi kompenzira in vpeljuje skupen način manipuliranja z dogodki (events). S knjižnico GLUT je tako mogoče pisati prenosljive programe, ki so enostavni za programiranje in dovolj zmogljivi za nezahtevne uporabniške vmesnike.

Enostavni OpenGL program

Osnovni jezik OpenGL je podan v knjižnici GL. Bolj zahtevni primitivi se gradijo z knjižnico GLU (GL Utility) v kateri so podprogrami, ki uporabljajo rutine GL. Rutine GLU vsebujejo več GL ukazov, ki pa so splošno uporabni in so bili zato standardizirani.

Dogodki

Vsi okenski vmesniki delujejo na principu dogodkov (events). To so signali okenskega sistema, ki se pošiljajo programu. Naš program je tako v celoti odgovoren za vsebino okna. Okenski sistem mu le dodeli področje (okno) katero vsebino mora popolnoma nadzorovati. Poleg dodeljenega področja pa okenski sistem pošilja še sporočila našemu programu. Najbolj pogosta sporočila so:

display Prosim obnovi (nariši) vsebino okna. Več možnih primerov je, da se to zgodi. Lahko je drugo okno odkrilo del našega okna, okno se je premaknilo na omizju ali pa se je ponovno prikazalo po tem, ko je bilo ikonizirano. Prestrezanje tega dogodka je obvezno, saj mora prav vsak program poskrbeti, da se vsebina okna obnovi.

reshape Velikost/oblika okna se je spremenila. Poračunaj vsebino okna za novo velikost. Ta dogodek se zgodi, kadar uporabnik z miško spremeni velikost okna.

keyboard Pritisnjena je bila tipka na tipkovnici.

mouse Stanje gumbov na miški se je spremenilo. Uporabnik je pritisnil ali sprostil enega od gumbov.

motion Uporabnik premika miško ob pritisnjenem gumbu.

timer Program zahteva sporočilo po preteku določenega časa, da bo popravil vsebino okna. Primerno je za časovne simulacije.

Seveda poleg naštetih dogodkov obstajajo še drugi dogodki, za katere lahko skrbi naš program. Ni pa potrebno, da naš program skrbi za vse naštete dogodke. Običajno mora program povedati okenskemu sistemu, za katere dogodke bo skrbel in za te dogodke mu bo sistem tudi pošiljal sporočila.

GLUT

Za abstrakcijo dogodkov okenskega sistema v našem primeru skrbi knjižnica GLUT. Primer minimalnega programa, ki nariše črto, je naslednji:

#include <GL/glut.h>

void display()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0, 0.4, 1.0);
  glBegin(GL_LINES);
  glVertex2f(0.1, 0.1);
  glVertex3f(0.8, 0.8, 1.0);
  glEnd();
  glFlush();
}

int main(int argc, char *argv[])
{
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
  glutCreateWindow("C GLUT program");
  glutDisplayFunc(display);
  glutMainLoop();
  return 0;
} 

C program je sestavljen iz dveh delov: glavnega programa main in podprograma display. Z ukazom glutInit inicializiramo GLUT knjižnico podprogramov. Sledi zahteva po vrsti okna. S konstantama GLUT_SINGLE in GLUT_RGB povemo, da želimo okno z eno ravnino tribarvnega RGB prostora. spremenljivka window hrani številko okna, ki jo naredi glutCreateWindow in hkrati pove OS, kakšen naj bo napis na oknu. Okenskemu sistemu moramo še dopovedati, katere dogodke bo program prestrezal. Za podani primer je to le prikaz vsebine okna. S klicem podprograma glutDisplayFunc prijavimo OS, da naj pošilja sporočila za izris, knjižnici GLUT pa s tem dopovemo, da ob zahtevi za ponovni izris pokliče podprogram display.

Zadnji klic v glavnem programu je vedno glutMainLoop. Ta podprogram se konča le takrat, ko se konča celoten program. Kot že ime podprograma govori, je to glavna zanka programa, v kateri GLUT sistematično sprejema sporočila in kliče naše podprograme za dogodke. V podanem primeru je to le en tip dogodkov, ki je tudi najbolj uporaben - display. Ostale tipe dogodkov pa glutMainLoop obdeluje na privzeti način, ki je lahko preprosto ignoriranje sporočila ali pa klicanje vgrajenega podprograma, ki obdela sporočilo. Izgled glavnega programa je tako običajno zelo podoben za vse tipe GLUT programov, v katerem si sledijo ukazi v naslednjem zaporedju:

1. Vključi definicije konstant GLUT z ukazom #include <GL/glut.h>

2. Inicializiraj GLUT

3. Nastavi parametre okna (položaj, velikost, tip, bitne ravnine, pomnilnik)

4. Naredi okno in ga poimenuj

5. Prijavi podprograme, ki jih bo program izvajal ob dogodkih. Obvezno prestrezanje je le za display. Ostali so poljubni.

6. Nastavi lastnosti OpenGL stroja. To so običajno ukazi glEnable ali pa kakšna nastavitev luči, materialov in obnašanja GL stroja. V tem področju se običajno nastavi tudi ostale spremenljivke, ki niso neposredno vezane na OpenGL, ampak na samo delovanje programa, ki poleg prikaza dela še kaj drugega.

7. Zadnji je klic glutMainLoop, iz katerega se program vrne, ko zapremo okno. Ob tem glavni program konča.


Izris v jeziku OpenGL

V podprogramu display se morajo torej nahajati ukazi, ki nekaj narišejo v okno. To so ukazi v jeziku OpenGL ali krajše v jeziku GL. Vsi podprogrami ali funkcije v GL imajo predpono pri imenu gl oziroma za Fortran fgl. Te predpone so potrebne zaradi možnega prekrivanja z imeni v drugih knjižnicah in jezikih. Za razumevanje jezika se lahko funkcije razlaga brez teh predpon, saj je OpenGL koncipiran tako, da so tipi argumentov za vse jezike podobni. Za posamezen jezik se predpostavi prefiks (za Fortran je to fgl, za C pa gl)

Podprogram display torej skrbi za izris vsebine okna. Z ukazom Clear se briše celotno področje okna. Kaj konkretno se briše povemo z argumentom. V našem primeru je to GL_COLOR_BUFFER_BIT, kar pomeni brisanje vseh točk v barvnem pomnilniku.

Z ukazom Color se nastavi trenutna barva grafičnih gradnikov, ki se bodo izrisovali v nadaljnih ukazih. Kot argument se podaja barva v RGB komponentah. Običajno imajo GL ukazi na koncu imena tudi oznako tipa argumentov, ki jih je potrebno podati pri klicu podprograma. To je običaj za skoraj vse jezike. Tako imamo za Fortran in za C pri podprogramih, kot končnico imena še oznako o številu argumentov in tip argumentov. Podprogram glColor3f torej pomeni, da zahteva podprogram tri argumente tipa float, kar je ekvivalentno v fortranu tipu real ali real*4. Najbolj uporabljane GL ukaze lahko torej podamo z različnimi tipi argumentov za isti ukaz. Izbor tipa argumentov je odvisen od programerja in njegovih zahtev. Tako so argumenti za isto funkcijo izbrani glede na priročnost. Za podani primer imamo ukaz Vertex v dveh oblikah in isti tip argumentov. Vertex2f pomeni, da podajamo kordinate vozlišča z dvema argumentoma. Tipi argumentov (končnic) so naslednji:

f V jeziku C float in real ali real*4 za Fortran

d double za C in real*8 za Fortran

i integer

s short integer v C-ju ali integer*2 za Fortran

Namesto fiksiranega števila argumentov obstajajo tudi funkcije, ki imajo podan argument v obliki vektorja. Za to se uporabi končnica v. Nekaj primerov končnic:

3f Sledijo trije argumenti realnih števil

3i Sledijo trije argumenti celih števil

3fv Sledi vektor treh realnih števil

Ker je GL v osnovi 3D, pomeni, da so argumenti, če sta podani le dve koordinati x in y v ravnini z=0. Torej je to ekvivalentno klicu podprograma Vertex(x, y, 0). V našem primeru imamo tudi nastavitev vozlišča z ukazom Vertex3f(0.8,0.8,1.0), ki podaja vse tri koordinate v prostoru. Koordinata z=1 je torej podana, vendar je zaradi privzetega začetnega ortografskega pogleda v prostor ravnine (x,y) koordinata z po globini neopazna. Če pa bi bila projekcija perspekvivna in ne ortogonalna, bi opazili tudi vpliv koordinate z. Raznolikost tipov argumentov se pokaže ravno pri podajanju vozlišč, saj obstajajo naslednjji podprogrami: glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv, glVertex3sv, glVertex4dv, glVertex4fv, glVertex4iv, glVertex4sv. In vse to za en sam ukaz.

Namen velikega števila istih podprogramov za isto funkcijo je opustitev pretvarjanja tipov in s tem pisanje bolj razumljive in hitrejše kode. V jeziku C++ ali Java, ki pa sam dodaja ustrezne argumente k imenom funkcij, pa bi lahko obstajal le en podprogram (npr. glVertex), jezik pa bi sam dodal ustrezne končnice in s tem klical ustrezno funkcijo.

Izris grafičnih elementov risbe se v GL podaja med ukazoma glBegin in glEnd. Predmet risanja podamo kot argument v ukazu Begin. Na splošno velja, da funkcije brez končnic zahtevajo en sam argument s konstanto, ki je podana v header datoteki s končnico .h in se vključuje za začetek programske enote s stavkom #include <GL/glut.h>. Za Fortran je programska enota vsak podprogram, zato moramo pri vsakem podprogramu, ki uporablja te konstante, na začetku dopisati še vključevanje teh konstant. Za C je modul .c datoteka, in ni potrebno vključevanje definicij konstant za vsak podprogram, kot je to nujno za Fortran.

Vse te GL konstante, ki so napisane v gl.h in glu.h imajo standardno predpono GL_ in je za vse jezike enoznačen. Ker pa Fortran ne zahteva deklaracje spremenljivk in ima implicitno definirane tipe je prav možno, da se zatipkamo pri imenu konstante, kar za Fortran pomeni realno številko z vrednostjo 0.0. Da se izognemo takim težavam, se priporoča ukaz implicit none, s katerim izključimo predpostavljene tipe in moramo za vsako spremenljivko povedati, kakšnega tipa je. žal pa F77 ne omogoča prototipov tako, da je še vedno potrebna pazljivost, kakšne tipe podajamo kot argumente podprogramom. Posebno to velja za podprograme GLU, ki običajno nimajo tako razvejanih možnosti argumentov, kot knjižnica GL.

Zadnji ukaz glFlush dopove GL stroju naj vse te ukaze, ki jih je sprejel do sedaj, spravi iz svojih internih pomnilnikov v okno okenskega sistema. Ker imamo v našem primeru le enostaven izris, smo se odločili le za en slikovni pomnilnik (GLUT_SINGLE), ki je primeren le za statične slike. Za aplikacije pri katerih se vsebina zaslona pogosto spreminja, je primerneje uporabiti okno z dvema grafičnima pomnilnikoma GLUT_DOUBLE. Prednost slednjega je v tem, da v en pomnilnik rišemo, drugega pa prikazujemo. Rišemo v ravnino, ki je v ozadju. Ob koncu risanja pa le zamenjamo ravnini. Ker pa je to odvisno od sistema, se ukaz za zamenjavo risalnih ravnin imenuje glutSwapBuffers. Prednost takega načina se pokaže pri animacijah.

Geometrijski primitivi

Uporabiti je mogoče le enostavne primitive. To pa predvsem zaradi zahtevane hitrosti in enostavosti izdelave strojnega pospeševanja. Ločimo tri vrste teh enostavnih primitivov:

  • točke ali pike
  • črte
  • ploskvice konveksnega tipa

Bolj zahtevne predstavitve izvedemo z kombiniranjem teh primitivov. Tako krivulje različnih tipov aproksimiramo z lomljenkami, površine pa s ploskvicami. Za najbolj razširjene kompleksne tipe se že nahajajo podprogrami v knjižnici GLU. Obstajajo tudi možnosti sestavljenih enostavnih gradnikov za črte in ploskvice. Za črte poznamo tako naslednje možnosti:

GL_LINES Pari vozlišč podajajo posamezne segmente

GL_LINE_STRIP Zaporedje povezanih vozlišč podaja lomljenko

GL_LINE_LOOP Lomljenka se zaključi tako, da poveže prvo in zadnje vozlišče.

Konstante so podane kot argument za podprogram Begin. Za ploskve se podaja več točk. Najenostavnejše ploskve so trikotniki. Možni so še ravninski štirikotniki in konveksni ravninski mnogokotniki. Enostavne elemente lahko podajamo tudi v pasovih in trikotnike v pahljačah:

GL_TRIANGLES Tri vozlišča za en trikotnik

GL_TRIANGLE_STRIP Pas trikotnikov. Tri vozlišča za prvi in nato vsako nadaljnje vozlišče k prejšnjemo trikotniku doda nov trikotnik.

GL_TRIANGLE_FAN Pahljača: vsako dodatno vozlišče naredi dodaten trikotnik v smislu dežnika.

GL_QUADS Ravninski štirikotnik se podaja s štirimi vozlišči.

GL_QUAD_STRIP Dodatni štirikotniki gradijo pas z dodajanjem parov vozlišč.

GL_POLYGON En sam konveksni mogokotnik poljubnega števila vozlišč.

Za nesestavljeni tipe gradnikov lahko med Begin in End podamo tudi več vozlišč. Tip gradnika se pri pri tem avtomatsko ponovi. Med Begin in End se lahko uporabljajo še ukazi za barvo glColor in normale glNormal. Ti ukazi nastavljajo trenutno stanje, ki velja za vsa naslednja vozlišča. Primer podprograma za prikaz enega trikotnika v ravnini je naslednji:

void display()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_TRIANGLES);
  glColor3f(1.0, 0.0, 0.0);
  glVertex2f(-1.0, -1.0);
  glColor3f(0.0, 1.0, 0.0);
  glVertex2f(0.0, 1.0);
  glColor3f(0.0, 0.0, 1.0);
  glVertex2f(1.0, 0.0);
  glEnd();
  glFlush();
}

Pred vsako točko je podana še trenutna barva. Izrisani trikotnik tako ni enotne barve ampak se njegova notranjost preliva iz ene skrajne barve v drugo. Rezultat prikazuje slika 1. Uporaba različnih barv v vozliščih mogoče ni posebno uporabna. Za podajanje normal pa je običajno potrebno, da so normale v vozliščih različne. Različne normale v vozliščih nastajajo povsod tam kjer imajo ploskvice skupen rob, za katerega želimo, da ima gladek prehod. To pa je povsod tam, kjer aproksimiramo 'gladko' površino z osnovnimi gradniki. Slika 2 kaže splošno postavitev treh točk v prostoru. Normalo za trikotnik z vozlišči r0, r1, r2 izračunamo z vektorskim produktom

n = [(r1 - r0)×(r2 - r0)] / |(r1 - r0)×(r2 - r0)|

Imenovalec zgornje enačbe je dolžina vektorja n. Normala je pravokotna na razliko vektorjev, ki podajajo vozlišče gradnika.

Slika 1: Trikotnik s podanimi barvami v vozliščih

PNG normal0

Slika 2: Normala

Geometrijske transformacije

Osnova vseh grafičnih knjižnic so tudi osnovne geometrijske transformacije, kot so:

Translate(x, y, z) Premik v smeri vektorja

Rotate(fi, x, y, z) Rotacija za fi stopinj okoli osi podane z (x, y, z)

Scale(x, y, z) Skaliranje po posameznih oseh

Ukazi za transformacije se ne smejo pojavljati med Begin/End?, saj bi to pomenilo, da se transformacija spreminja med izrisom. Geometrijske transformacije nam pomagajo pri modeliranju, saj lahko podajamo vozlišča gradnikov v nekem poljubnem koordinatnem sistemu. To je lahko svetovni koordinatni sistem ali lokalni koordinatni sistem. Za primer izberimo izris krivulje y(x)=sin(x) v jeziku GL. Kot smo že opazili, je prednastavljeno okno v GLUT obliki kvadrata, velikosti (-1,-1) do (1,1). Vsega skupaj torej dve enoti. V zaslonskih koordinatah je prednastavljena velikost okna 300×300 pikslov. Za nas je pomembno, da sinus narišemo v mejah od -1 do 1. Vzemimo primer, ko predvidimo število točk. Podprogram za izris je naslednji:

void display()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_LINE_STRIP);
  for(i=0;i<=10;i++)
  {
    y=sin((i-5)/5.0*3.14);
    glVertex2f((i-5)/5.0, y/3.14);
  }
  glEnd();
  glFlush();
}

Da smo spravili naših 11 točk lomljenke v okvir -1, 1 je bilo potrebno premakniti koordinatni sistem osi x za 5, ga nato še skalirati tako, da smo iz območja [ 0,10 ] dobili območje [-3.14, 3.14]. čeprav smo za izračun koordinate y potrebovali na osi x območje [-3.14, 3.14] pa je potrebna os x za izris v območju [-1,1]. Zato pri izrisu podajamo os x tako, da ponovno poračunavamo območje [ 0,10 ] v območje [-1,1], tako da i-ju odštejemo 5 in delimo z 5. Lahko bi tudi delili s 5 in odšteli

  1. Nekoliko bi poenostavili stvari, če bi imeli vsaj en kordinatni

sistem že takoj uporaben. Recimo os x. Zanka se nekoliko poenostavi, še vedno pa je potrebno vse koordinate pomanjšati za 3.14 oziroma poskalirati.

for(x=-3.14;x<=3.14;x+=0.6)
{
  y=sin(x);
  glVertex2f(x/3.14, y/3.14);
}

Bolj razumljivo bi bilo risati kar v lokalnem koordinatnem sistemu in prednastaviti pomanjšavo modela. Za pomanjšavo uporabimo ukaz za skaliranje, ki posamezne koordinate množi s konstanto 1/3.14, preden se izriše. Podprogram za izris je naslednji:

void display()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glScalef(1/3.14, 1/3.14, 1.0);
  glBegin(GL_LINE_STRIP);
  for(x=-3.14;x<=3.14;x+=0.6)
  {
    y=sin(x);
    glVertex2f(x, y);
  }
  glEnd();
  glFlush();
}

Prednost takega načina razmišljanja se pokaže, že ko želimo pod sinusom narisati še krivuljo kosinusa. Seveda ni možno obeh krivulj risati z GL_LINE_STRIP v isti zanki. Zato se odločimo za ponovno risanje v lokalnem koordinatnem sistemu in prednastavimo pomik navzdol za 1.5 enote.

void display()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glScalef(1/3.14, 1/3.14, 1.0);
  glBegin(GL_LINE_STRIP);
  for(x=-3.14;x<=3.14;x+=0.6)
  {
    y=sin(x);
    glVertex2f(x, y);
  }
  glEnd();
  glTranslatef(0.0, -1.5, 0.0);
  glBegin(GL_LINE_STRIP);
  for(x=-3.14;x<=3.14;x+=0.6)
  {
    y=cos(x);
    glVertex2f(x, y);
  }
  glEnd();  
  glFlush();
}

Podani program za kosinus ne nastavlja ponovno skaliranja, saj je ukaz že pred tem nastavil pomanjšavo. Translacija za -1.5 se izvede v koordinatnem sistemu kosinusa. Splošen napotek za razumevanje transformacije vsakega vozlišča je, da se za podano koordinato upoštevajo transformacije, kot so napisane od spodaj na vzgor. Transformacija, ki se izvede zadnja je torej napisana na prvem mestu v programu. Tak način transformiranja točk nam omogoča enostavnejše modeliranje. Koordinata y kosinusa se izračuna tako, da se pred izrisom najprej vsaki točki y prišteje translacija -1.5 in potem se še izvede skaliranje tako, da se ta vmesna točka pomnoži še z 1/3.14.


Nadzor transformacijske matrike

OpenGL pa za izračun koordinat ne hrani vse zgodovine posameznih transformacij za nazaj, saj bi bilo to računsko potratno. Vse te transformacije, ki jih v poljubnem zaporedju navajamo v programu, popravljajo transformacijsko matriko. OpenGL ima le dve aktivni transformacijski matriki, ki jih uporablja za poračun koordinat. Prva matrika je modelna, druga pa je projekcijska. Mi bomo uporabljali le modelno transformacijo in upoštevali, da projekcijska matrika omogoča prikaz ravnine (x,y) v področju [-1,1]. Modelna matrika je tudi stalno aktivna, če se ne izbere projekcijsko.

Modelna matrika se ob vsakem klicu transformacijskega podprograma popravi. Začetna oblika modelne matrike je enotska. Vsak klic podprograma Translate, Scale in Rotate pa matriko popravi tako, da so upoštevane vse prejšnje transformacije in nad njimi še novo podana transformacija. Matrika je torej stalna, kar se kaže tudi v napaki prejšnjega programa za izris sinusa in kosinusa, ki je pri vsakem ponovnem izrisu trikrat manjši. To lahko preverimo tako, da okno prekrijemo s kakim drugim oknom in ga potem ponovno odkrijemo. Kot že omenjeno, je na začetku programa matrika enotska. S podprogramom glLoadIdentity na začetku bi lahko to tudi zagotovili ob vsakem izrisu.

Za bolj zahtevne transformacije je potrebno matriko začasno shraniti in obnoviti. OpenGL ima v ta namen poseben pomnilnik v obliki sklada, v katerega lahko shranjujemo trenutno transformacijsko matriko. V pomnilniku oblike LIFO (Last In, First Out) je prostora je za najmanj 32 matrik. Pomnilnik si lahko predstavljamo kot hladilnik, v katerega shranjujemo matrike. Matriko, ki jo želimo shraniti, potisnemo na začetek in to tako, da vse ostale matrike potisnemo malo naprej na polici. Ko nekaj želimo iz hladilnika, je to lahko le zadnja matrika. če želimo predzadnjo, moramo poprej vzeti zadnjo. Za shranitev trenutne matrike se uporabi glPushMatrix, za ponastavitev iz sklada pa uporabimo glPopMatrix. Izkaže se, da je za modeliranje taka oblika pomnilnika povsem primerna.

Za primer vzemimo primer kocke sestavljene iz šestih ploskev. Za izris kvadrata obstaja že krajša funkcija glRectf(x1, y1, x2, y2) za ravnino z=0. če želimo imeti kvadrat v poljubni ravnini, pa uporabimo transformacije.

float kvadrat(int i) 
{
  float r[6]={1,0,0,1,1,1}, g[6]={0,1,0,1,0,0}, b[6]={0,0,1,0,1,1}; 
  glPushMatrix();
  glColor3f(r[i], g[i], b[i]);
  glTranslatef(0.0, 0.0, 1.0);
  glRectf(-1.0, -1.0, 1.0, 1.0);
  glPopMatrix();
}

void display()
{
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(30.0, 1.0, 0.0, 0.0);
  glRotatef(30.0, 0.0, 1.0, 0.0);
  glScalef(0.5, 0.5, 0.5);
  kvadrat(1);
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat(2); 
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat(3);
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat(4);
  glRotatef(90.0, 1.0, 0.0, 0.0);
  kvadrat(5);
  glRotatef(180.0, 1.0, 0.0, 0.0);
  kvadrat(6);
  glPopMatrix();
  glFlush();
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_SINGLE|GLUT_DEPTH);
  glutCreateWindow("C GLUT program");
  glutDisplayFunc(display);
  glEnable(GL_DEPTH_TEST);
  glutMainLoop();
  return 0;
}  

Podprogram kvadrat je narejen tako, da riše transliran kvadrat v ravnini z=1. To lahko razumemo kot nov primitiv, saj par ukazov Push/Pop? ne popravlja transformacije ob klicu podprograma. Šest stranic se riše z rotacijo osnovne stranice okoli osi y in x. Modelna matrika se shani z začetnim ukazom Push in potem ponovno obnovi z ukazom Pop.


Globinski pomilnik

Da se ploskve v prostoru pravilno izrisujejo tudi takrat, ko rišemo ploskvice za drugimi, je potrebno uporabiti globinski pomnilnik ali z-buffer. To pa mora omogočati že sam okenski sistem, zato je potrebno tak način prikaza zahtevati že pri fglutInitDisplayMode in kasneje še dopovedati GL stroju, da poleg barve točk na zaslonu, shranjuje še koordinato z v svoj pomnilnik. S tem pomnilnikom GL ob rasterizaciji lika za vsako točko ugotovi, če je že kakšna točka po globini pred njim in jo zato ne riše. Z ukazom glEnable(GL_DEPTH_TEST) se zahteva izračunavanje globine, ki jo je potrebno, tako kot barvo, pred vsakim začetkom risanja pobrisati z ukazom glClear.

Slika 3: Kocka brez spodnjega in zgornjega pokrova (kvadrat(5) in kvadrat(6)

če rišemo zaprte modele, potem notranjosti ni možno videti. Primer odprtega modela kaže slika 3. V takih primerih se ob uporabi prostorskega pomnilnika običajno kar polovica ploskvic modela prekrije v celoti in kasneje na zaslonu ni vidna. Skupna značilnost vseh teh ploskvic, ki se prekrijejo je, da imajo normalo površine negativno (nz < 0). Da se izognemo nepotrebni rasterizaciji teh ploskvic, vključimo GL_CULL_FACE. Da pa bo izločanje delovalo, mora imeti GL podatek za normalo površine, ki jo je potrebno podati pred podatki v vozliščih. Za pravilno delovanje globinskega pomnilnika je potrebna tudi nastavitev projekcijske matrike, kot je to opisano v poglavju Transformacije pogleda.

Animacija

Imejmo primer animacije vozil na avtocesti. Predstavljeno bo cestišče v eno smer z dvema pasovoma, voznim in prehitevalnim. Vozila imajo začetni položaj in hitrost. Opazujemo vozišče dolžine 500 metrov. Hitrost vozila med vožnjo se ne spreminja. Spreminja se le položaj vozil (x, y) na cestišču, ki jih izriše podprogram vozilo.

#include <GL/glut.h>

float y[5]={0,50,120,170,200};
float v[5]={50,30,45,31,33};
float pas; 

void ura()
{
  float dt;
  int i;
  dt=0.1;
  for(i=1;i<6;i++)
  {
    y[i]=y[i]+v[i]*dt;
  }
  glutPostRedisplay();
  glutTimerFunc(100, ura, 0);
}

float vozilo(float y, float pas)
{
  glPushMatrix();
  glColor3f(1.0, 1.0, 1.0);
  glTranslatef(pas, y, 0.5);
  glRectf(-2.0, 0.0, 2.0, 6.0);
  glPopMatrix();
}

void display()
{
  int i;
  glClear(GL_COLOR_BUFFER_BIT);
  glPushMatrix();
  glRotatef(-45.0, 0.0, 0.0, 1.0);
  glTranslatef(0.0, -1.0, 0.0);
  glScalef(0.004, 0.004, 0.004);
  glColor3f(0.0, 0.0, 0.0);
  glRectf(-4.0, 0.0, 4.0, 500.0);
  glTranslatef(0.0, -50.0, 0.0);
  for(i=1;i<6;i++)
  {
    if (i<5 && (y[i+1]-y[i])>10.0)
      pas=-2.0;
    else
      pas=2.0;

    vozilo(y[i], pas);
  }
  glPopMatrix();
  glutSwapBuffers();
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE);
  glutCreateWindow("Avtocesta");
  glClearColor(0.0, 0.5, 0.0, 0.0);
  glutDisplayFunc(display);
  glutTimerFunc(100, ura, 0);
  glutMainLoop();
  return 0;
} 

Pas predstavlja odmik v smeri x od sredine cestišča. Vse enote so v metrih. Vozilo je zaradi sorazmerja narisano nekoliko večje. Za animacije je primernejša uporaba dvojnega pomnilnika GLUT_DOUBLE. S tem se izognemo težavam izrisa, saj v trenutku, ko se zgornja plast izrisuje, nemoteno rišemo v spodnjo plast. Ko je spodnja plast izdelana z ukazom glutSwapBuffers, zamenjamo trenutni prikaz.

Za animacijo, pri kateri je zahtevano točno časovno zaporedje, je primerno uporabiti uro (timer), ki program opozori, da je pretekel predpisani čas in da je potrebno izračunati nov položaj vozil. V našem primeru je podana spremeba vsakih 100 ms in zato nov položaj v smeri y linearno narašča za v(i) dt, kjer je hitrost podana v metrih na sekundo. Izbor 0.1s za premik pomeni 1/0.1=10 posnetkov na sekundo, kar je spodnja meja pri animacijah. Po poračunu novih položajev pošljemo sporočilo glutPostRedisplay, da se na novo izriše scena. Lahko bi tudi neposredno klicali display, vendar bi bilo potem potrebno zagotoviti še kompenzacijo hitrosti, saj že v podprogramu ura izgubimo nekaj časa pri izračunu novih položajev. Prostorski pomnilnik v tem primeru ni potreben, saj je zagotovljeno, da se izrisi prekrijejo v pravilnem vrstnem redu.

Transformacije pogleda

Za zahtevnejše načine gledanja na model je potrebno nastaviti projekcijo modela iz svetovnih koordinat v normalizirane oz. zaslonske koordinate. V praksi obstajata dva načina projekcije: ortografska in perspektivna. V tehniški predstavitvah se uporablja predvsem paralelna oz. ortografska projekcija. Le v primeru animacije, kjer želimo poudariti bližino in oddaljenost določenih objektov, se uporablja tudi perspektivna projekcija.

Transformacije v PNG

Slika 4: Zaporedje pretvorbe koordinat vozlišč

OpenGL ločuje projekcijsko matriko in modelno matriko zato, da ni potrebno nastavljati projekcije pri vsakem izrisu. Slika 4 kaže zaporedje transformacij iz svetovnih koordinat v zaslonske. Pri risanju modela običajno začnemo z enotsko ModelView? matriko.

Najpreprostejši način prikaza, kot je bil prikazan tudi v dosedanjih primerih je, da stlačimo naš model s transformacijami v privzete normalizirane koordinate [-1, 1]. Pri tem načinu sta tako modelna kot projekcijska matrika enotski. Modelna matrika je enotska le na začetku vsakega risanja, projekcijska pa je konstantna ves čas. Pri takem načinu ni potrebno preklapljati med trenutno aktivnima projekcijama. In če se zadovoljimo s takim načinom, potem zadostuje tudi privzeta zaslonska transformacija pri spremembi velikosti okna, ki je pri sistemu GLUT le enovrstičen ukaz:

glViewport (0, 0, width, height);

Nekoliko zahtevnejša je sorazmerna sprememba, ki ne bo anamorfično popravljala velikosti okna:

void reshape (int w, int h)
{

  int width, height;
  double left, right, bottom, top, znear, zfar;
  width = w;
  height = h;

  if (w == h)
  {
    left = -width/(1.0*height);
    right = width/(1.0*height);
    bottom = -1.0;
    top = 1.0;
  }
  else
  {
    left = -1.0;
    right = 1.0;
    bottom = -height/(1.0*width);
    top = height/(1.0*width);
  }
  znear = -1.0;
  zfar = 1.0;
  glViewport (0, 0, width, height);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity();
  glOrtho(left, right, bottom, top, znear, zfar);
  glMatrixMode(GL_MODELVIEW);
} 

Predstavljeni podprogram se priporoča v uporabo za vse programe, ki pripravljajo model v velikosti [-1, 1] za ModelView?. Če bi želeli dodati modelno transformacijo v reshape, potem za zadnjo vrstico dopišemo še modelno transformacijo in nato v programu za izris pred začetkom le obnovimo stanje modelne matrike. Primer animacije bi tako imel namesto nastavitve modelne transformacije slednje v podprogramu reshape. Začetna nastavitev modelne matrike pred začetkom izrisa v podprogramu display pa bi bila:

glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
... izris
glPopMatrix();

Takoj za brisanjem zaslona z ukazom Push shranimo modelno matriko in jo ob koncu ponovno nastavimo na začetno vrednost. V podprogramu reshape, pa modelno matriko popravljamo:

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(-45.0, 0.0, 0.0, 1.0);
glTranslatef(0.0, -1.0, 0.0);
glScalef(0.004, 0.004, 0.004);

Tak pristop nekoliko jasneje predstavi program, saj so vse enote, s katerimi manipuliramo, v programu za izris v svetovnih oz. modelnih koordinatah. V spošnem se priporoča nastavitev projekcije za vse modele, ki uporabljajo izris ploskev. To pa zaradi tega, ker je privzeta projekcijska matrika enotska. Poglejmo to na primeru paralelne projekcije glOrtho(l,r,bn,f):

PM = [ 2/(r-l), 0, 0, (r+l)/(l-r); 0, 2/(t-b), 0, (t+b)/(t-b); 0, 0, 2/(f-n), (f+n)/f-n); 0, 0, 0, 1 ]

Za primer normalizacijskega prostora v obsegu [-1,1] je tako matrika paralelne projekcije

PM(-1, 1, -1, 1, -1, 1) = [ 1, 0, 0, 0; 0, 1, 0, 0; 0, 0, -1, 0; 0, 0, 0, 1 ]

kar se razlikuje od enotske prav v koordinati z. Če bi projekcijsko matriko ohranili enotsko, potem bi to pomenilo, da objekt gledamo kot zrcalno sliko zadnje strani. Nastavitev projekcijske matrike je torej obvezna za vse izrise ploskvic po globini, kot tudi za modele z osenčenjem. Zaradi tega je tudi program za izris kocke nelogično postavil v ospredje modro stranico in ne rdečo.

Osvetlitev

Do sedaj predstavljeni primeri so uporabljali le sintetične barve. To pomeni, da se barva vsake ploskvice ne spreminja v odvisnosti od položaja v prostoru. Tak način prikaza je uporaben le za omejen nabor prostorskih modelov. Neprimeren je že za vse modele, ki imajo površine sestavljene iz primitivov in te površine niso ravninske. Za primer kocke (slika 3) je bilo potrebno za vsako stranico nastaviti svojo barvo, da smo lahko dobili vtis prostora. Če bi kocko risali le z eno barvo, potem bi dobili na zaslon le obris.

Za bolj realističen izris je potrebno vključiti računanje osvetlitve. Žal osvetlitev zajema veliko parametrov, ki jih je potrebno nastaviti preden lahko karkoli dobimo na zaslonu. Tako je potrebno nastavljati položaj in lastnosti luči, osvetlitveni model in lastnosti površin modelov. Za vsako luč se lahko tako nastavi 10 lastnosti in vsaka površina ima 5 lastnosti materiala.

Kot predpogoj za pravilno osvetljen model pa je podana normala v vsakem vozlišču vsake ploskvice. Najpreprostejši način pri uporabi osvetlitve je, da parametre luči ne nastavljamo in da uporabimo nastavljanje lastnosti materiala površine le z ukazom za barvo. S tem vpeljemo veliko predpostavk, ki pa so za šolsko rabo povsem uporabne. Predpostavljena je le ena luč bele svetlobe s položajem (0, 0, 1) in difuzni odboj svetlobe na površini. Barvo površine podajamo kar z običajnim ukazom za barvo. Program za izris osenčenega modela kocke je tako v minimalni obliki naslednji:

#include <GL/glut.h>

float kvadrat() 
{
  glPushMatrix();
  glTranslatef(0.0, 0.0, 1.0);
  glNormal3f(0.0, 0.0, 1.0);
  glRectf(-1.0, -1.0, 1.0, 1.0);
  glPopMatrix();
}

void display()
{
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glColor3f(0.7, 0.6, 0.2);
  glPushMatrix();
  glScalef(0.5, 0.5, 0.5);
  kvadrat();
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat();
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat();
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat();
  glRotatef(90.0, 1.0, 0.0, 0.0);
  kvadrat();
  glRotatef(180.0, 1.0, 0.0, 0.0);
  kvadrat();
  glPopMatrix();
  glFlush();
}

void reshape (int w, int h)
{
  double l;
  l = 1.0;
  glViewport (0, 0, w, h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-l, l, -l, l, -l, l);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glRotatef(30.0, 1.0, 0.0, 0.0);
  glRotatef(30.0, 0.0, 1.0, 0.0);
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_SINGLE|GLUT_DEPTH);
  glutCreateWindow("Osencena kocka");
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_COLOR_MATERIAL);
  glutMainLoop();
  return 0;
}  

Razširjeni primitiv smo poenostavili tako, da ne vsebuje več definicije barve, ampak le geometrijo. Obvezno je bilo potrebno podati izračun normale. Za naš primitiv kvadrata je to (0,0,1). Program za izris v bistvu ni spremenjen, le da je sedaj transformacija modela preseljena v podprogram za nastavitev velikosti okna reshape. V glavnem programu pa je potrebno najprej vključiti računanje osvetlitve GL_LIGHTING, prižgati je potrebno luč št 0, ki ima začetni položaj (0,0,1).

Slika 5: Osenčen model kocke

Z vključitvijo GL_COLOR_MATERIAL pa poenostavimo podajanje barve za material površine tako, da vsi klici podprogramov Color nastavljajo privzeto difuzno in ambientno barvo površine. Slika 5 prikazuje rezultat upodabljanja z osvetlitvijo.

Tekst

OpenGL sam ne podpira teksta in je zato potrebno uporabiti razne prijeme za izris teksta v prostoru. Možnih je več načinov za risanje besedila:

stroke črke so izrisane s črtami v prostoru modela

bitmap črke so izrisane na zaslon

teksture črke so izrisane rastrsko v prostoru modela

V šolskih primerih so najbolj uporabni že izdelani fonti v knjižnici GLUT. Možne so naslednje številke fontov:

  1. GLUT_STROKE_ROMAN
  1. GLUT_STROKE_MONO_ROMAN
  1. GLUT_BITMAP_9_BY_15

  1. GLUT_BITMAP_8_BY_13

  1. GLUT_BITMAP_TIMES_ROMAN_10

  1. GLUT_BITMAP_TIMES_ROMAN_24

  1. GLUT_BITMAP_HELVETICA_10

  1. GLUT_BITMAP_HELVETICA_12

  1. GLUT_BITMAP_HELVETICA_18

Za primer razširimo program za izris osenčene kocke z besedilom na vsaki stranici. Podprogram kvadrat kot argument vzame besedilo. Začetek izpisa premakne za malenkost višje in začne v koordinati x=-0.8. Ker pa ne želimo, da se besedilo

senči, je tu potrebno izklapljanje senčenja takrat, ko izrisujemo posamezne črke. Ker so črke v vnaprej določeni velikost, jih je potrebno ustrezno pomanjšati s skaliranjem. Podprogram glutStrokeCharacter po vsaki izrisani črti sam nastavi pomik v smeri x za širino izrisane črke.

#include <GL/glut.h>                                                  
                                                                                                               
float kvadrat(char *s)
{
  char *c;
  glPushMatrix();
  glTranslatef(0.0, 0.0, 1.0);
  glNormal3f(0.0, 0.0, 1.0);
  glRectf(-1.0, -1.0, 1.0, 1.0);
  glTranslatef(-0.8, 0.0, 0.01);
  glDisable(GL_LIGHTING);
  glScalef(0.003, 0.003, 0.003);
  glColor3f(1.0, 0.0, 0.0);
  for(c=s; *c; c++)
  {
    glutStrokeCharacter(GLUT_STROKE_ROMAN, *c);
  }
  glEnable(GL_LIGHTING);
  glPopMatrix();
}

void display()
{
  float mat[4]={0.9, 0.6, 0.3, 1.0};
  glClear(GL_COLOR_BUFFER_BIT);
  glClear(GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(30.0, 1.0, 0.0, 0.0);
  glRotatef(30.0, 0.0, 1.0, 0.0);
  glScalef(0.5, 0.5, 0.5);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat);
  kvadrat("Spredaj");
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat("Desno");
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat("Zadaj");
  glRotatef(90.0, 0.0, 1.0, 0.0);
  kvadrat("Levo");
  glRotatef(90.0, 1.0, 0.0, 0.0);
  kvadrat("Spodaj");
  glRotatef(180.0, 1.0, 0.0, 0.0);
  kvadrat("Zgoraj");
  glPopMatrix();
  glFlush();
}

void reshape (int w, int h)
{
  double l;
  l = 1;
  glViewport (0, 0, w, h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-l, l, -l, l, -l, l);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_SINGLE|GLUT_DEPTH);
  glutCreateWindow("C GLUT program");
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glutMainLoop();
  return 0;
}

Podajanje barve za površino je spremenjeno tako, da se ne uporabi funkcije Color ampak normalno funkcijo za podajanje lastnosti materialaf glMaterialfv. Rezultat kaže slika

  1. Če bi napisali komentar pred izrisom

štirikotnika, potem bi bilo vidno besedilo tudi za ostale (skrite) strani.

Slika 6: Osenčen model kocke z napisi

Včasih pa raje želimo, da se besedilo na zaslonu ne izrisuje rotirano in senčeno, temveč da se le pojavi na določenem položaju v prostoru in potem izriše v zaslonskih koordinatah. V ta namen uporabimo bitmap fonte in naslednji podprogram za izpis besedila:

float output(x,y,z,char *s)
{
  char *c;
  glRasterPos3f(x,y,z);
  for(c=s; *c; c++)
  {
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *c);
  }
}

Primer izrisa z bitmap fonti kaže slika 7.

Slika 7: Osenčen model kocke z bitmap napisi



Uporabniški vmesnik

V nadaljevanju so prikazani primeri programov, ki izkoriščajo dodatne funkcionalnost knjižnice GLUT za vnos dodatnih podatkov v program. To je predvsem uporaba tipk in miške.

Rotacija s tipkami

Rotiramo že vgrajeni geometrijski model čajnika s tipkami x, y, z. Vsak pritisk na tipko poveča kot rotacije za pet stopinj. Ker izrisujemo žični model, podprogram za reshape ni potreben. Podprogram keyboard ob pritisku na tipko dobi tudi informacijo o zaslonskem položaju miške.

#include <stdio.h>
#include <GL/glut.h>

float rx, ry, rz;

void display()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.5, 0.4, 1.0);
  glPushMatrix();
  glRotatef(rx, 1.0, 0.0, 0.0);
  glRotatef(ry, 0.0, 1.0, 0.0);
  glRotatef(rz, 0.0, 0.0, 1.0);
  glutWireTeapot(0.5);
  glPopMatrix();
  glutSwapBuffers();
}

void keyboard(unsigned char key, int x, int y)
{
  fprintf (stderr,"Key %c at %d, %d\n", key, x, y);
  if (key=='x') 
    rx = rx + 5.0;
  if (key=='y') 
    ry = ry + 5.0;
  if (key=='z') 
    rz = rz + 5.0;

  glutPostRedisplay();
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
  glutCreateWindow("Use keys x, y, and z");
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}  


Miška in inverzna projekcija

Za vsak pritisk gumba miške lahko dobimo poleg koordinate tudi še stanje gumbov. Naslednji primer prikazuje risanje črte v ravnini (x,y) s tem, da je potrebno zaslonske koordinate pretvoriti nazaj v modelne.

#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>

#define MAXN 100

GLint n;
GLfloat *vertex;

void redraw()
{
  int i;
  glClear(GL_COLOR_BUFFER_BIT);
  glPushMatrix();
  glColor3f(1,1,1);
  glBegin(GL_LINE_STRIP);
  for (i = 0; i < n; i++)
  {
    glVertex2fv(&vertex[i*2]);
  }
  glEnd();
  
  for (i = 0; i < n; i++) //Adding spheres on world coords
  {
  glColor3f(1,0,0);
  glPushMatrix();
  glTranslatef(vertex[i*2],vertex[i*2+1],0); 
  glutSolidSphere(0.01,8,8); 
  glPopMatrix();
  }
  glPopMatrix();
  glutSwapBuffers();
}

void mouse(int button, int state, int x, int y)
{
  GLint viewport[4];
  GLdouble mvmatrix[16], projmatrix[16];
  GLdouble wx, wy, wz; //Returned world x, y, z coords 
  GLdouble px, py, pz; //Picked window coordinates 
  int status;
  if (button == GLUT_LEFT_BUTTON )
  {
  if (state == GLUT_DOWN) 
    {
      glGetIntegerv (GL_VIEWPORT, viewport);
      glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);
      glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);
      //Note viewport[4] is height in pixels 
      px = x;
      py = viewport[3] - y - 1;
      pz = 0.0;
      fprintf (stderr, "Coordinates at cursor are %f, %f\n", px, py);
      status = gluUnProject (px, py, pz, mvmatrix, projmatrix, viewport, &wx, &wy, &wz);
      fprintf(stderr, "World coords at z=0.0 are %f, %f, %f\n", wx, wy, wz);
      if (n < MAXN) 
      {
        vertex[n*2] = wx; 
        vertex[n*2+1] = wy;
        n=n+1;
      }       
      else
      {
        fprintf(stderr, "Maximum number of points was received!\n");
      }
      glutPostRedisplay();
    }
  }
}


int main(int argc, char *argv[])
{ 
  vertex = (GLfloat *) malloc(2 * MAXN * sizeof (GLfloat)); 
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
  glutInitWindowSize (700, 700);
  glutInitWindowPosition (0, 0);
  glutCreateWindow("Click in window");
  glutDisplayFunc(redraw); 
  glutMouseFunc(mouse);
  glutMainLoop();
  return 0;
} 

Kvaternionska rotacija

Naslednji program prikazuje vrtenje osenčenega čajnika z miško. V ta namem se uporabi že izdelam podprogram v jeziku C, ki nam omogoča kvaternionsko rotacijo. Za vrtenje enotske krogle je potrebno zaznati tako začetni pritisk na gumb (podprogram mouse) kot vse naslednje pomike miške (podprogram motion).

#include <GL/glut.h>
#include "trackball.h"
#include "trackball.c"

GLfloat m[4][4];                
float last[4];
float cur[4]; 
int width; 
int height;
int beginx;
int beginy;
float p1x; 
float p1y; 
float p2x; 
float p2y;

void display()
{
  glPushMatrix();
  build_rotmatrix(m, cur);  
  glLoadIdentity();
  glMultMatrixf(*m);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glutSolidTeapot(0.5); 
  glPopMatrix();
  glutSwapBuffers();
}

void mouse(int button,int state, int x, int y)   
{
  beginx = x;
  beginy = y;
}

void motion(int x,int y)   
{
  p1x = (2.0*beginx - width)/width;
  p1y = (height - 2.0*beginy)/height;
  p2x = (2.0 * x - width) / width;
  p2y = (height - 2.0 * y) / height;
  trackball(last,p1x, p1y, p2x, p2y);   
  add_quats(last, cur, cur);   
  beginx = x;
  beginy = y;
  glutPostRedisplay();   
}

void reshape (int w, int h)
{

  width=w;
  height=h;
  double l;
  l = 1;
  glViewport (0, 0, w, h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-l, l, -l, l, -l, l);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}


int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  trackball(cur, 0.0, 0.0, 0.0, 0.0);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutCreateWindow("Rotacija z misko");
  glutDisplayFunc(display);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutReshapeFunc(reshape);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glutMainLoop();
  return 0;
}

Predstavljeni program je sestavljen iz dveh delov. Kodo za rotacijo v jeziku C trackball.c uporabimo kot zunanje podprograme. Rezultat osenčenega model, ki je bil obrnjen z miško, prikazuje slika 8.

Slika 8: Osenčen model čajnika

Seznam primerov

1. minimalni_program.c - Minimalni program, ki nam izriše črto

2. trikotnik.c - Trikotnik s prelivajočimi se barvami

3. sinus_1.c - Izris funkcije sinus s pomočjo premikanja in skaliranja koordinatnega sistema

4. sinus_2.c - Izris funkcije sinus s pomočjo skaliranja vseh koordinat za 3.14

5. sinus_3.c - Izris funkcije sinus z uporabo ukaza za skaliranje glScalef

6. sinus_cosinus.c - Izris funkcije sinus in cosinus

7. kocka.c - Primer kocke sestavljene iz šestih ploskev

8. animacija.c - Animacija vozil na avtocesti

9. osvetlitev.c - Izris osenčenega modela kocke

10. text.c - Izris osenčene kocke z besedilom na vsaki stranici

11. keyboard.c - Rotacija s tipkami

12. mouse.c - Risanje črt z miško

13. rotacija.c, trackball.c, trackball.h - Kvaternionska rotacija

Last modified 13 years ago Last modified on Jun 12, 2012, 8:50:09 PM

Attachments (30)

Download all attachments as: .zip