[[PageOutline]] = 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: {{{ #!c #include 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 '' {{{ #!comment 2. Dopovej Fortranu, da so imena, kot je npr. ''display'', podprogrami in ne spremenljivke. To se naredi z ukazom ''external'' }}} '''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. [[BR]] == 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 ''. 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: {{{ #!c 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 '''r,,0,,''', '''r,,1,,''', '''r,,2,,''' izračunamo z vektorskim produktom '''n''' = [('''r,,1,,''' - '''r,,0,,''')×('''r,,2,,''' - '''r,,0,,''')] / |('''r,,1,,''' - '''r,,0,,''')×('''r,,2,,''' - '''r,,0,,''')| Imenovalec zgornje enačbe je dolžina vektorja '''n'''. Normala je pravokotna na razliko vektorjev, ki podajajo vozlišče gradnika. [[Image(color-triangle.png)]] Slika 1: Trikotnik s podanimi barvami v vozliščih [[Image(normal0.png)]] 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: {{{ #!c 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. {{{ #!c 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: {{{ #!c 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. {{{ #!c 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. [[BR]] == 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. {{{ #!c 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''. [[BR]] == 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''. [[Image(half-cube.png)]] 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 (''n,,z,, < 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''. {{{ #!c #include 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. [[Image(viewing.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: {{{ #!c glViewport (0, 0, width, height); }}} Nekoliko zahtevnejša je sorazmerna sprememba, ki ne bo anamorfično popravljala velikosti okna: {{{ #!c 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: {{{ #!c 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: {{{ #!c 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,b,,n,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: {{{ #!c #include 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)''. [[Image(cube-l.png)]] 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 2. GLUT_STROKE_MONO_ROMAN 3. GLUT_BITMAP_9_BY_15 4. GLUT_BITMAP_8_BY_13 5. GLUT_BITMAP_TIMES_ROMAN_10 6. GLUT_BITMAP_TIMES_ROMAN_24 7. GLUT_BITMAP_HELVETICA_10 8. GLUT_BITMAP_HELVETICA_12 9. 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. {{{ #!c #include 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 6. Če bi napisali komentar pred izrisom štirikotnika, potem bi bilo vidno besedilo tudi za ostale (skrite) strani. [[Image(cube-f.png)]] 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: {{{ #!c 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. [[Image(cube-b.png)]] Slika 7: Osenčen model kocke z ''bitmap'' napisi [[BR]][[BR]] = 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. {{{ #!c #include #include 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; } }}} [[BR]] == 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. {{{ #!c #include #include #include #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''). {{{ #!c #include #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. [[Image(teapot.png)]] Slika 8: Osenčen model čajnika = Seznam primerov = '''1.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/minimalni_program.c/ minimalni_program.c] - ''Minimalni program, ki nam izriše črto'' '''2.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/trikotnik.c/ trikotnik.c] - ''Trikotnik s prelivajočimi se barvami'' '''3.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/sinus_1.c/ sinus_1.c] - ''Izris funkcije sinus s pomočjo premikanja in skaliranja koordinatnega sistema'' '''4.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/sinus_2.c/ sinus_2.c] - ''Izris funkcije sinus s pomočjo skaliranja vseh koordinat za 3.14'' '''5.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/sinus_3.c/ sinus_3.c] - ''Izris funkcije sinus z uporabo ukaza za skaliranje'' glScalef '''6.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/sinus_cosinus.c/ sinus_cosinus.c] - ''Izris funkcije sinus in cosinus'' '''7.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/kocka.c/ kocka.c] - ''Primer kocke sestavljene iz šestih ploskev'' '''8.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/animacija.c/ animacija.c] - ''Animacija vozil na avtocesti'' '''9.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/osvetlitev.c/ osvetlitev.c] - ''Izris osenčenega modela kocke'' '''10.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/text.c/ text.c] - ''Izris osenčene kocke z besedilom na vsaki stranici'' '''11.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/keyboard.c/ keyboard.c] - ''Rotacija s tipkami'' '''12.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/mouse.c/ mouse.c] - ''Risanje črt z miško'' '''13.''' [http://www.lecad.uni-lj.si:8000/vaje/attachment/wiki/opengl-intro/rotacija.c/ rotacija.c] - ''Kvaternionska rotacija'' {{{ #!comment = Razvojno okolje = }}}