= Računalniška grafika = 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: \item[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 dogodek je obvezno saj mora prav vsak program poskrbeti, da se vsebina okna obnovi. \item[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. \item[keyboard] Pritisnjena je bila tipka na tipkovnici. \item[mouse] Stanje gumbov na miški se je spremenilo. Uporabnik je pritisnil ali sprostil enega od gumbov. \item[motion] Uporabnik premika miško ob pritisnjenem gumbu. \item[timer] Program zahteva sporočilo po preteku po določenega časa, da bo popravil vsebino okna. Primerno je za časovne simulacije. \end{description} 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 okenskem sistemu, za katere dogodke bo skrbel in za te dogodke mu bo sistem tudi pošiljal sporočila. \subsection{GLUT} Za abstrakcijo dogodkov okenskega sistema v našem primeru skrbi knjižnica GLUT. Primer minimalnega programa, ki nariše črto, je naslednji: {\scriptsize\begin{verbatim} subroutine display include 'GL/fgl.h' implicit none call fglClear(GL_COLOR_BUFFER_BIT) call fglColor3f(1.0, 0.4, 1.0) call fglBegin(GL_LINES) call fglVertex2f(0.1,0.1) call fglVertex3f(0.8,0.8,1.0) call fglEnd call fglFlush end program crta external display include 'GL/fglut.h' call fglutInit call fglutInitDisplayMode(GLUT_SINGLE + GLUT_RGB) call fglutCreateWindow('Fortran GLUT program') call fglutDisplayFunc(display) call fglutMainLoop end \end{verbatim} } Fortranski program je sestavljen iz dveh delov: glavnega programa \texttt{crta} in podprograma display. Z ukazom \texttt{fglutInit} 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 \texttt{window} hrani številko okna, ki jo naredi \texttt{fglutCreateWindow} 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 \texttt{fglutDisplayFunc} 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 \texttt{display}. Zadnji klic v glavnem programu je vedno \texttt{fglutMainLoop}. Ta podprogram se konča le takrat 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 - \texttt{display}. Ostale tipe dogodkov pa \texttt{fglutMainLoop} 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: \begin{enumerate} \item Vključi definicije konstant \texttt{GLUT} z ukazom \texttt{include 'GL/glut.h'} \item Dopovej Fortranu, da so imena, kot je npr. \texttt{display}, podprogrami in ne spremenljivke. To se naredi z ukazom \texttt{external} \item Inicializiraj GLUT \item Nastavi parametre okna (položaj, velikost, tip, bitne ravnine, pomnilnik) \item Naredi okno in ga poimenuj \item Prijavi podprograme, ki jih bo program izvajal ob dogodkih. Obvezno prestrezanje je le za \emph{display}. Ostali so poljubni. \item Nastavi lastnosti OpenGL stroja. To so običajno ukazi \texttt{fglEnable} 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. \item Zadnji je klic \texttt{fglutMainLoop}, iz katerega se program vrne, ko zapremo okno. Ob tem glavni program konča. \end{enumerate} \subsection{Izris v jeziku OpenGL} V podprogramu \texttt{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 \emph{gl} oziroma za Fortran \emph{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 \texttt{fgl}, za C pa \texttt{gl}) Podprogram \texttt{display} torej skrbi za izris vsebine okna. Z ukazom \emph{Clear} se briše celotno področje okna. Kaj konkretno se briše povemo z argumentom. V našem primeru je to \texttt{GL\_COLOR\_BUFFER\_BIT}, kar pomeni brisanje vseh točk v barvnem pomnilniku. Z ukazom \emph{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 \emph{fglColor3f} torej pomeni, da zahteva podprogram tri argumente tipa \texttt{float}, kar je ekvivalentno v fortranu tipu \emph{real} ali \texttt{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 \emph{Vertex} v dveh oblikah in isti tip argumentov. \texttt{Vertex2f} pomeni, da podajamo kordinate vozlišča z dvema argumentoma. Tipi argumentov (končnic) so naslednji: \begin{description} \item[f] V jeziku C float in real ali real*4 za Fortran \item[d] double za C in real*8 za Fortran \item[i] integer \item[s] short integer v C-ju ali integer*2 za F \end{description} Namesto fiksiranega števila argumentov obstajajo tudi funkcije, ki imajo podan argument v obliki vektorja. Za to se uporabi končnica \texttt{v}. Nekaj primerov končnic: \begin{description} \item[3f] Sledijo trije argumenti realnih števil \item[3i] Sledijo trije argumenti celih števil \item[3fv] Sledi vektor treh realnih števil \end{description} 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 \emph{Vertex(x, y, 0)}. V našem primeru imamo tudi nastavitev vozlišča z ukazom \texttt{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: \emph{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. \emph{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 \emph{glBegin} in \emph{glEnd}. Predmet risanja podamo kot argument v ukazu \texttt{Begin}. Na splošno velja, da funkcije brez končnic zahtevajo en sam argument s konstanto, ki je podana v \emph{header} datoteki s končnico \texttt{.h} in se vključuje za začetek programske enote s stavkom \texttt{include 'GL/fgl.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 \emph{.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 \emph{fgl.h} in \emph{fglu.h} imajo standardno predpono \texttt{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 \emph{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 \emph{GLU}, ki običajno nimajo tako razvejanih možnosti argumentov, kot knjižnica \emph{GL}. Zadnji ukaz \texttt{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 (\texttt{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 \texttt{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 \texttt{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: \begin{itemize} \item točke ali pike \item črte \item ploskvice konveksnega tipa \end{itemize} 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 \emph{GLU}. Obstajajo tudi možnosti sestavljenih enostavnih gradnikov za črte in ploskvice. Za črte poznamo tako naslednje možnosti: \begin{description} \item[GL\_LINES] Pari vozlišč podajajo posamezne segmente \item[GL\_LINE\_STRIP] Zaporedje povezanih vozlišč podaja lomljenko \item[GL\_LINE\_LOOP] Lomljenka se zaključi tako, da poveže prvo in zadnje vozlišče. \end{description} Konstante so podane kot argument za podprogram \emph{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: \begin{description} \item[GL\_TRIANGLES] Tri vozlišča za en trikotnik \item[GL\_TRIANGLE\_STRIP] Pas trikotnikov. Tri vozlišča za prvi in nato vsako nadaljnje vozlišče k prejšnjemo trikotniku doda nov trikotnik. \item[GL\_TRIANGLE\_FAN] Pahljača: vsako dodatno vozlišče naredi dodaten trikotnik v smislu dežnika. \item[GL\_QUADS] Ravninski štirikotnik se podaja s štirimi vozlišči. \item[GL\_QUAD\_STRIP] Dodatni štirikotniki gradijo pas z dodajanjem parov vozlišč. \item[GL\_POLYGON] En sam konveksni mogokotnik poljubnega števila vozlišč. \end{description} Za nesestavljeni tipe gradnikov lahko med \emph{Begin} in \emph{End} podamo tudi več vozlišč. Tip gradnika se pri pri tem avtomatsko ponovi. Med \emph{Begin} in \emph{End} se lahko uporabljajo še ukazi za barvo \emph{glColor} in normale \emph{glNormal}. Ti ukazi nastavljajo trenutno stanje, ki velja za vsa naslednja vozlišča. Primer podprograma za prikaz enega trikotnika v ravnini je naslednji: {\scriptsize \begin{verbatim} subroutine display implicit none include 'GL/fgl.h' call fglClear(GL_COLOR_BUFFER_BIT) call fglBegin(GL_TRIANGLES) call fglColor3f(1.0, 0.0, 0.0) call fglVertex2f(-1.0, -1.0) call fglColor3f(0.0, 1.0, 0.0) call fglVertex2f(0.0, 1.0) call fglColor3f(0.0, 0.0, 1.0) call fglVertex2f(1.0, 0.0) call fglEnd call fglFlush end \end{verbatim} } 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 \ref{fig:color-triangle}. \begin{figure}[htbp] \centering \includegraphics[height=1.5in]{color-triangle} \caption{Trikotnik s podanimi barvami v vozliščih} \label{fig:color-triangle} \end{figure} 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 \ref{fig:normala} kaže splošno postavitev treh točk v prostoru. \begin{figure}[htbp] \centering \includegraphics{normal0} \caption{Normala} \label{fig:normala} \end{figure} Normalo za trikotnik z vozlišči $\vec{r}_0, \vec{r}_1, \vec{r}_2$ izračunamo z vektorskim produktom $$ \vec{n} = \frac{ (\vec{r}_1-\vec{r}_0)\times (\vec{r}_2-\vec{r}_0) } {|(\vec{r}_1-\vec{r}_0)\times (\vec{r}_2-\vec{r}_0)|} $$ Imenovalec zgornje enačbe je dolžina vektorja $\vec{n}$. Normala je pravokotna na razliko vektorjev, ki podajajo vozlišče gradnika. = Geometrijske transformacije = Osnova vseh grafičnih knjižnic so tudi osnovne geometrijske transformacije, kot so: \begin{description} \item[Translate(x, y, z)] Premik v smeri vektorja \item[Rotate(fi, x, y, z)] Rotacija za \emph{fi} stopinj okoli osi podane z (x, y, z) \item[Scale(x, y, z)] Skaliranje po posameznih oseh \end{description} Ukazi za transformacije se ne smejo pojavljati med \emph{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\times 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: {\scriptsize\begin{verbatim} subroutine display include 'GL/fgl.h' call fglClear(GL_COLOR_BUFFER_BIT) call fglBegin(GL_LINE_STRIP) do i=0,10 y = sin((i-5)/5.0*3.14) call fglVertex2f((i-5)/5.0, y/3.14) end do call fglEnd call fglFlush end \end{verbatim}} 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 \texttt{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. {\scriptsize\begin{verbatim} do x=-3.14, 3.14, 0.6 y = sin(x) call fglVertex2f(x/3.14, y/3.14) end do \end{verbatim}} 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: {\scriptsize\begin{verbatim} subroutine display include 'GL/fgl.h' call fglClear(GL_COLOR_BUFFER_BIT) call fglScalef(1/3.14, 1/3.14, 1.0) call fglBegin(GL_LINE_STRIP) do x=-3.14, 3.14, 0.6 y = sin(x) call fglVertex2f(x, y) end do call fglEnd call fglFlush end \end{verbatim}} 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 \emph{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. {\scriptsize\begin{verbatim} subroutine display include 'GL/fgl.h' call fglClear(GL_COLOR_BUFFER_BIT) call fglScalef(1/3.14, 1/3.14, 1.0) call fglBegin(GL_LINE_STRIP) do x=-3.14, 3.14, 0.6 y = sin(x) call fglVertex2f(x, y) end do call fglEnd call fglTranslatef(0.0, -1.5, 0.0) call fglBegin(GL_LINE_STRIP) do x=-3.14, 3.14, 0.6 y = cos(x) call fglVertex2f(x, y) end do call fglEnd call fglFlush end \end{verbatim}} 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. \subsection{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 \emph{Translate}, \emph{Scale} in \emph{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 \emph{fglLoadIdentity} 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 (\emph{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 \textbf{glPushMatrix}, za ponastavitev iz sklada pa uporabimo \textbf{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 \texttt{glRectf(x1, y1, x2, y2)} za ravnino $z=0$. če želimo imeti kvadrat v poljubni ravnini, pa uporabimo transformacije. {\scriptsize \begin{verbatim} subroutine kvadrat(i) real r(6), g(6), b(6) data r /1,0,0,1,1,1/, g /0,1,0,1,0,0/ data b /0,0,1,0,1,1/ call fglPushMatrix call fglColor3f(r(i), g(i), b(i)) call fglTranslatef(0.0, 0.0, 1.0) call fglRectf(-1.0, -1.0, 1.0, 1.0) call fglPopMatrix end subroutine display implicit none include 'GL/fgl.h' call fglClear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT) call fglPushMatrix call fglRotatef(30.0, 1.0, 0.0, 0.0) call fglRotatef(30.0, 0.0, 1.0, 0.0) call fglScalef(0.5, 0.5, 0.5) call kvadrat(1) call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat(2) call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat(3) call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat(4) call fglRotatef(90.0, 1.0, 0.0, 0.0) call kvadrat(5) call fglRotatef(180.0, 1.0, 0.0, 0.0) call kvadrat(6) call fglPopMatrix call fglFlush end program kocka external display include 'GL/fglut.h' include 'GL/fgl.h' call fglutinit call fglutInitDisplayMode(GLUT_SINGLE+GLUT_DEPTH) call fglutCreateWindow('Fortran GLUT program') call fglutDisplayFunc(display) call fglEnable(GL_DEPTH_TEST) call fglutmainloop end \end{verbatim} } Podprogram \emph{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 \emph{Push} in potem ponovno obnovi z ukazom \emph{Pop}. \subsection{Globinski pomilnik} Da se ploskve v prostoru pravilno izrisujejo tudi takrat, ko rišemo ploskvice za drugimi, je potrebno uporabiti globinski pomnilnik ali \emph{z-buffer}. To pa mora omogočati že sam okenski sistem, zato je potrebno tak način prikaza zahtevati že pri \texttt{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 \texttt{fglEnable(GL\_DEPTH\_TEST)} se zahteva izračunavanje globine, ki jo je potrebno tako kot barvo pred vsakim začetkom risanja pobrisati z ukazom \texttt{fglClear}. \begin{figure}[htbp] \centering \includegraphics{half-cube} \caption{Kocka brez spodnjega in zgornjega pokrova (kvadrat(5) in kvadrat(6)} \label{fig:half-cube} \end{figure} če rišemo zaprte modele, potem notranjosti ni možno videti. Primer odprtega modela kaže slika \ref{fig:half-cube}. 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 izogemo nepotrebni rasterizaciji teh ploskvic, vključimo \texttt{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 \S\ref{sec:viewing}. \subsection{Animacija} \label{sec:animate} 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 \texttt{vozilo}. {\scriptsize\begin{verbatim} subroutine display implicit none include 'GL/fgl.h' common /vozila/ y(5), v(5) real y, v, pas integer i data y /0,50,120,170,200/ data v /50,30,45,31,33/ call fglClear(GL_COLOR_BUFFER_BIT) call fglPushMatrix call fglRotatef(-45.0, 0.0, 0.0, 1.0) call fglTranslatef(0.0, -1.0, 0.0) call fglScalef(0.004, 0.004, 0.004) call fglColor3f(0.0, 0.0, 0.0) call fglRectf(-4.0, 0.0, 4.0, 500.0) call fglTranslatef(0.0, -50.0, 0.0) do i=1,5 if (i.ne.5 .and. y(i+1)-y(i).lt.10.0) then pas=-2.0 else pas = 2.0 end if call vozilo(y(i), pas) end do call fglPopMatrix call fglutSwapBuffers end subroutine vozilo(y, pas) call fglPushMatrix call fglColor3f(1.0, 1.0, 1.0) call fglTranslatef(pas, y, 0.5) call fglRectf(-2.0, 0.0, 2.0, 6.0) call fglPopMatrix end subroutine ura(n) common /vozila/ y(5), v(5) real y, v, dt dt = 0.1 do i=1,5 y(i)=y(i)+v(i)*dt end do call fglutPostRedisplay call fglutTimerfunc(100, ura, 0) end program Mad Max external display external ura include 'GL/fglut.h' integer window call fglutInit call fglutInitDisplayMode(GLUT_RGB+GLUT_DOUBLE) call fglutCreateWindow('Avtocesta') call fglClearColor(0.0, 0.5, 0.0, 0.0) call fglutDisplayFunc(display) call fglutTimerFunc(100, ura, 0) call fglutMainLoop end \end{verbatim} } 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 \texttt{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 \emph{fglutSwapBuffers}, zamenjamo trenutni prikaz. Za animacijo, pri kateri je zahtevano točno časovno zaporedje je primerno uporabiti uro (\emph{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 \emph{fglutPostRedisplay}, da se na novo izriše scena. Lahko bi tudi neposredno klicali \texttt{display}, vendar bi bilo potem potrebno zagotoviti še kompenzacijo hitrosti, saj že v podprogramu ura izgubimo nekaj časa pri izrečunu novih položajev. Prostorski pomnilnik v tem primeru ni potreben, saj je zagotovljeno, da se izrisi prekrijejo v pravilnem vrstnem redu. \subsection{Transformacije pogleda} \label{sec:viewing} 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. \begin{figure}[htbp] \centering \includegraphics[width=3in]{viewing} \caption{Zaporedje pretvorbe koordinat vozlišč} \label{fig:viewing} \end{figure} OpenGL ločuje projekcijsko matriko in modelno matriko zato, da ni potrebno nastavljati projekcije pri vsakem izrisu. Slika \ref{fig:viewing} kaže zaporedje transformacij iz svetovnih koordinat v zaslonske. Pri risanju modela običajno začnemo z enotsko \emph{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: {\scriptsize\texttt{call fglViewport (0, 0, width, height)}} Nekoliko zahtevnejša je sorazmerna sprememba, ki ne bo anamorfično popravljala velikosti okna: {\scriptsize\begin{verbatim} subroutine reshape (w, h) integer w, h implicit none include 'GL/fgl.h' common /viewport/ width, height integer width, height real*8 left, right, bottom, top, znear, zfar width = w height = h if (w .ge. h) then 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) end if znear = -1.0 zfar = 1.0 call fglViewport (0, 0, width, height) call fglMatrixMode (GL_PROJECTION) call fglLoadIdentity call fglOrtho(left, right, bottom, top, znear, zfar) call fglMatrixMode(GL_MODELVIEW) end \end{verbatim} } Predstavljeni podprogram se priporoča v uporabo za vse programe, ki pripravljajo model v velikosti [-1,~1] za \emph{ModelView}. če bi želeli dodati modelno transformacijo v \emph{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 \ref{sec:animate} bi tako imel namesto nastavitve modelne transformacije slednje v podprogramu \emph{reshape}. Začetna nastavitev modelne matrike pred začetkom izrisa v podprogramu \emph{display} pa bi bila: {\scriptsize\begin{verbatim} call fglClear(GL_COLOR_BUFFER_BIT) call fglPushMatrix ... izris call fglPopMatrix \end{verbatim} } Takoj za brisanjem zaslona z ukazom \emph{Push} shranimo modelno matriko in jo ob koncu ponovno nastavimo na začetno vrednost. V podprogramu \emph{reshape}, pa modelno matriko popravljamo: {\scriptsize\begin{verbatim} call fglMatrixMode(GL_MODELVIEW) call fglLoadIdentity call fglRotatef(-45.0, 0.0, 0.0, 1.0) call fglTranslatef(0.0, -1.0, 0.0) call fglScalef(0.004, 0.004, 0.004) \end{verbatim} } 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 \emph{glOrtho(l,r,b,,n,f)}: $$ PM = \left[ \begin{array}{cccc} \frac{2}{r-l} & 0 & 0 & \frac{r+l}{l-r} \cr 0 & \frac{2}{t-b} & 0 & \frac{t+b}{t-b} \cr 0 & 0 & \frac{2}{f-n} & \frac{f+n}{f-n} \cr 0 & 0 & 0 & 1 \cr \end{array} \right]\quad . $$ Za primer normalizacijskega prostora v obsegu [-1,1] je tako matrika paralelne projekcije $$ PM(-1, 1, -1, 1, -1, 1) = \left[ \begin{array}{cccc} 1 & 0 & 0 & 0 \cr 0 & 1 & 0 & 0 \cr 0 & 0 & -1 & 0 \cr 0 & 0 & 0 & 1 \cr \end{array} \right]\quad , $$ 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 \ref{fig:half-cube}) 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: {\scriptsize\begin{verbatim} subroutine kvadrat() call fglPushMatrix call fglTranslatef(0.0, 0.0, 1.0) call fglNormal3f(0.0, 0.0, 1.0) call fglRectf(-1.0, -1.0, 1.0, 1.0) call fglPopMatrix end subroutine display implicit none include 'GL/fgl.h' call fglClear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT) call fglColor3f(0.7, 0.6, 0.2) call fglPushMatrix call fglScalef(0.5, 0.5, 0.5) call kvadrat call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat call fglRotatef(90.0, 1.0, 0.0, 0.0) call kvadrat call fglRotatef(180.0, 1.0, 0.0, 0.0) call kvadrat call fglPopMatrix call fglFlush end program kocka external display external reshape include 'GL/fglut.h' include 'GL/fgl.h' call fglutinit call fglutinitdisplaymode(GLUT_SINGLE+GLUT_DEPTH) call fglutcreatewindow('Osencena kocka') call fglutDisplayFunc(display) call fglutReshapeFunc(reshape) call fglClearColor(1.0, 1.0, 1.0, 1.0) call fglEnable(GL_LIGHTING) call fglEnable(GL_LIGHT0) call fglEnable(GL_DEPTH_TEST) call fglEnable(GL_COLOR_MATERIAL) call fglutMainLoop end subroutine reshape (w, h) integer w, h implicit none include 'GL/fgl.h' real*8 l l = 1.0 call fglViewport (0, 0, w, h) call fglMatrixMode (GL_PROJECTION) call fglLoadIdentity call fglOrtho(-l, l, -l, l, -l, l) call fglMatrixMode(GL_MODELVIEW) call fglLoadIdentity call fglRotatef(30.0, 1.0, 0.0, 0.0) call fglRotatef(30.0, 0.0, 1.0, 0.0) end \end{verbatim} } 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 \texttt{reshape}. V glavnem programu pa je potrebno najprej vključiti računanje osvetlitve \emph{GL\_LIGHTING}, prižgati je potrebno luč št 0, ki ima začetni položaj $(0,0,1)$. \begin{figure}[htbp] \centering \includegraphics[width=2.5in]{cube-l} \caption{Osenčen model kocke} \label{fig:cube-l} \end{figure} Z vključitvijo \emph{GL\_COLOR\_MATERIAL} pa poenostavimo podajanje barve za material površine tako, da vsi klici podprogramov \emph{Color} nastavljajo privzeto difuzno in ambientno barvo površine. Slika \ref{fig:cube-l} 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: \begin{description} \item[stroke] črke so izrisane s črtami v prostoru modela \item[bitmap] črke so izrisane na zaslon \item[teksture] črke so izrisane rastrsko v prostoru modela \end{description} V šolskih primerih so najbolj uporabni že izdelani fonti v knjižnici GLUT. Možne so naslednje številke fontov: {\small \begin{enumerate} \item GLUT\_STROKE\_ROMAN \item GLUT\_STROKE\_MONO\_ROMAN \item GLUT\_BITMAP\_9\_BY\_15 \item GLUT\_BITMAP\_8\_BY\_13 \item GLUT\_BITMAP\_TIMES\_ROMAN\_10 \item GLUT\_BITMAP\_TIMES\_ROMAN\_24 \item GLUT\_BITMAP\_HELVETICA\_10 \item GLUT\_BITMAP\_HELVETICA\_12 \item GLUT\_BITMAP\_HELVETICA\_18 \end{enumerate} } Za primer razširimo program za izris osenčene kocke z besedilom na vsaki stranici. Podprogram \emph{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 \texttt{fglutStrokeCharacter} po vsaki izrisani črti sam nastavi pomik v smeri x za širino izrisane črke. {\scriptsize\begin{verbatim} subroutine kvadrat(s) include 'GL/fgl.h' character s*(*), c call fglPushMatrix call fglTranslatef(0.0, 0.0, 1.0) call fglNormal3f(0.0, 0.0, 1.0) call fglRectf(-1.0, -1.0, 1.0, 1.0) call fglTranslatef(-0.8, 0.0, 0.01) call fglDisable(GL_LIGHTING) call fglScalef(0.003, 0.003, 0.003) call fglColor3f(1.0, 0.0, 0.0) lenc = len(s) do i=1,lenc c = s(i:i) call fglutStrokeCharacter(1, ichar(c)) end do call fglEnable(GL_LIGHTING) call fglPopMatrix end subroutine display implicit none include 'GL/fgl.h' real mat(4) data mat /0.9, 0.6, 0.3, 1.0/ call fglClear(GL_COLOR_BUFFER_BIT) call fglClear(GL_DEPTH_BUFFER_BIT) call fglPushMatrix call fglRotatef(30.0, 1.0, 0.0, 0.0) call fglRotatef(30.0, 0.0, 1.0, 0.0) call fglScalef(0.5, 0.5, 0.5) call fglMaterialfv(GL_FRONT, GL_DIFFUSE, mat) call kvadrat('Spredaj') call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat('Desno') call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat('Zadaj') call fglRotatef(90.0, 0.0, 1.0, 0.0) call kvadrat('Levo') call fglRotatef(90.0, 1.0, 0.0, 0.0) call kvadrat('Spodaj') call fglRotatef(180.0, 1.0, 0.0, 0.0) call kvadrat('Zgoraj') call fglPopMatrix call fglFlush end subroutine reshape(w, h) include 'GL/fgl.h' integer w, h real*8 l l = 1 call fglViewPort(0, 0, w, h) call fglMatrixMode(GL_PROJECTION) call fglLoadIdentity call fglOrtho(-l,l,-l,l,-l,l) call fglMatrixMode(GL_MODELVIEW) call fglLoadIdentity end program crta external display external reshape include 'GL/fglut.h' include 'GL/fgl.h' call fglutinit call fglutinitdisplaymode(GLUT_SINGLE+GLUT_DEPTH) call fglutcreatewindow('Fortran GLUT program') call fglutDisplayFunc(display) call fglutReshapeFunc(reshape) call fglEnable(GL_DEPTH_TEST) call fglEnable(GL_LIGHTING) call fglEnable(GL_LIGHT0) call fglClearColor(1.0, 1.0, 1.0, 1.0) call fglutmainloop end \end{verbatim} } \begin{figure}[htbp] \centering \includegraphics[width=2.5in]{cube-f} \caption{Osenčen model kocke z napisi} \label{fig:cube-f} \end{figure} Podajanje barve za površino je spremenjeno tako, da se ne uporabi funkcije \emph{Color} ampak normalno funkcijo za podajanje lastnosti materialaf \texttt{glMaterialfv}. Rezultat kaže slika \ref{fig:cube-f}. če bi napisali komentar pred izrisom štirikotnika, potem bi bilo vidno besedilo tudi za ostale (skrite) strani. 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 \emph{bitmap} fonte in naslednji podprogram za izpis besedila: {\scriptsize\begin{verbatim} subroutine output(x,y,z,s) character s*(*) call fglRasterPos3f(x,y,z) lenc = len(s) do i=1,lenc call fglutBitmapCharacter(6, ichar(s(i:i))) end do end \end{verbatim} } Primer izrisa z bitmap fonti kaže slika \ref{fig:cube-b} \begin{figure}[htbp] \centering \includegraphics[width=2.5in]{cube-b} \caption{Osenčen model kocke z \emph{bitmap} napisi} \label{fig:cube-b} \end{figure} = 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. \subsection{Rotacija s tipkami} Rotiramo že vgrajeni geometrijski model čajnika s tipkami \textbf{x, y, z}. Vsak pritisk na tipko poveča kot rotacije za pet stopinj. Podatke o trenutni rotaciji prenašamo s poljem \texttt{common}. Ker izrisujemo žični model, podprogram za \emph{reshape} ni potreben. Podprogram \emph{keyboard} ob pritisku na tipko dobi tudi informacijo o zaslonskem položaju miške. {\scriptsize\begin{verbatim} subroutine display implicit none include 'GL/fgl.h' common /rotation/ rx, ry, rz real rx, ry, rz call fglClear(GL_COLOR_BUFFER_BIT) call fglColor3f(0.5, 0.4, 1.0) call fglPushMatrix call fglRotatef(rx, 1.0, 0.0, 0.0) call fglRotatef(ry, 0.0, 1.0, 0.0) call fglRotatef(rz, 0.0, 0.0, 1.0) call fglutWireTeapot(dble(r)) call fglPopMatrix call fglutSwapBuffers end subroutine keyboard(key,x,y) common /rotation/ rx, ry, rz integer key,x,y print *, 'Key ', char(key), key, ' at', x, y if (key .eq. ichar('x')) rx = rx + 5.0 if (key .eq. ichar('y')) ry = ry + 5.0 if (key .eq. ichar('z')) rz = rz + 5.0 call fglutpostredisplay end program teapot external display external keyboard include 'GL/fglut.h' integer window call fglutInit call fglutInitDisplayMode(ior(GLUT_DOUBLE,GLUT_RGB)) window = fglutCreateWindow('Use keys x, y, and z') call fglutDisplayFunc(display) call fglutKeyboardFunc(keyboard) call fglutMainLoop end \end{verbatim} } \subsection{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. {\scriptsize\begin{verbatim} subroutine redraw implicit none include 'GL/fgl.h' common /vertices/ n, vertex(2, 100) integer n, i real vertex call fglClear(GL_COLOR_BUFFER_BIT) call fglbegin(GL_LINE_STRIP) do i = 1,n call fglVertex2f(vertex(1, i), vertex(2, i)) end do call fglend call fglFlush end subroutine mouse (button, state, x, y) implicit none include 'GL/fglut.h' include 'GL/fgl.h' include 'GL/fglu.h' common /vertices/ n, vertex(2, 100) integer n, i real vertex integer button, state, x, y integer viewport(4) real*8 mvmatrix(16), projmatrix(16) real*8 wx, wy, wz ! returned world x, y, z coords real*8 px, py, pz ! picked window coortinates integer status if (button .eq. GLUT_LEFT_BUTTON) then if (state .eq. GLUT_DOWN) then call fglGetIntegerv (GL_VIEWPORT, viewport) call fglGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix) call fglGetDoublev (GL_PROJECTION_MATRIX, projmatrix) note viewport(4) is height of window in pixels px = x py = viewport(4) - y - 1 pz = 0.0 print *, ' Coordinates at cursor are ', px, py status = fgluUnProject (px, py, pz, mvmatrix, projmatrix, viewport, wx, wy, wz) print *, 'World coords at z=0.0 are ', wx, wy, wz n = n + 1 vertex(1, n) = wx vertex(2, n) = wy call fglutPostRedisplay end if end if end program main external redraw external mouse include 'GL/fglut.h' call fglutinit call fglutinitdisplaymode(ior(GLUT_SINGLE,GLUT_RGB)) call fglutInitWindowSize (500, 500) call fglutInitWindowPosition (100, 100) window = fglutcreatewindow('Click in window') call fglutdisplayfunc(redraw) call fglutMouseFunc(mouse) call fglutmainloop end \end{verbatim} } \subsection{Kvaternionska rotacija} Naslednji program prikazuje vrtenje osenčenega čajnika z miško. V ta namem se uporabi že izdelam podprogram v jeziku C, ki ga kličemo iz fortrana in nam omogoča kvaternionsko rotacijo. Za vrtenje enotske krogle je potrebno zaznati tako začetni pritisk na gumb (podprogram \emph{mouse}) kot vse naslednje pomike miške (podprogram \emph{motion}). {\scriptsize\begin{verbatim} subroutine display implicit none include 'GL/fgl.h' common /quaternion/ last(4), cur(4) real last, cur, m(4,4) call build_rotmatrix(m, cur) call fglLoadIdentity call fglMultMatrixf(m) call fglclear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT) call fglutSolidTeapot(dble(0.5)) call fglutSwapBuffers end subroutine motion (x, y) include 'GL/fglut.h' include 'GL/fgl.h' implicit none integer x, y common /quaternion/ last(4), cur(4) common /mousestart/ beginx, beginy common /viewport/ width, height integer width, height integer beginx, beginy real last, cur real p1x, p1y, p2x, p2y p1x = (2.0*beginx - width)/width p1y = (height - 2.0*beginy)/height p2x = (2.0 * x - width) / width p2y = (height - 2.0 * y) / height call trackball(last,p1x, p1y, p2x, p2y) call add_quats(last, cur, cur) beginx = x beginy = y call fglutPostRedisplay end subroutine mouse (button, state, x, y) implicit none integer button, state, x, y include 'GL/fglut.h' include 'GL/fgl.h' include 'GL/fglu.h' common /mousestart/ beginx, beginy integer beginx, beginy beginx = x beginy = y end subroutine reshape(w, h) include 'GL/fgl.h' integer w, h real*8 l common /viewport/ width, height integer width, height width=w height=h l = 1 call fglViewPort(0, 0, w, h) call fglMatrixMode(GL_PROJECTION) call fglLoadIdentity call fglOrtho(-l,l,-l,l,-l,l) call fglMatrixMode(GL_MODELVIEW) call fglLoadIdentity end program trackballdemo implicit none include 'GL/fglut.h' include 'GL/fgl.h' include 'GL/fglu.h' external display external motion external mouse external reshape integer window common /quaternion/ last(4), cur(4) real last, cur call trackball(cur, 0.0, 0.0, 0.0, 0.0) call fglutinit call fglutinitdisplaymode(GLUT_DOUBLE+GLUT_RGB+GLUT_DEPTH) window = fglutcreatewindow('Use mouse to rotate') call fglutdisplayfunc(display) call fglutmousefunc(mouse) call fglutmotionfunc(motion) call fglutreshapefunc(reshape) call fglEnable(GL_LIGHTING) call fglEnable(GL_LIGHT0) call fglEnable(GL_DEPTH_TEST) call fglutmainloop end \end{verbatim} } Predstavljeni program je sestavljen iz dveh delov. Kodo za rotacijo v jeziku C \texttt{trackball.c} uporabimo kod zunanje podprograme. Rezultat osenčenega model, ki je bil obrnjen z miško, prikazuje slika \ref{fig:teapot}. \begin{figure}[htbp] \centering \includegraphics[width=2.1in]{teapot} \caption{Osenčen model čajnika} \label{fig:teapot} \end{figure} = Razvojno okolje = Za šolske probleme smo pripravili razvojno okolje, ki omogoča prevajanje kode v jezikih F77, C++ in C za okenski sistem Windows. Razvojno okolje deluje v načunu ukazne vrstice in nima priloženega integriranega vmesnika. Vsi ukazi za popravljanje programov in prevajanje se tako podajajo v ukazni vrstici DOS okna (\emph{Start-Programs-Command Prompt}). Osnova okolja je Borlandov C++ prevajalnik, ki ga lahko dobimo zastonj. Besedilo dogovora uporabe se nahaja v datoteki \emph{linence.txt}. Prevajanje v jeziku Fortran pa dosežemo z pretvorbo fortranske kode v C, nato sledi prevajanje v C-ju in povezovanje v končni program (\emph{.exe}). Končni program lahko zaženemo z DOS okna ali z dvoklikom na izvršni program. Poleg Novega grafičnega okna vsak GLUT program uporablja še konzolo za morebiten vnos ali izpis z ukazoma \texttt{print *,} ali \texttt{read *,} \subsection{Namestitev} Namestitev je možna z CD-ROMA ali datoteke \texttt{bcc-fgl-full.zip}. V slednjem primeru je potrebno paketno datoteko odpakirati v začasen imenik, nakar sledi namestitev tako kot iz CD-ROM-a. \begin{enumerate} \item Za namestitev je na disku C potrebnih 60 MB prostora! \item Dvolkikni na \texttt{install.bat} \end{enumerate} \subsection{Dokumentacija} Po namestitvi se navodila z nahajajo v imeniku \verb|c:\bcc55\doc|. Priližena so naslednja navodila v obliki PDF: \begin{description} \item[redbook-*.pdf] OpenGL Programming Guide \item[opengl-intro.pdf] Ta dokument \item[fgl.pdf] OpenGL reference \item[fglu.pdf] OpenGL Utility reference \item[fglut.pdf] GLUT \item[f2c.pdf] Prevajalnik za Fortran \end{description} V datoteki \texttt{} se nahaja ta dokument. Iskanje po dokumentaciji za OpenGL ( \texttt{fgl.pdf, fglu.pdf}), izvedemo tako, da natipkamo npr. \texttt{fglVertex3f(} \subsection{Prevajanje} Primeri so v imeniku \verb|c:\bcc55\examples|. Pred prevajanjem je potrebno odpreti okno DOS \emph{Start-Run-command-OK} in nastaviti pot do prevajalnikov z ukazom \begin{verbatim} PATH=\BCC55\bin;%PATH% \end{verbatim} Pomik v imenik naredimo z ukazom \begin{verbatim} c: cd \bcc55\examples \end{verbatim} Pot lahko nastavimo tudi za celoten sistem: \emph{Start-Settings-Control Panel-System-evironment->PATH} in dopišemo \verb|c:\bcc55\bin;| na začetku ali koncu obstoječe poti. Za prevajanje fortranskih datotek ne smemo uporabiti končnice \texttt{.f} Primer prevajanja začetnega primera \texttt{line.f} za izris črte: \begin{verbatim} f77 line \end{verbatim} Za najzahtevnejši primer čajnika uporabimo hkratno prevajanje fortranskega in C programa, ki oba med seboj tudi poveže v program \texttt{tblight.exe} \begin{verbatim} f77 tblight trackball.c \end{verbatim} če imamo več modulov, potem lahko že prevedene podprograme \texttt{.obj} le povežemo v izvršno kodo. Primer: \begin{verbatim} f77 tblight trackball.obj \end{verbatim} Prevajanje in povezovanje v jeziku C se izvede z klicem prevajalnika \emph{bcc32}. Primer: \begin{verbatim} bcc32 teapot.c \end{verbatim} \subsection{Urejanje fortranskih in C programov:} Na urejanje kode lahko uporabimo DOS-ov urejevalnik EDIT ali Windows notepad. Oba imata svoje slabosti; EDIT ima težave z daljšimi imeni datotek, NOTEPAD pa nima prikaza trenutne vrstice in ob prvem shranjevanju datoteke lepi končnico \texttt{.txt}, tako da moramo datoteko kasneje preimenovati v \texttt{.f}. Kljub slabostim sta oba urejevalnika primerna za šolske probleme. V trenutnem imeniku DOS okna odtipkamo izbrani ukaz: \begin{verbatim} notepad teapot.f edit teapot.f \end{verbatim}