| | 1 | = Računalniška grafika = |
| | 2 | |
| | 3 | Leon Kos |
| | 4 | |
| | 5 | |
| | 6 | Predstavljeni so bistveni prijemi pri programiranju |
| | 7 | računalniške grafike z grafično knjižnico OpenGL. Ta |
| | 8 | dokument naj bi bil osnova za vaje pri predmetih RPK, OPK, PK in |
| | 9 | izdelavo seminarjev s tega področja. Ker študenti FS |
| | 10 | pridobijo znanje programiranja v jeziku Fortran, so primeri podani za |
| | 11 | ta jezik. To pa ne pomeni, da je teorija omejena le na ta jezik, saj |
| | 12 | brez bistvenih popravkov kode lahko pišemo tudi v jeziku C in C++. |
| | 13 | |
| | 14 | |
| | 15 | = Uvod = |
| | 16 | |
| | 17 | Za modeliranje posebnih modelov običajno ni možno uporabiti |
| | 18 | splošno namenskih grafičnih orodij. To se pokaže predvsem |
| | 19 | pri vizualizaciji inženirskih preračunov. Rezultati modeliranja |
| | 20 | običajno niso le funkcije ampak kompleksni objekti kot so grafi, |
| | 21 | vodi, hierarhične strukture, animacije gibanja, mehanizmi, |
| | 22 | kontrola poti, volumski modeli posebnih oblik, ... |
| | 23 | |
| | 24 | Skozi razvoj računalnikov so se uvajali različni standardi za |
| | 25 | grafiko. Od začetnikov kot je GKS in njegovega naslednika PHIGS je |
| | 26 | ostal le še spomin. To pa predvsem zaradi zahtevnosti |
| | 27 | implementacije in zaprtosti kode. Kot edini odprti standard obstaja |
| | 28 | le OpenGL, ki ga je najprej uvedel SGI na svojih grafično podprtih |
| | 29 | delovnih postajah. Poleg OpenGL obstaja tudi Microsoftov Direct3D, ki |
| | 30 | pa je omejen na PC računalnike z Windows in ni tako enostaven za |
| | 31 | uporabo kot OpenGL, ki se je zaradi svoje odprtosti in zmogljivosti |
| | 32 | uveljavil na vseh operacijskih sistemih in strojnih platformah. |
| | 33 | |
| | 34 | Jezik OpenGL je konstruiran kot strojno-neodvisen vmesnik med |
| | 35 | programsko kodo in grafičnim pospeševalnikom. Strojna |
| | 36 | neodvisnost jezika OpenGL poneni tudi to, da v specifikaciji jezika ni |
| | 37 | podpore za nadzor okenskega sistema in dogodkov (\emph{events}) pri |
| | 38 | interaktivnem programiranju. Za tak nadzor so za vsak operacijski |
| | 39 | sistem izdelani vmesniki, ki povezujejo OpenGL stroj z okenskim |
| | 40 | sistemom. |
| | 41 | |
| | 42 | Zaradi specifičnosti različnih okenskih sistemov (Windows, |
| | 43 | Xwindow, MacOS, BeOS) je potrebno za vsak sistem uporabiti posebne |
| | 44 | prijeme pri klicanje OpenGL ukazov. Da bi vseeno lahko posali |
| | 45 | programe, s sicer omejeno funkcionalnostjo uporabniškega vmesnika, |
| | 46 | se je izdelala knjižnica GLUT (GL UTility), ki vse razlike |
| | 47 | med operacijskimi sistemi kompenzira in vpeljuje skupen način |
| | 48 | manipuliranja z dogodki (\emph{events}). S knjižnico GLUT je tako |
| | 49 | mogoče pisati prenosljive programe, ki so enostavni za |
| | 50 | programiranje in dovolj zmogljivi za nezahtevne uporabniške |
| | 51 | vmesnike. |
| | 52 | |
| | 53 | |
| | 54 | = Enostavni OpenGL program = |
| | 55 | |
| | 56 | Osnovni jezik OpenGL je podan v knjižnici GL. Bolj zahtevni |
| | 57 | primitivi se gradijo z knjižnico GLU (GL Utility) v kateri so |
| | 58 | podprogrami, ki uporabljajo rutine GL. Rutine GLU vsebujejo več |
| | 59 | GL ukazov, ki pa so splošno uporabni in so bili zato |
| | 60 | standardizirani. |
| | 61 | |
| | 62 | \subsection{Dogodki} |
| | 63 | Vsi okenski vmesniki delujejo na principu dogodkov (\emph{events}). To |
| | 64 | so signali okenskega sistema, ki se pošiljajo programu. Naš |
| | 65 | program je tako v celoti odgovoren za vsebino okna. Okenski sistem mu |
| | 66 | le dodeli področje (okno) katero vsebino mora popolnoma |
| | 67 | nadzorovati. Poleg dodeljenega področja pa okenski sistem |
| | 68 | pošilja še sporočila našemu programu. Najbolj pogosta |
| | 69 | sporočila so: |
| | 70 | \begin{description} |
| | 71 | \item[display] Prosim obnovi (nariši) vsebino okna. Več |
| | 72 | možnih primerov je, da se to zgodi. Lahko je drugo okno odkrilo |
| | 73 | del našega okna, okno se je premaknilo na omizju ali pa se je |
| | 74 | ponovno prikazalo po tem ko je bilo ikonizirano. Prestrezanje tega |
| | 75 | dogodek je obvezno saj mora prav vsak program poskrbeti, da se |
| | 76 | vsebina okna obnovi. |
| | 77 | \item[reshape] Velikost/oblika okna se je spremenila. Poračunaj |
| | 78 | vsebino okna za novo velikost. Ta dogodek se zgodi, kadar |
| | 79 | uporabnik z miško spremeni velikost okna. |
| | 80 | \item[keyboard] Pritisnjena je bila tipka na tipkovnici. |
| | 81 | \item[mouse] Stanje gumbov na miški se je spremenilo. Uporabnik je |
| | 82 | pritisnil ali sprostil enega od gumbov. |
| | 83 | \item[motion] Uporabnik premika miško ob pritisnjenem gumbu. |
| | 84 | \item[timer] Program zahteva sporočilo po preteku po določenega |
| | 85 | časa, da bo popravil vsebino okna. Primerno je za časovne |
| | 86 | simulacije. |
| | 87 | \end{description} |
| | 88 | Seveda poleg naštetih dogodkov obstajajo še drugi dogodki, za |
| | 89 | katere lahko skrbi naš program. Ni pa potrebno da naš program |
| | 90 | skrbi za vse naštete dogodke. Običajno mora program povedati |
| | 91 | okenskem sistemu, za katere dogodke bo skrbel in za te dogodke mu bo |
| | 92 | sistem tudi pošiljal sporočila. |
| | 93 | |
| | 94 | \subsection{GLUT} |
| | 95 | Za abstrakcijo dogodkov |
| | 96 | okenskega sistema v našem primeru skrbi knjižnica GLUT. Primer |
| | 97 | minimalnega programa, ki nariše črto, je naslednji: |
| | 98 | {\scriptsize\begin{verbatim} |
| | 99 | subroutine display |
| | 100 | include 'GL/fgl.h' |
| | 101 | implicit none |
| | 102 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 103 | call fglColor3f(1.0, 0.4, 1.0) |
| | 104 | call fglBegin(GL_LINES) |
| | 105 | call fglVertex2f(0.1,0.1) |
| | 106 | call fglVertex3f(0.8,0.8,1.0) |
| | 107 | call fglEnd |
| | 108 | call fglFlush |
| | 109 | end |
| | 110 | |
| | 111 | program crta |
| | 112 | external display |
| | 113 | include 'GL/fglut.h' |
| | 114 | call fglutInit |
| | 115 | call fglutInitDisplayMode(GLUT_SINGLE + GLUT_RGB) |
| | 116 | call fglutCreateWindow('Fortran GLUT program') |
| | 117 | call fglutDisplayFunc(display) |
| | 118 | call fglutMainLoop |
| | 119 | end |
| | 120 | \end{verbatim} |
| | 121 | } |
| | 122 | |
| | 123 | Fortranski program je sestavljen iz dveh delov: glavnega programa |
| | 124 | \texttt{crta} in podprograma display. Z ukazom \texttt{fglutInit} |
| | 125 | inicializiramo GLUT knjižnico podprogramov. Sledi zahteva po vrsti |
| | 126 | okna. S konstantama GLUT\_SINGLE in GLUT\_RGB povemo, da želimo |
| | 127 | okno z eno ravnino tribarvnega RGB prostora. spremenljivka |
| | 128 | \texttt{window} hrani številko okna, ki jo naredi |
| | 129 | \texttt{fglutCreateWindow} in hkrati pove OS, kakšen naj bo napis |
| | 130 | na oknu. Okenskemu sistemu moramo še dopovedati, katere dogodke bo |
| | 131 | program prestrezal. Za podani primer je to le prikaz vsebine okna. S |
| | 132 | klicem podprograma \texttt{fglutDisplayFunc} prijavimo OS, da naj |
| | 133 | pošilja sporočila za izris, knjižnici GLUT pa s tem |
| | 134 | dopovemo, da ob zahtevi za ponovni izris pokliče podprogram |
| | 135 | \texttt{display}. |
| | 136 | |
| | 137 | Zadnji klic v glavnem programu je vedno \texttt{fglutMainLoop}. Ta |
| | 138 | podprogram se konča le takrat se konča celoten program. Kot že |
| | 139 | ime podprograma govori, je to glavna zanka programa, v kateri GLUT |
| | 140 | sistematično sprejema sporočila in kliče naše |
| | 141 | podprograme za dogodke. V podanem primeru je to le en tip dogodkov, ki |
| | 142 | je tudi najbolj uporaben - \texttt{display}. Ostale tipe dogodkov pa |
| | 143 | \texttt{fglutMainLoop} obdeluje na privzeti način, ki je lahko |
| | 144 | preprosto ignoriranje sporočila ali pa klicanje vgrajenega |
| | 145 | podprograma, ki obdela sporočilo. Izgled glavnega programa je tako |
| | 146 | običajno zelo podoben za vse tipe GLUT programov v katerem si |
| | 147 | sledijo ukazi v naslednjem zaporedju: |
| | 148 | \begin{enumerate} |
| | 149 | \item Vključi definicije konstant \texttt{GLUT} z ukazom |
| | 150 | \texttt{include 'GL/glut.h'} |
| | 151 | \item Dopovej Fortranu, da so imena, kot je npr. \texttt{display}, |
| | 152 | podprogrami in ne spremenljivke. To se naredi z ukazom \texttt{external} |
| | 153 | \item Inicializiraj GLUT |
| | 154 | \item Nastavi parametre okna (položaj, velikost, tip, bitne |
| | 155 | ravnine, pomnilnik) |
| | 156 | \item Naredi okno in ga poimenuj |
| | 157 | \item Prijavi podprograme, ki jih bo program izvajal ob |
| | 158 | dogodkih. Obvezno prestrezanje je le za \emph{display}. Ostali so poljubni. |
| | 159 | \item Nastavi lastnosti OpenGL stroja. To so običajno ukazi |
| | 160 | \texttt{fglEnable} ali pa kakšna nastavitev luči, materialov |
| | 161 | in obnašanja GL stroja. V tem področju se običajno |
| | 162 | nastavi tudi ostale spremenljivke, ki niso neposredno vezane na |
| | 163 | OpenGL, ampak na samo delovanje programa, ki poleg prikaza dela |
| | 164 | še kaj drugega. |
| | 165 | \item Zadnji je klic \texttt{fglutMainLoop}, iz katerega se program |
| | 166 | vrne, ko zapremo okno. Ob tem glavni program konča. |
| | 167 | \end{enumerate} |
| | 168 | |
| | 169 | |
| | 170 | \subsection{Izris v jeziku OpenGL} |
| | 171 | V podprogramu \texttt{display} se morajo torej nahajati ukazi, ki |
| | 172 | nekaj narišejo v okno. To so ukazi v jeziku OpenGL ali krajše |
| | 173 | v jeziku GL. Vsi podprogrami ali funkcije v GL imajo predpono pri |
| | 174 | imenu \emph{gl} oziroma za Fortran \emph{fgl}. Te predpone so potrebne |
| | 175 | zaradi možnega prekrivanja z imeni v drugih knjižnicah in |
| | 176 | jezikih. Za razumevanje jezika se lahko funkcije razlaga brez teh |
| | 177 | predpon, saj je OpenGL koncipiran tako, da so tipi argumentov za vse |
| | 178 | jezike podobni. Za posamezen jezik se predpostavi prefiks (za Fortran |
| | 179 | je to \texttt{fgl}, za C pa \texttt{gl}) |
| | 180 | |
| | 181 | Podprogram \texttt{display} torej skrbi za izris vsebine okna. Z |
| | 182 | ukazom \emph{Clear} se briše celotno področje okna. Kaj |
| | 183 | konkretno se briše povemo z argumentom. V našem primeru je to |
| | 184 | \texttt{GL\_COLOR\_BUFFER\_BIT}, kar pomeni brisanje vseh točk v |
| | 185 | barvnem pomnilniku. |
| | 186 | |
| | 187 | Z ukazom \emph{Color} se nastavi trenutna barva grafičnih |
| | 188 | gradnikov, ki se bodo izrisovali v nadaljnih ukazih. Kot argument se |
| | 189 | podaja barva v RGB komponentah. Običajno imajo GL ukazi na koncu |
| | 190 | imena tudi oznako tipa argumentov, ki jih je potrebno podati pri klicu |
| | 191 | podprograma. To je običaj za skoraj vse jezike. Tako imamo za |
| | 192 | Fortran in za C pri podprogramih, kot končnico imena še oznako o |
| | 193 | številu argumentov in tip argumentov. Podprogram \emph{fglColor3f} |
| | 194 | torej pomeni, da zahteva podprogram tri argumente tipa \texttt{float}, |
| | 195 | kar je ekvivalentno v fortranu tipu \emph{real} ali \texttt{real*4}. |
| | 196 | Najbolj uporabljane GL ukaze lahko torej podamo z različnimi tipi |
| | 197 | argumentov za isti ukaz. Izbor tipa argumentov je odvisen od |
| | 198 | programerja in njegovih zahtev. Tako so argumenti za isto funkcijo |
| | 199 | izbrani glede na priročnost. Za podani primer imamo ukaz |
| | 200 | \emph{Vertex} v dveh oblikah in isti tip argumentov. \texttt{Vertex2f} |
| | 201 | pomeni, da podajamo kordinate vozlišča z dvema argumentoma. |
| | 202 | Tipi argumentov (končnic) so naslednji: |
| | 203 | \begin{description} |
| | 204 | \item[f] V jeziku C float in real ali real*4 za Fortran |
| | 205 | \item[d] double za C in real*8 za Fortran |
| | 206 | \item[i] integer |
| | 207 | \item[s] short integer v C-ju ali integer*2 za F |
| | 208 | \end{description} |
| | 209 | Namesto fiksiranega števila argumentov obstajajo tudi funkcije, ki |
| | 210 | imajo podan argument v obliki vektorja. Za to se uporabi končnica |
| | 211 | \texttt{v}. Nekaj primerov končnic: |
| | 212 | \begin{description} |
| | 213 | \item[3f] Sledijo trije argumenti realnih števil |
| | 214 | \item[3i] Sledijo trije argumenti celih števil |
| | 215 | \item[3fv] Sledi vektor treh realnih števil |
| | 216 | \end{description} |
| | 217 | |
| | 218 | Ker je GL v osnovi 3D pomeni, da so argumenti če sta podani le dve |
| | 219 | koordinati $x$ in $y$ v ravnini $z=0$. Torej je to ekvivalentno klicu |
| | 220 | podprograma \emph{Vertex(x, y, 0)}. V našem primeru imamo |
| | 221 | tudi nastavitev vozlišča z ukazom |
| | 222 | \texttt{Vertex3f(0.8,0.8,1.0)}, ki podaja vse tri koordinate v |
| | 223 | prostoru. Koordinata $z=1$ je torej podana, vendar je zaradi |
| | 224 | privzetega začetnega ortografskega pogleda v prostor ravnine (x,y) |
| | 225 | koordinata $z$ po globini neopazna. če pa bi bila projekcija |
| | 226 | perspekvivna in ne ortogonalna, bi opazili tudi vpliv |
| | 227 | koordinate $z$. Raznolikost tipov argumentov se pokaže ravno pri |
| | 228 | podajanju vozlišč, saj obstajajo naslednjji podprogrami: |
| | 229 | \emph{glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, |
| | 230 | glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, |
| | 231 | glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, |
| | 232 | glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv, glVertex3sv, |
| | 233 | glVertex4dv, glVertex4fv, glVertex4iv, glVertex4sv}. In vse to za |
| | 234 | en sam ukaz. |
| | 235 | |
| | 236 | Namen velikega števila istih podprogramov za isto funkcijo je |
| | 237 | opustitev pretvarjanja tipov in s tem pisanje bolj razumljive |
| | 238 | in hitrejše kode. V jeziku C++ ali Java, ki pa sam dodaja ustrezne |
| | 239 | argumente k imenom funkcij, pa bi lahko obstajal le en podprogram (npr. |
| | 240 | \emph{glVertex}), jezik pa bi sam dodal ustrezne končnice in s tem |
| | 241 | klical ustrezno funkcijo. |
| | 242 | |
| | 243 | Izris grafičnih elementov risbe se v GL podaja med ukazoma |
| | 244 | \emph{glBegin} in \emph{glEnd}. Predmet risanja podamo kot argument v |
| | 245 | ukazu \texttt{Begin}. Na splošno velja, da funkcije brez končnic |
| | 246 | zahtevajo en sam argument s konstanto, ki je podana v \emph{header} |
| | 247 | datoteki s končnico \texttt{.h} in se vključuje za začetek |
| | 248 | programske enote s stavkom \texttt{include 'GL/fgl.h'}. Za fortran je |
| | 249 | programska enota vsak podprogram, zato moramo pri vsakem |
| | 250 | podprogramu, ki uporablja te konstante, na začetku dopisati |
| | 251 | še vključevanje teh konstant. Za C je modul \emph{.c} |
| | 252 | datoteka, in ni potrebno vključevanje definicij konstant za vsak |
| | 253 | podprogram, kot je to nujno za Fortran. |
| | 254 | |
| | 255 | Vse te GL konstante, ki so napisane v \emph{fgl.h} in \emph{fglu.h} |
| | 256 | imajo standardno predpono \texttt{GL\_} in je za vse jezike |
| | 257 | enoznačen. Ker pa Fortran ne zahteva deklaracje spremenljivk in |
| | 258 | ima implicitno definirane tipe je prav možno, da se zatipkamo pri |
| | 259 | imenu konstante, kar za Fortran pomeni realno številko z |
| | 260 | vrednostjo 0.0. Da se izognemo takim težavam, se priporoča |
| | 261 | ukaz \emph{implicit none}, s katerim izključimo predpostavljene |
| | 262 | tipe in moramo za vsako spremenljivko povedati, kakšnega tipa je. |
| | 263 | žal pa F77 ne omogoča prototipov tako, da je še vedno |
| | 264 | potrebna pazljivost, kakšne tipe podajamo kot argumente |
| | 265 | podprogramom. Posebno to velja za podprograme \emph{GLU}, ki |
| | 266 | običajno nimajo tako razvejanih možnosti argumentov, kot |
| | 267 | knjižnica \emph{GL}. |
| | 268 | |
| | 269 | Zadnji ukaz \texttt{glFlush} dopove GL stroju naj vse te ukaze, ki jih |
| | 270 | je sprejel do sedaj, spravi iz svojih internih pomnilnikov v okno |
| | 271 | okenskega sistema. Ker imamo v našem primeru le enostaven izris, |
| | 272 | smo se odločili le za en slikovni pomnilnik |
| | 273 | (\texttt{GLUT\_SINGLE}), ki je primeren le za statične slike. Za |
| | 274 | aplikacije pri katerih se vsebina zaslona pogosto spreminja, je |
| | 275 | primerneje uporabiti okno z dvema grafičnima pomnilnikoma |
| | 276 | \texttt{GLUT\_DOUBLE}. Prednost slednjega je v tem, da v en pomnilnik |
| | 277 | rišemo, drugega pa prikazujemo. Rišemo v ravnino, ki je v ozadju. |
| | 278 | Ob koncu risanja pa le zamenjamo ravnini. Ker pa je to odvisno od |
| | 279 | sistema se ukaz za zamenjavo risalnih ravnin imenuje |
| | 280 | \texttt{glutSwapBuffers}. Prednost takega načina se pokaže pri |
| | 281 | animacijah. |
| | 282 | |
| | 283 | = Geometrijski primitivi = |
| | 284 | Uporabiti je mogoče le enostavne primitive. To pa predvsem zaradi |
| | 285 | zahtevane hitrosti in enostavosti izdelave strojnega pospeševanja. |
| | 286 | Ločimo tri vrste teh enostavnih primitivov: |
| | 287 | \begin{itemize} |
| | 288 | \item točke ali pike |
| | 289 | \item črte |
| | 290 | \item ploskvice konveksnega tipa |
| | 291 | \end{itemize} |
| | 292 | Bolj zahtevne predstavitve izvedemo z kombiniranjem teh primitivov. Tako |
| | 293 | krivulje različnih tipov aproksimiramo z lomljenkami, površine |
| | 294 | pa s ploskvicami. Za najbolj razširjene kompleksne tipe se že |
| | 295 | nahajajo podprogrami v knjižnici \emph{GLU}. Obstajajo tudi |
| | 296 | možnosti sestavljenih enostavnih gradnikov za črte in |
| | 297 | ploskvice. Za črte poznamo tako naslednje možnosti: |
| | 298 | \begin{description} |
| | 299 | \item[GL\_LINES] Pari vozlišč podajajo posamezne segmente |
| | 300 | \item[GL\_LINE\_STRIP] Zaporedje povezanih vozlišč podaja |
| | 301 | lomljenko |
| | 302 | \item[GL\_LINE\_LOOP] Lomljenka se zaključi tako, da poveže |
| | 303 | prvo in zadnje vozlišče. |
| | 304 | \end{description} |
| | 305 | Konstante so podane kot argument za podprogram \emph{Begin}. Za |
| | 306 | ploskve se podaja več točk. Najenostavnejše ploskve so |
| | 307 | trikotniki. Možni so še ravninski štirikotniki in konveksni |
| | 308 | ravninski mnogokotniki. Enostavne elemente lahko podajamo tudi v |
| | 309 | pasovih in trikotnike v pahljačah: |
| | 310 | \begin{description} |
| | 311 | \item[GL\_TRIANGLES] Tri vozlišča za en trikotnik |
| | 312 | \item[GL\_TRIANGLE\_STRIP] Pas trikotnikov. Tri vozlišča za |
| | 313 | prvi in nato vsako nadaljnje vozlišče k prejšnjemo |
| | 314 | trikotniku doda nov trikotnik. |
| | 315 | \item[GL\_TRIANGLE\_FAN] Pahljača: vsako dodatno vozlišče |
| | 316 | naredi dodaten trikotnik v smislu dežnika. |
| | 317 | \item[GL\_QUADS] Ravninski štirikotnik se podaja s štirimi |
| | 318 | vozlišči. |
| | 319 | \item[GL\_QUAD\_STRIP] Dodatni štirikotniki gradijo pas z |
| | 320 | dodajanjem parov vozlišč. |
| | 321 | \item[GL\_POLYGON] En sam konveksni mogokotnik poljubnega števila |
| | 322 | vozlišč. |
| | 323 | \end{description} |
| | 324 | Za nesestavljeni tipe gradnikov lahko med \emph{Begin} in \emph{End} |
| | 325 | podamo tudi več vozlišč. Tip gradnika se pri pri tem |
| | 326 | avtomatsko ponovi. Med \emph{Begin} in \emph{End} se lahko uporabljajo |
| | 327 | še ukazi za barvo \emph{glColor} in normale \emph{glNormal}. Ti |
| | 328 | ukazi nastavljajo trenutno stanje, ki velja za vsa naslednja |
| | 329 | vozlišča. Primer podprograma za prikaz enega trikotnika v |
| | 330 | ravnini je naslednji: |
| | 331 | {\scriptsize |
| | 332 | \begin{verbatim} |
| | 333 | subroutine display |
| | 334 | implicit none |
| | 335 | include 'GL/fgl.h' |
| | 336 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 337 | call fglBegin(GL_TRIANGLES) |
| | 338 | call fglColor3f(1.0, 0.0, 0.0) |
| | 339 | call fglVertex2f(-1.0, -1.0) |
| | 340 | call fglColor3f(0.0, 1.0, 0.0) |
| | 341 | call fglVertex2f(0.0, 1.0) |
| | 342 | call fglColor3f(0.0, 0.0, 1.0) |
| | 343 | call fglVertex2f(1.0, 0.0) |
| | 344 | call fglEnd |
| | 345 | call fglFlush |
| | 346 | end |
| | 347 | \end{verbatim} |
| | 348 | } |
| | 349 | Pred vsako točko je podana še trenutna barva. Izrisani |
| | 350 | trikotnik tako ni enotne barve ampak se njegova notranjost preliva iz |
| | 351 | ene skrajne barve v drugo. Rezultat prikazuje slika \ref{fig:color-triangle}. |
| | 352 | \begin{figure}[htbp] |
| | 353 | \centering |
| | 354 | \includegraphics[height=1.5in]{color-triangle} |
| | 355 | \caption{Trikotnik s podanimi barvami v vozliščih} |
| | 356 | \label{fig:color-triangle} |
| | 357 | \end{figure} |
| | 358 | Uporaba različnih barv v vozliščih mogoče ni posebno |
| | 359 | uporabna. Za podajanje normal pa je običajno potrebno, da so |
| | 360 | normale v vozliščih različne. Različne normale v |
| | 361 | vozliščih nastajajo povsod tam kjer imajo ploskvice skupen rob, |
| | 362 | za katerega želimo, da ima gladek prehod. To pa je povsod tam, kjer |
| | 363 | aproksimiramo ,,gladko`` površino z osnovnimi gradniki. Slika |
| | 364 | \ref{fig:normala} kaže splošno postavitev treh točk v |
| | 365 | prostoru. |
| | 366 | \begin{figure}[htbp] |
| | 367 | \centering |
| | 368 | \includegraphics{normal0} |
| | 369 | \caption{Normala} |
| | 370 | \label{fig:normala} |
| | 371 | \end{figure} |
| | 372 | Normalo za trikotnik |
| | 373 | z vozlišči $\vec{r}_0, \vec{r}_1, \vec{r}_2$ izračunamo z |
| | 374 | vektorskim produktom |
| | 375 | $$ \vec{n} = \frac{ (\vec{r}_1-\vec{r}_0)\times (\vec{r}_2-\vec{r}_0) } |
| | 376 | {|(\vec{r}_1-\vec{r}_0)\times (\vec{r}_2-\vec{r}_0)|} |
| | 377 | $$ |
| | 378 | |
| | 379 | Imenovalec zgornje enačbe je dolžina vektorja $\vec{n}$. |
| | 380 | Normala je pravokotna na razliko vektorjev, ki podajajo |
| | 381 | vozlišče gradnika. |
| | 382 | |
| | 383 | |
| | 384 | = Geometrijske transformacije = |
| | 385 | Osnova vseh grafičnih knjižnic so tudi osnovne geometrijske |
| | 386 | transformacije, kot so: |
| | 387 | \begin{description} |
| | 388 | \item[Translate(x, y, z)] Premik v smeri vektorja |
| | 389 | \item[Rotate(fi, x, y, z)] Rotacija za \emph{fi} stopinj okoli osi |
| | 390 | podane z (x, y, z) |
| | 391 | \item[Scale(x, y, z)] Skaliranje po posameznih oseh |
| | 392 | \end{description} |
| | 393 | Ukazi za transformacije se ne smejo pojavljati med \emph{Begin/End}, |
| | 394 | saj bi to pomenilo, da se transformacija spreminja med izrisom. |
| | 395 | Geometrijske transformacije nam pomagajo pri modeliranju, saj lahko |
| | 396 | podajamo vozlišča gradnikov v nekem poljubnem koordinatnem |
| | 397 | sistemu. To je lahko svetovni koordinatni sistem ali lokalni |
| | 398 | koordinatni sistem. Za primer izberimo izris krivulje $y(x)=\sin(x)$ v |
| | 399 | jeziku GL. Kot smo že opazili, je prednastavljeno okno v GLUT |
| | 400 | obliki kvadrata, velikosti (-1,-1) do (1,1). Vsega skupaj torej dve |
| | 401 | enoti. V zaslonskih koordinatah je prednastavljena velikost okna |
| | 402 | $300\times 300$ pikslov. Za nas je pomembno, da sinus narišemo v |
| | 403 | mejah od -1 do 1. Vzemimo primer, ko predvidimo število točk. |
| | 404 | Podprogram za izris je naslednji: |
| | 405 | |
| | 406 | {\scriptsize\begin{verbatim} |
| | 407 | subroutine display |
| | 408 | include 'GL/fgl.h' |
| | 409 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 410 | call fglBegin(GL_LINE_STRIP) |
| | 411 | do i=0,10 |
| | 412 | y = sin((i-5)/5.0*3.14) |
| | 413 | call fglVertex2f((i-5)/5.0, y/3.14) |
| | 414 | end do |
| | 415 | call fglEnd |
| | 416 | call fglFlush |
| | 417 | end |
| | 418 | \end{verbatim}} |
| | 419 | Da smo spravili naših 11 točk lomljenke v okvir -1, 1 je bilo |
| | 420 | potrebno premakniti koordinatni sistem osi $x$ za 5, ga nato še |
| | 421 | skalirati tako, da smo iz območja [0,10] dobili območje [-3.14, |
| | 422 | 3.14]. čeprav smo za izračun koordinate y potrebovali na osi x |
| | 423 | območje [-3.14, 3.14] pa je potrebna os $x$ za izris v območju |
| | 424 | [-1,1]. Zato pri izrisu podajamo os $x$ tako, da ponovno |
| | 425 | poračunavamo območje [0,10] v območje [-1,1], tako da \texttt{i}-ju |
| | 426 | odštejemo 5 in delimo z 5. Lahko bi tudi delili s 5 in odšteli |
| | 427 | 1. Nekoliko bi poenostavili stvari, če bi imeli vsaj en kordinatni |
| | 428 | sistem že takoj uporaben. Recimo os $x$. Zanka se nekoliko |
| | 429 | poenostavi, še vedno pa je potrebno vse koordinate pomanjšati |
| | 430 | za 3.14 oziroma poskalirati. |
| | 431 | {\scriptsize\begin{verbatim} |
| | 432 | do x=-3.14, 3.14, 0.6 |
| | 433 | y = sin(x) |
| | 434 | call fglVertex2f(x/3.14, y/3.14) |
| | 435 | end do |
| | 436 | \end{verbatim}} |
| | 437 | Bolj razumljivo bi bilo risati kar v lokalnem koordinatnem sistemu in |
| | 438 | prednastaviti pomanjšavo modela. Za pomanjšavo uporabimo |
| | 439 | ukaz za skaliranje, ki posamezne koordinate množi s konstanto 1/3.14, |
| | 440 | preden se izriše. Podprogram za izris je naslednji: |
| | 441 | {\scriptsize\begin{verbatim} |
| | 442 | subroutine display |
| | 443 | include 'GL/fgl.h' |
| | 444 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 445 | call fglScalef(1/3.14, 1/3.14, 1.0) |
| | 446 | call fglBegin(GL_LINE_STRIP) |
| | 447 | do x=-3.14, 3.14, 0.6 |
| | 448 | y = sin(x) |
| | 449 | call fglVertex2f(x, y) |
| | 450 | end do |
| | 451 | call fglEnd |
| | 452 | call fglFlush |
| | 453 | end |
| | 454 | \end{verbatim}} |
| | 455 | Prednost takega načina razmišljanja se pokaže, že ko |
| | 456 | želimo pod sinusom narisati še krivuljo kosinusa. Seveda ni |
| | 457 | možno obeh krivulj risati z \emph{GL\_LINE\_STRIP} v isti |
| | 458 | zanki. Zato se odločimo za ponovno risanje v lokalnem |
| | 459 | koordinatnem sistemu in prednastavimo pomik navzdol za 1.5 enote. |
| | 460 | {\scriptsize\begin{verbatim} |
| | 461 | subroutine display |
| | 462 | include 'GL/fgl.h' |
| | 463 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 464 | call fglScalef(1/3.14, 1/3.14, 1.0) |
| | 465 | call fglBegin(GL_LINE_STRIP) |
| | 466 | do x=-3.14, 3.14, 0.6 |
| | 467 | y = sin(x) |
| | 468 | call fglVertex2f(x, y) |
| | 469 | end do |
| | 470 | call fglEnd |
| | 471 | call fglTranslatef(0.0, -1.5, 0.0) |
| | 472 | call fglBegin(GL_LINE_STRIP) |
| | 473 | do x=-3.14, 3.14, 0.6 |
| | 474 | y = cos(x) |
| | 475 | call fglVertex2f(x, y) |
| | 476 | end do |
| | 477 | call fglEnd |
| | 478 | call fglFlush |
| | 479 | end |
| | 480 | \end{verbatim}} |
| | 481 | Podani program za kosinus ne nastavlja ponovno skaliranja, saj je ukaz |
| | 482 | že pred tem nastavil pomanjšavo. Translacija za -1.5 se izvede |
| | 483 | v koordinatnem sistemu kosinusa. Splošen napotek za razumevanje |
| | 484 | transformacije vsakega vozlišča je, da se za podano koordinato |
| | 485 | upoštevajo transformacije, kot so napisane od spodaj na |
| | 486 | vzgor. Transformacija, ki se izvede zadnja je torej napisana na prvem |
| | 487 | mestu v programu. Tak način transformiranja točk nam |
| | 488 | omogoča enostavnejše modeliranje. Koordinata $y$ kosinusa se |
| | 489 | izračuna tako, da se pred izrisom najprej vsaki točki $y$ |
| | 490 | prišteje translacija -1.5 in potem se še izvede skaliranje |
| | 491 | tako, da se ta vmesna točka pomnoži še z 1/3.14. |
| | 492 | |
| | 493 | |
| | 494 | \subsection{Nadzor transformacijske matrike} |
| | 495 | OpenGL pa za izračun koordinat ne hrani vse zgodovine posameznih |
| | 496 | transformacij za nazaj, saj bi bilo to računsko potratno. Vse te |
| | 497 | transformacije, ki jih v poljubnem zaporedju navajamo v programu, |
| | 498 | popravljajo transformacijsko matriko. OpenGL ima le dve aktivni |
| | 499 | transformacijski matriki, ki jih uporablja za poračun koordinat. |
| | 500 | Prva matrika je modelna, druga pa je projekcijska. Mi bomo |
| | 501 | uporabljali le modelno transformacijo in upoštevali, da |
| | 502 | projekcijska matrika omogoča prikaz ravnine $(x,y)$ v področju |
| | 503 | [-1,1]. Modelna matrika je tudi stalno aktivna, če se ne |
| | 504 | izbere projekcijsko. |
| | 505 | |
| | 506 | Modelna matrika se ob vsakem klicu transformacijskega podprograma |
| | 507 | popravi. Začetna oblika modelne matrike je enotska. Vsak klic |
| | 508 | podprograma \emph{Translate}, \emph{Scale} in \emph{Rotate} pa matriko |
| | 509 | popravi tako, da so upoštevane vse prejšnje transformacije in |
| | 510 | nad njimi še novo podana transformacija. Matrika je torej stalna, |
| | 511 | kar se kaže tudi v napaki prejšnjega programa za izris sinusa |
| | 512 | in kosinusa, ki je pri vsakem ponovnem izrisu trikrat manjši. To |
| | 513 | lahko preverimo tako, da okno prekrijemo s kakim drugim oknom in ga |
| | 514 | potem ponovno odkrijemo. Kot že omenjeno, je na začetku |
| | 515 | programa matrika enotska. S podprogramom \emph{fglLoadIdentity} na |
| | 516 | začetku bi lahko to tudi zagotovili ob vsakem izrisu. |
| | 517 | |
| | 518 | Za bolj zahtevne transformacije je potrebno matriko začasno |
| | 519 | shraniti in obnoviti. OpenGL ima v ta namen poseben pomnilnik v obliki |
| | 520 | sklada, v katerega lahko shranjujemo trenutno |
| | 521 | transformacijsko matriko. V pomnilniku oblike LIFO (\emph{Last In, |
| | 522 | First Out}) je prostora je za najmanj 32 matrik. Pomnilnik si lahko |
| | 523 | predstavljamo kot hladilnik, v katerega shranjujemo matrike. Matriko, ki |
| | 524 | jo želimo shraniti, potisnemo na začetek in to tako, da |
| | 525 | vse ostale matrike potisnemo malo naprej na polici. Ko nekaj želimo |
| | 526 | iz hladilnika, je to lahko le zadnja matrika. če |
| | 527 | želimo predzadnjo, moramo poprej vzeti zadnjo. Za shranitev |
| | 528 | trenutne matrike se uporabi \textbf{glPushMatrix}, za ponastavitev iz |
| | 529 | sklada pa uporabimo \textbf{glPopMatrix}. Izkaže se, da je za |
| | 530 | modeliranje taka oblika pomnilnika povsem primerna. |
| | 531 | |
| | 532 | Za primer vzemimo primer kocke sestavljene iz šestih ploskev. Za |
| | 533 | izris kvadrata obstaja že krajša funkcija \texttt{glRectf(x1, |
| | 534 | y1, x2, y2)} za ravnino $z=0$. če želimo imeti kvadrat v |
| | 535 | poljubni ravnini, pa uporabimo transformacije. |
| | 536 | {\scriptsize |
| | 537 | \begin{verbatim} |
| | 538 | subroutine kvadrat(i) |
| | 539 | real r(6), g(6), b(6) |
| | 540 | data r /1,0,0,1,1,1/, g /0,1,0,1,0,0/ |
| | 541 | data b /0,0,1,0,1,1/ |
| | 542 | call fglPushMatrix |
| | 543 | call fglColor3f(r(i), g(i), b(i)) |
| | 544 | call fglTranslatef(0.0, 0.0, 1.0) |
| | 545 | call fglRectf(-1.0, -1.0, 1.0, 1.0) |
| | 546 | call fglPopMatrix |
| | 547 | end |
| | 548 | |
| | 549 | subroutine display |
| | 550 | implicit none |
| | 551 | include 'GL/fgl.h' |
| | 552 | call fglClear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT) |
| | 553 | call fglPushMatrix |
| | 554 | call fglRotatef(30.0, 1.0, 0.0, 0.0) |
| | 555 | call fglRotatef(30.0, 0.0, 1.0, 0.0) |
| | 556 | call fglScalef(0.5, 0.5, 0.5) |
| | 557 | call kvadrat(1) |
| | 558 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 559 | call kvadrat(2) |
| | 560 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 561 | call kvadrat(3) |
| | 562 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 563 | call kvadrat(4) |
| | 564 | call fglRotatef(90.0, 1.0, 0.0, 0.0) |
| | 565 | call kvadrat(5) |
| | 566 | call fglRotatef(180.0, 1.0, 0.0, 0.0) |
| | 567 | call kvadrat(6) |
| | 568 | call fglPopMatrix |
| | 569 | call fglFlush |
| | 570 | end |
| | 571 | |
| | 572 | program kocka |
| | 573 | external display |
| | 574 | include 'GL/fglut.h' |
| | 575 | include 'GL/fgl.h' |
| | 576 | call fglutinit |
| | 577 | call fglutInitDisplayMode(GLUT_SINGLE+GLUT_DEPTH) |
| | 578 | call fglutCreateWindow('Fortran GLUT program') |
| | 579 | call fglutDisplayFunc(display) |
| | 580 | call fglEnable(GL_DEPTH_TEST) |
| | 581 | call fglutmainloop |
| | 582 | end |
| | 583 | \end{verbatim} |
| | 584 | } Podprogram \emph{kvadrat} je narejen tako, da riše transliran |
| | 585 | kvadrat v ravnini $z=1$. To lahko razumemo kot nov primitiv, saj par |
| | 586 | ukazov Push/Pop ne popravlja transformacije ob klicu podprograma. |
| | 587 | šest stranic se riše z rotacijo osnovne stranice okoli osi y |
| | 588 | in x. Modelna matrika se shani z začetnim ukazom \emph{Push} in |
| | 589 | potem ponovno obnovi z ukazom \emph{Pop}. |
| | 590 | |
| | 591 | |
| | 592 | \subsection{Globinski pomilnik} |
| | 593 | Da se ploskve v prostoru pravilno izrisujejo tudi takrat, ko |
| | 594 | rišemo ploskvice za drugimi, je potrebno uporabiti globinski |
| | 595 | pomnilnik ali \emph{z-buffer}. To pa mora omogočati že sam |
| | 596 | okenski sistem, zato je potrebno tak način prikaza zahtevati že |
| | 597 | pri \texttt{fglutInitDisplayMode} in kasneje še dopovedati GL |
| | 598 | stroju, da poleg barve točk na zaslonu shranjuje še koordinato |
| | 599 | $z$ v svoj pomnilnik. S tem pomnilnikom GL ob rasterizaciji lika za |
| | 600 | vsako točko ugotovi, če je že kakšna točka po |
| | 601 | globini pred njim in jo zato ne riše. Z ukazom |
| | 602 | \texttt{fglEnable(GL\_DEPTH\_TEST)} se zahteva izračunavanje |
| | 603 | globine, ki jo je potrebno tako kot barvo pred vsakim začetkom |
| | 604 | risanja pobrisati z ukazom \texttt{fglClear}. |
| | 605 | |
| | 606 | \begin{figure}[htbp] |
| | 607 | \centering |
| | 608 | \includegraphics{half-cube} |
| | 609 | \caption{Kocka brez spodnjega in zgornjega pokrova (kvadrat(5) in kvadrat(6)} |
| | 610 | \label{fig:half-cube} |
| | 611 | \end{figure} |
| | 612 | |
| | 613 | če rišemo zaprte modele, potem notranjosti ni možno |
| | 614 | videti. Primer odprtega modela kaže slika \ref{fig:half-cube}. V |
| | 615 | takih primerih se ob uporabi prostorskega pomnilnika običajno kar |
| | 616 | polovica ploskvic modela prekrije v celoti in kasneje na zaslonu ni |
| | 617 | vidna. Skupna značilnost vseh teh ploskvic, ki se prekrijejo je, |
| | 618 | da imajo normalo površine negativno ($n_z < 0$). Da se izogemo |
| | 619 | nepotrebni rasterizaciji teh ploskvic, vključimo |
| | 620 | \texttt{GL\_CULL\_FACE}. Da pa bo izločanje delovalo, mora imeti |
| | 621 | GL podatek za normalo površine, ki jo je potrebno podati pred |
| | 622 | podatki v vozliščih. Za pravilno delovanje globinskega |
| | 623 | pomnilnika je potrebna tudi nastavitev projekcijske matrike kot je to |
| | 624 | opisano v \S\ref{sec:viewing}. |
| | 625 | |
| | 626 | \subsection{Animacija} |
| | 627 | \label{sec:animate} |
| | 628 | Imejmo primer animacije vozil na avtocesti. Predstavljeno bo |
| | 629 | cestišče v eno smer z dvema pasovoma, voznim in prehitevalnim. |
| | 630 | Vozila imajo začetni položaj in hitrost. Opazujemo |
| | 631 | vozišče dolžine 500 metrov. Hitrost vozila med vožnjo se ne |
| | 632 | spreminja. Spreminja se le položaj vozil (x, y) na |
| | 633 | cestišču, ki jih izriše podprogram \texttt{vozilo}. |
| | 634 | |
| | 635 | {\scriptsize\begin{verbatim} |
| | 636 | subroutine display |
| | 637 | implicit none |
| | 638 | include 'GL/fgl.h' |
| | 639 | common /vozila/ y(5), v(5) |
| | 640 | real y, v, pas |
| | 641 | integer i |
| | 642 | data y /0,50,120,170,200/ |
| | 643 | data v /50,30,45,31,33/ |
| | 644 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 645 | call fglPushMatrix |
| | 646 | call fglRotatef(-45.0, 0.0, 0.0, 1.0) |
| | 647 | call fglTranslatef(0.0, -1.0, 0.0) |
| | 648 | call fglScalef(0.004, 0.004, 0.004) |
| | 649 | call fglColor3f(0.0, 0.0, 0.0) |
| | 650 | call fglRectf(-4.0, 0.0, 4.0, 500.0) |
| | 651 | call fglTranslatef(0.0, -50.0, 0.0) |
| | 652 | do i=1,5 |
| | 653 | if (i.ne.5 .and. y(i+1)-y(i).lt.10.0) then |
| | 654 | pas=-2.0 |
| | 655 | else |
| | 656 | pas = 2.0 |
| | 657 | end if |
| | 658 | call vozilo(y(i), pas) |
| | 659 | end do |
| | 660 | call fglPopMatrix |
| | 661 | call fglutSwapBuffers |
| | 662 | end |
| | 663 | |
| | 664 | subroutine vozilo(y, pas) |
| | 665 | call fglPushMatrix |
| | 666 | call fglColor3f(1.0, 1.0, 1.0) |
| | 667 | call fglTranslatef(pas, y, 0.5) |
| | 668 | call fglRectf(-2.0, 0.0, 2.0, 6.0) |
| | 669 | call fglPopMatrix |
| | 670 | end |
| | 671 | |
| | 672 | subroutine ura(n) |
| | 673 | common /vozila/ y(5), v(5) |
| | 674 | real y, v, dt |
| | 675 | dt = 0.1 |
| | 676 | do i=1,5 |
| | 677 | y(i)=y(i)+v(i)*dt |
| | 678 | end do |
| | 679 | call fglutPostRedisplay |
| | 680 | call fglutTimerfunc(100, ura, 0) |
| | 681 | end |
| | 682 | |
| | 683 | program Mad Max |
| | 684 | external display |
| | 685 | external ura |
| | 686 | include 'GL/fglut.h' |
| | 687 | integer window |
| | 688 | call fglutInit |
| | 689 | call fglutInitDisplayMode(GLUT_RGB+GLUT_DOUBLE) |
| | 690 | call fglutCreateWindow('Avtocesta') |
| | 691 | call fglClearColor(0.0, 0.5, 0.0, 0.0) |
| | 692 | call fglutDisplayFunc(display) |
| | 693 | call fglutTimerFunc(100, ura, 0) |
| | 694 | call fglutMainLoop |
| | 695 | end |
| | 696 | \end{verbatim} |
| | 697 | } |
| | 698 | |
| | 699 | Pas predstavlja odmik v smeri $x$ od sredine cestišča. Vse |
| | 700 | enote so v metrih. Vozilo je zaradi sorazmerja narisano |
| | 701 | nekoliko večje. Za animacije je primernejša uporaba dvojnega |
| | 702 | pomnilnika \texttt{GLUT\_DOUBLE}. S tem se izognemo težavam izrisa, |
| | 703 | saj v trenutku, ko se zgornja plast izrisuje, nemoteno rišemo v |
| | 704 | spodnjo plast. Ko je spodnja plast izdelana z ukazom |
| | 705 | \emph{fglutSwapBuffers}, zamenjamo trenutni prikaz. |
| | 706 | |
| | 707 | Za animacijo, pri kateri je zahtevano točno časovno zaporedje je |
| | 708 | primerno uporabiti uro (\emph{timer}), ki program opozori, da je |
| | 709 | pretekel predpisani čas in da je potrebno izračunati nov |
| | 710 | položaj vozil. V našem primeru je podana spremeba vsakih 100~ms |
| | 711 | in zato nov položaj v smeri $y$ linearno narašča za $v(i) |
| | 712 | dt$, kjer je hitrost podana v metrih na sekundo. Izbor 0.1s za premik |
| | 713 | pomeni 1/0.1=10 posnetkov na sekundo, kar je spodnja meja pri |
| | 714 | animacijah. Po poračunu novih položajev pošljemo |
| | 715 | sporočilo \emph{fglutPostRedisplay}, da se na novo izriše |
| | 716 | scena. Lahko bi tudi neposredno klicali \texttt{display}, vendar bi |
| | 717 | bilo potem potrebno zagotoviti še kompenzacijo hitrosti, saj |
| | 718 | že v podprogramu ura izgubimo nekaj časa pri izrečunu |
| | 719 | novih položajev. Prostorski pomnilnik v tem primeru ni potreben, |
| | 720 | saj je zagotovljeno, da se izrisi prekrijejo v pravilnem vrstnem redu. |
| | 721 | |
| | 722 | \subsection{Transformacije pogleda} |
| | 723 | \label{sec:viewing} |
| | 724 | Za zahtevnejše načine gledanja na model je potrebno nastaviti |
| | 725 | projekcijo modela iz svetovnih koordinat v normalizirane oz. zaslonske |
| | 726 | koordinate. V praksi obstajata dva načina projekcije: ortografska in |
| | 727 | perspektivna. V tehniški predstavitvah se uporablja predvsem |
| | 728 | paralelna oz. ortografska projekcija. Le v primeru animacije, kjer |
| | 729 | želimo poudariti bližino in oddaljenost določenih |
| | 730 | objektov, se uporablja tudi perspektivna projekcija. |
| | 731 | \begin{figure}[htbp] |
| | 732 | \centering |
| | 733 | \includegraphics[width=3in]{viewing} |
| | 734 | \caption{Zaporedje pretvorbe koordinat vozlišč} |
| | 735 | \label{fig:viewing} |
| | 736 | \end{figure} |
| | 737 | |
| | 738 | OpenGL ločuje projekcijsko matriko in modelno matriko zato, |
| | 739 | da ni potrebno nastavljati projekcije pri vsakem izrisu. Slika |
| | 740 | \ref{fig:viewing} kaže zaporedje transformacij iz svetovnih |
| | 741 | koordinat v zaslonske. Pri risanju modela običajno začnemo z |
| | 742 | enotsko \emph{ModelView} matriko. |
| | 743 | |
| | 744 | Najpreprostejši način prikaza, kot je bil prikazan tudi v |
| | 745 | dosedanjih primerih je, da stlačimo naš model s |
| | 746 | transformacijami v privzete normalizirane koordinate [-1, 1]. Pri tem |
| | 747 | načinu sta tako modelna kot projekcijska matrika enotski. Modelna |
| | 748 | matrika je enotska le na začetku vsakega risanja, projekcijska pa |
| | 749 | je konstantna ves čas. Pri takem načinu ni potrebno |
| | 750 | preklapljati med trenutno aktivnima projekcijama. In če se |
| | 751 | zadovoljimo s takim načinom, potem zadostuje tudi |
| | 752 | privzeta zaslonska transformacija pri spremembi velikosti okna, ki je |
| | 753 | pri sistemu GLUT le enovrstičen ukaz: |
| | 754 | |
| | 755 | {\scriptsize\texttt{call fglViewport (0, 0, width, height)}} |
| | 756 | |
| | 757 | Nekoliko zahtevnejša je sorazmerna sprememba, |
| | 758 | ki ne bo anamorfično popravljala velikosti okna: |
| | 759 | {\scriptsize\begin{verbatim} |
| | 760 | subroutine reshape (w, h) |
| | 761 | integer w, h |
| | 762 | implicit none |
| | 763 | include 'GL/fgl.h' |
| | 764 | common /viewport/ width, height |
| | 765 | integer width, height |
| | 766 | real*8 left, right, bottom, top, znear, zfar |
| | 767 | width = w |
| | 768 | height = h |
| | 769 | if (w .ge. h) then |
| | 770 | left = -width/(1.0*height) |
| | 771 | right = width/(1.0*height) |
| | 772 | bottom = -1.0 |
| | 773 | top = 1.0 |
| | 774 | else |
| | 775 | left = -1.0 |
| | 776 | right = 1.0 |
| | 777 | bottom = -height/(1.0*width) |
| | 778 | top = height/(1.0*width) |
| | 779 | end if |
| | 780 | znear = -1.0 |
| | 781 | zfar = 1.0 |
| | 782 | call fglViewport (0, 0, width, height) |
| | 783 | call fglMatrixMode (GL_PROJECTION) |
| | 784 | call fglLoadIdentity |
| | 785 | call fglOrtho(left, right, bottom, top, znear, zfar) |
| | 786 | call fglMatrixMode(GL_MODELVIEW) |
| | 787 | end |
| | 788 | \end{verbatim} |
| | 789 | } |
| | 790 | |
| | 791 | Predstavljeni podprogram se priporoča v uporabo za vse programe, |
| | 792 | ki pripravljajo model v velikosti [-1,~1] za \emph{ModelView}. če |
| | 793 | bi želeli dodati modelno transformacijo v \emph{reshape}, potem za |
| | 794 | zadnjo vrstico dopišemo še modelno transformacijo in nato v |
| | 795 | programu za izris pred začetkom le obnovimo stanje modelne |
| | 796 | matrike. Primer animacije \ref{sec:animate} bi tako imel namesto |
| | 797 | nastavitve modelne transformacije slednje v podprogramu \emph{reshape}. |
| | 798 | Začetna nastavitev modelne matrike pred začetkom izrisa v |
| | 799 | podprogramu \emph{display} pa bi bila: |
| | 800 | {\scriptsize\begin{verbatim} |
| | 801 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 802 | call fglPushMatrix |
| | 803 | ... izris |
| | 804 | call fglPopMatrix |
| | 805 | \end{verbatim} |
| | 806 | } |
| | 807 | Takoj za brisanjem zaslona z ukazom \emph{Push} shranimo modelno |
| | 808 | matriko in jo ob koncu ponovno nastavimo na začetno vrednost. |
| | 809 | V podprogramu \emph{reshape}, pa modelno matriko popravljamo: |
| | 810 | {\scriptsize\begin{verbatim} |
| | 811 | call fglMatrixMode(GL_MODELVIEW) |
| | 812 | call fglLoadIdentity |
| | 813 | call fglRotatef(-45.0, 0.0, 0.0, 1.0) |
| | 814 | call fglTranslatef(0.0, -1.0, 0.0) |
| | 815 | call fglScalef(0.004, 0.004, 0.004) |
| | 816 | \end{verbatim} |
| | 817 | } |
| | 818 | Tak pristop nekoliko jasneje predstavi program, saj so vse enote, s |
| | 819 | katerimi manipuliramo, v programu za izris v svetovnih oz. modelnih |
| | 820 | koordinatah. V spošnem se priporoča nastavitev projekcije za |
| | 821 | vse modele, ki uporabljajo izris ploskev. To pa zaradi tega, ker je |
| | 822 | privzeta projekcijska matrika enotska. Poglejmo to na primeru |
| | 823 | paralelne projekcije \emph{glOrtho(l,r,b,,n,f)}: |
| | 824 | $$ |
| | 825 | PM = \left[ |
| | 826 | \begin{array}{cccc} |
| | 827 | \frac{2}{r-l} & 0 & 0 & \frac{r+l}{l-r} \cr |
| | 828 | 0 & \frac{2}{t-b} & 0 & \frac{t+b}{t-b} \cr |
| | 829 | 0 & 0 & \frac{2}{f-n} & \frac{f+n}{f-n} \cr |
| | 830 | 0 & 0 & 0 & 1 \cr |
| | 831 | \end{array} |
| | 832 | \right]\quad . |
| | 833 | $$ |
| | 834 | Za primer normalizacijskega prostora v obsegu [-1,1] je tako matrika paralelne |
| | 835 | projekcije |
| | 836 | $$ |
| | 837 | PM(-1, 1, -1, 1, -1, 1) = \left[ |
| | 838 | \begin{array}{cccc} |
| | 839 | 1 & 0 & 0 & 0 \cr |
| | 840 | 0 & 1 & 0 & 0 \cr |
| | 841 | 0 & 0 & -1 & 0 \cr |
| | 842 | 0 & 0 & 0 & 1 \cr |
| | 843 | \end{array} |
| | 844 | \right]\quad , |
| | 845 | $$ |
| | 846 | kar se razlikuje od enotske prav v koordinati $z$. če bi |
| | 847 | projekcijsko matriko ohranili enotsko, potem bi to pomenilo, da objekt |
| | 848 | gledamo kot zrcalno sliko zadnje strani. Nastavitev projekcijske matrike |
| | 849 | je torej obvezna za vse izrise ploskvic po globini, kot tudi za modele |
| | 850 | z osenčenjem. Zaradi tega je tudi program za izris kocke |
| | 851 | nelogično postavil v ospredje modro stranico in ne rdečo. |
| | 852 | |
| | 853 | = Osvetlitev = |
| | 854 | Do sedaj predstavljeni primeri so uporabljali le sintetične barve. |
| | 855 | To pomeni, da se barva vsake ploskvice ne spreminja v odvisnosti od |
| | 856 | položaja v prostoru. Tak način prikaza je uporaben le za |
| | 857 | omejen nabor prostorskih modelov. Neprimeren je že za vse modele, |
| | 858 | ki imajo površine sestavljene iz primitivov in te površine |
| | 859 | niso ravninske. Za primer kocke (slika \ref{fig:half-cube}) je bilo |
| | 860 | potrebno za vsako stranico nastaviti svojo barvo, da smo lahko dobili |
| | 861 | vtis prostora. če bi kocko risali le z eno barvo, potem bi dobili |
| | 862 | na zaslon le obris. |
| | 863 | |
| | 864 | Za bolj realističen izris je potrebno vključiti računanje |
| | 865 | osvetlitve. žal osvetlitev zajema veliko parametrov, ki jih je |
| | 866 | potrebno nastaviti preden lahko karkoli dobimo na zaslonu. Tako je |
| | 867 | potrebno nastavljati položaj in lastnosti luči, osvetlitveni |
| | 868 | model in lastnosti površin modelov. Za vsako luč se lahko tako |
| | 869 | nastavi 10 lastnosti in vsaka površina ima 5 lastnosti materiala. |
| | 870 | |
| | 871 | Kot predpogoj za pravilno osvetljen model pa je podana normala v |
| | 872 | vsakem vozlišču vsake ploskvice. Najpreprostejši način |
| | 873 | pri uporabi osvetlitve je, da parametre luči ne nastavljamo in da |
| | 874 | uporabimo nastavljanje lastnosti materiala površine le z ukazom za |
| | 875 | barvo. S tem vpeljemo veliko predpostavk, ki pa so za šolsko rabo |
| | 876 | povsem uporabne. Predpostavljena je le ena luč bele svetlobe s |
| | 877 | položajem $(0, 0, 1)$ in difuzni odboj svetlobe na |
| | 878 | površini. Barvo površine podajamo kar z običajnim ukazom |
| | 879 | za barvo. Program za izris osenčenega modela kocke je tako v |
| | 880 | minimalni obliki naslednji: |
| | 881 | |
| | 882 | {\scriptsize\begin{verbatim} |
| | 883 | subroutine kvadrat() |
| | 884 | call fglPushMatrix |
| | 885 | call fglTranslatef(0.0, 0.0, 1.0) |
| | 886 | call fglNormal3f(0.0, 0.0, 1.0) |
| | 887 | call fglRectf(-1.0, -1.0, 1.0, 1.0) |
| | 888 | call fglPopMatrix |
| | 889 | end |
| | 890 | |
| | 891 | subroutine display |
| | 892 | implicit none |
| | 893 | include 'GL/fgl.h' |
| | 894 | call fglClear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT) |
| | 895 | call fglColor3f(0.7, 0.6, 0.2) |
| | 896 | call fglPushMatrix |
| | 897 | call fglScalef(0.5, 0.5, 0.5) |
| | 898 | call kvadrat |
| | 899 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 900 | call kvadrat |
| | 901 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 902 | call kvadrat |
| | 903 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 904 | call kvadrat |
| | 905 | call fglRotatef(90.0, 1.0, 0.0, 0.0) |
| | 906 | call kvadrat |
| | 907 | call fglRotatef(180.0, 1.0, 0.0, 0.0) |
| | 908 | call kvadrat |
| | 909 | call fglPopMatrix |
| | 910 | call fglFlush |
| | 911 | end |
| | 912 | |
| | 913 | |
| | 914 | program kocka |
| | 915 | external display |
| | 916 | external reshape |
| | 917 | include 'GL/fglut.h' |
| | 918 | include 'GL/fgl.h' |
| | 919 | call fglutinit |
| | 920 | call fglutinitdisplaymode(GLUT_SINGLE+GLUT_DEPTH) |
| | 921 | call fglutcreatewindow('Osencena kocka') |
| | 922 | call fglutDisplayFunc(display) |
| | 923 | call fglutReshapeFunc(reshape) |
| | 924 | call fglClearColor(1.0, 1.0, 1.0, 1.0) |
| | 925 | call fglEnable(GL_LIGHTING) |
| | 926 | call fglEnable(GL_LIGHT0) |
| | 927 | call fglEnable(GL_DEPTH_TEST) |
| | 928 | call fglEnable(GL_COLOR_MATERIAL) |
| | 929 | call fglutMainLoop |
| | 930 | end |
| | 931 | |
| | 932 | subroutine reshape (w, h) |
| | 933 | integer w, h |
| | 934 | implicit none |
| | 935 | include 'GL/fgl.h' |
| | 936 | real*8 l |
| | 937 | l = 1.0 |
| | 938 | call fglViewport (0, 0, w, h) |
| | 939 | call fglMatrixMode (GL_PROJECTION) |
| | 940 | call fglLoadIdentity |
| | 941 | call fglOrtho(-l, l, -l, l, -l, l) |
| | 942 | call fglMatrixMode(GL_MODELVIEW) |
| | 943 | call fglLoadIdentity |
| | 944 | call fglRotatef(30.0, 1.0, 0.0, 0.0) |
| | 945 | call fglRotatef(30.0, 0.0, 1.0, 0.0) |
| | 946 | end |
| | 947 | \end{verbatim} |
| | 948 | } |
| | 949 | |
| | 950 | Razširjeni primitiv smo poenostavili tako, da ne vsebuje več |
| | 951 | definicije barve, ampak le geometrijo. Obvezno je bilo potrebno podati |
| | 952 | izračun normale. Za naš primitiv kvadrata je to $(0,0,1)$. |
| | 953 | Program za izris v bistvu ni spremenjen, le da je sedaj |
| | 954 | transformacija modela preseljena v podprogram za nastavitev velikosti |
| | 955 | okna \texttt{reshape}. V glavnem programu pa je potrebno najprej |
| | 956 | vključiti računanje osvetlitve \emph{GL\_LIGHTING}, |
| | 957 | prižgati je potrebno luč št 0, ki ima začetni |
| | 958 | položaj $(0,0,1)$. |
| | 959 | |
| | 960 | \begin{figure}[htbp] |
| | 961 | \centering |
| | 962 | \includegraphics[width=2.5in]{cube-l} |
| | 963 | \caption{Osenčen model kocke} |
| | 964 | \label{fig:cube-l} |
| | 965 | \end{figure} |
| | 966 | |
| | 967 | Z vključitvijo \emph{GL\_COLOR\_MATERIAL} |
| | 968 | pa poenostavimo podajanje barve za material površine tako, da vsi |
| | 969 | klici podprogramov \emph{Color} nastavljajo privzeto difuzno in |
| | 970 | ambientno barvo površine. Slika \ref{fig:cube-l} prikazuje |
| | 971 | rezultat upodabljanja z osvetlitvijo. |
| | 972 | |
| | 973 | |
| | 974 | |
| | 975 | = Tekst = |
| | 976 | OpenGL sam ne podpira teksta in je zato potrebno uporabiti razne |
| | 977 | prijeme za izris teksta v prostoru. Možnih je več načinov |
| | 978 | za risanje besedila: |
| | 979 | \begin{description} |
| | 980 | \item[stroke] črke so izrisane s črtami v prostoru modela |
| | 981 | \item[bitmap] črke so izrisane na zaslon |
| | 982 | \item[teksture] črke so izrisane rastrsko v prostoru modela |
| | 983 | \end{description} |
| | 984 | V šolskih primerih so najbolj uporabni že izdelani fonti v |
| | 985 | knjižnici GLUT. Možne so naslednje številke fontov: |
| | 986 | {\small |
| | 987 | \begin{enumerate} |
| | 988 | \item GLUT\_STROKE\_ROMAN |
| | 989 | \item GLUT\_STROKE\_MONO\_ROMAN |
| | 990 | \item GLUT\_BITMAP\_9\_BY\_15 |
| | 991 | \item GLUT\_BITMAP\_8\_BY\_13 |
| | 992 | \item GLUT\_BITMAP\_TIMES\_ROMAN\_10 |
| | 993 | \item GLUT\_BITMAP\_TIMES\_ROMAN\_24 |
| | 994 | \item GLUT\_BITMAP\_HELVETICA\_10 |
| | 995 | \item GLUT\_BITMAP\_HELVETICA\_12 |
| | 996 | \item GLUT\_BITMAP\_HELVETICA\_18 |
| | 997 | \end{enumerate} |
| | 998 | } |
| | 999 | |
| | 1000 | Za primer razširimo program za izris osenčene kocke z |
| | 1001 | besedilom na vsaki stranici. Podprogram \emph{kvadrat} kot argument |
| | 1002 | vzame besedilo. Začetek izpisa premakne za malenkost višje in |
| | 1003 | začne v koordinati $x=-0.8$. Ker pa ne želimo, da se besedilo |
| | 1004 | senči je tu potrebno izklapljanje senčenja takrat, ko |
| | 1005 | izrisujemo posamezne črke. Ker so črke v vnaprej določeni |
| | 1006 | velikost, jih je potrebno ustrezno pomanjšati s |
| | 1007 | skaliranjem. Podprogram \texttt{fglutStrokeCharacter} po vsaki |
| | 1008 | izrisani črti sam nastavi pomik v smeri x za širino izrisane |
| | 1009 | črke. |
| | 1010 | |
| | 1011 | {\scriptsize\begin{verbatim} |
| | 1012 | subroutine kvadrat(s) |
| | 1013 | include 'GL/fgl.h' |
| | 1014 | character s*(*), c |
| | 1015 | call fglPushMatrix |
| | 1016 | call fglTranslatef(0.0, 0.0, 1.0) |
| | 1017 | call fglNormal3f(0.0, 0.0, 1.0) |
| | 1018 | call fglRectf(-1.0, -1.0, 1.0, 1.0) |
| | 1019 | call fglTranslatef(-0.8, 0.0, 0.01) |
| | 1020 | call fglDisable(GL_LIGHTING) |
| | 1021 | call fglScalef(0.003, 0.003, 0.003) |
| | 1022 | call fglColor3f(1.0, 0.0, 0.0) |
| | 1023 | lenc = len(s) |
| | 1024 | do i=1,lenc |
| | 1025 | c = s(i:i) |
| | 1026 | call fglutStrokeCharacter(1, ichar(c)) |
| | 1027 | end do |
| | 1028 | call fglEnable(GL_LIGHTING) |
| | 1029 | call fglPopMatrix |
| | 1030 | end |
| | 1031 | |
| | 1032 | subroutine display |
| | 1033 | implicit none |
| | 1034 | include 'GL/fgl.h' |
| | 1035 | real mat(4) |
| | 1036 | data mat /0.9, 0.6, 0.3, 1.0/ |
| | 1037 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 1038 | call fglClear(GL_DEPTH_BUFFER_BIT) |
| | 1039 | call fglPushMatrix |
| | 1040 | call fglRotatef(30.0, 1.0, 0.0, 0.0) |
| | 1041 | call fglRotatef(30.0, 0.0, 1.0, 0.0) |
| | 1042 | call fglScalef(0.5, 0.5, 0.5) |
| | 1043 | call fglMaterialfv(GL_FRONT, GL_DIFFUSE, mat) |
| | 1044 | call kvadrat('Spredaj') |
| | 1045 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 1046 | call kvadrat('Desno') |
| | 1047 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 1048 | call kvadrat('Zadaj') |
| | 1049 | call fglRotatef(90.0, 0.0, 1.0, 0.0) |
| | 1050 | call kvadrat('Levo') |
| | 1051 | call fglRotatef(90.0, 1.0, 0.0, 0.0) |
| | 1052 | call kvadrat('Spodaj') |
| | 1053 | call fglRotatef(180.0, 1.0, 0.0, 0.0) |
| | 1054 | call kvadrat('Zgoraj') |
| | 1055 | call fglPopMatrix |
| | 1056 | call fglFlush |
| | 1057 | end |
| | 1058 | |
| | 1059 | subroutine reshape(w, h) |
| | 1060 | include 'GL/fgl.h' |
| | 1061 | integer w, h |
| | 1062 | real*8 l |
| | 1063 | l = 1 |
| | 1064 | call fglViewPort(0, 0, w, h) |
| | 1065 | call fglMatrixMode(GL_PROJECTION) |
| | 1066 | call fglLoadIdentity |
| | 1067 | call fglOrtho(-l,l,-l,l,-l,l) |
| | 1068 | call fglMatrixMode(GL_MODELVIEW) |
| | 1069 | call fglLoadIdentity |
| | 1070 | end |
| | 1071 | |
| | 1072 | program crta |
| | 1073 | external display |
| | 1074 | external reshape |
| | 1075 | include 'GL/fglut.h' |
| | 1076 | include 'GL/fgl.h' |
| | 1077 | call fglutinit |
| | 1078 | call fglutinitdisplaymode(GLUT_SINGLE+GLUT_DEPTH) |
| | 1079 | call fglutcreatewindow('Fortran GLUT program') |
| | 1080 | call fglutDisplayFunc(display) |
| | 1081 | call fglutReshapeFunc(reshape) |
| | 1082 | call fglEnable(GL_DEPTH_TEST) |
| | 1083 | call fglEnable(GL_LIGHTING) |
| | 1084 | call fglEnable(GL_LIGHT0) |
| | 1085 | call fglClearColor(1.0, 1.0, 1.0, 1.0) |
| | 1086 | call fglutmainloop |
| | 1087 | end |
| | 1088 | \end{verbatim} |
| | 1089 | } |
| | 1090 | |
| | 1091 | \begin{figure}[htbp] |
| | 1092 | \centering |
| | 1093 | \includegraphics[width=2.5in]{cube-f} |
| | 1094 | \caption{Osenčen model kocke z napisi} |
| | 1095 | \label{fig:cube-f} |
| | 1096 | \end{figure} |
| | 1097 | |
| | 1098 | Podajanje barve za površino je spremenjeno tako, da se ne uporabi |
| | 1099 | funkcije \emph{Color} ampak normalno funkcijo za podajanje lastnosti |
| | 1100 | materialaf \texttt{glMaterialfv}. Rezultat kaže slika |
| | 1101 | \ref{fig:cube-f}. če bi napisali komentar pred izrisom |
| | 1102 | štirikotnika, potem bi bilo vidno besedilo tudi za ostale |
| | 1103 | (skrite) strani. |
| | 1104 | |
| | 1105 | Včasih pa raje želimo, da se besedilo na zaslonu ne |
| | 1106 | izrisuje rotirano in senčeno, temveč da se le pojavi na |
| | 1107 | določenem položaju v prostoru in potem izriše v zaslonskih |
| | 1108 | koordinatah. V ta namen uporabimo \emph{bitmap} fonte in naslednji |
| | 1109 | podprogram za izpis besedila: |
| | 1110 | {\scriptsize\begin{verbatim} |
| | 1111 | subroutine output(x,y,z,s) |
| | 1112 | character s*(*) |
| | 1113 | call fglRasterPos3f(x,y,z) |
| | 1114 | lenc = len(s) |
| | 1115 | do i=1,lenc |
| | 1116 | call fglutBitmapCharacter(6, ichar(s(i:i))) |
| | 1117 | end do |
| | 1118 | end |
| | 1119 | \end{verbatim} |
| | 1120 | } |
| | 1121 | Primer izrisa z bitmap fonti kaže slika \ref{fig:cube-b} |
| | 1122 | \begin{figure}[htbp] |
| | 1123 | \centering |
| | 1124 | \includegraphics[width=2.5in]{cube-b} |
| | 1125 | \caption{Osenčen model kocke z \emph{bitmap} napisi} |
| | 1126 | \label{fig:cube-b} |
| | 1127 | \end{figure} |
| | 1128 | |
| | 1129 | |
| | 1130 | = Uporabniški vmesnik = |
| | 1131 | V nadaljevanju so prikazani primeri programov, ki izkoriščajo |
| | 1132 | dodatne funkcionalnost knjižnice GLUT za vnos dodatnih podatkov v |
| | 1133 | program. To je predvsem uporaba tipk in miške. |
| | 1134 | |
| | 1135 | \subsection{Rotacija s tipkami} |
| | 1136 | Rotiramo že vgrajeni geometrijski model čajnika s tipkami \textbf{x, |
| | 1137 | y, z}. Vsak pritisk na tipko poveča kot rotacije za pet stopinj. |
| | 1138 | Podatke o trenutni rotaciji prenašamo s poljem \texttt{common}. Ker |
| | 1139 | izrisujemo žični model, podprogram za \emph{reshape} ni |
| | 1140 | potreben. Podprogram \emph{keyboard} ob pritisku na tipko dobi tudi |
| | 1141 | informacijo o zaslonskem položaju miške. |
| | 1142 | |
| | 1143 | {\scriptsize\begin{verbatim} |
| | 1144 | subroutine display |
| | 1145 | implicit none |
| | 1146 | include 'GL/fgl.h' |
| | 1147 | common /rotation/ rx, ry, rz |
| | 1148 | real rx, ry, rz |
| | 1149 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 1150 | call fglColor3f(0.5, 0.4, 1.0) |
| | 1151 | call fglPushMatrix |
| | 1152 | call fglRotatef(rx, 1.0, 0.0, 0.0) |
| | 1153 | call fglRotatef(ry, 0.0, 1.0, 0.0) |
| | 1154 | call fglRotatef(rz, 0.0, 0.0, 1.0) |
| | 1155 | call fglutWireTeapot(dble(r)) |
| | 1156 | call fglPopMatrix |
| | 1157 | call fglutSwapBuffers |
| | 1158 | end |
| | 1159 | |
| | 1160 | subroutine keyboard(key,x,y) |
| | 1161 | common /rotation/ rx, ry, rz |
| | 1162 | integer key,x,y |
| | 1163 | print *, 'Key ', char(key), key, ' at', x, y |
| | 1164 | if (key .eq. ichar('x')) rx = rx + 5.0 |
| | 1165 | if (key .eq. ichar('y')) ry = ry + 5.0 |
| | 1166 | if (key .eq. ichar('z')) rz = rz + 5.0 |
| | 1167 | call fglutpostredisplay |
| | 1168 | end |
| | 1169 | |
| | 1170 | program teapot |
| | 1171 | external display |
| | 1172 | external keyboard |
| | 1173 | include 'GL/fglut.h' |
| | 1174 | integer window |
| | 1175 | call fglutInit |
| | 1176 | call fglutInitDisplayMode(ior(GLUT_DOUBLE,GLUT_RGB)) |
| | 1177 | window = fglutCreateWindow('Use keys x, y, and z') |
| | 1178 | call fglutDisplayFunc(display) |
| | 1179 | call fglutKeyboardFunc(keyboard) |
| | 1180 | call fglutMainLoop |
| | 1181 | end |
| | 1182 | \end{verbatim} |
| | 1183 | } |
| | 1184 | |
| | 1185 | |
| | 1186 | \subsection{Miška in inverzna projekcija} |
| | 1187 | Za vsak pritisk gumba miške lahko dobimo poleg koordinate tudi |
| | 1188 | še stanje gumbov. Naslednji primer prikazuje risanje črte v |
| | 1189 | ravnini (x,y) s tem, da je potrebno zaslonske koordinate pretvoriti |
| | 1190 | nazaj v modelne. |
| | 1191 | {\scriptsize\begin{verbatim} |
| | 1192 | subroutine redraw |
| | 1193 | implicit none |
| | 1194 | include 'GL/fgl.h' |
| | 1195 | common /vertices/ n, vertex(2, 100) |
| | 1196 | integer n, i |
| | 1197 | real vertex |
| | 1198 | call fglClear(GL_COLOR_BUFFER_BIT) |
| | 1199 | call fglbegin(GL_LINE_STRIP) |
| | 1200 | do i = 1,n |
| | 1201 | call fglVertex2f(vertex(1, i), vertex(2, i)) |
| | 1202 | end do |
| | 1203 | call fglend |
| | 1204 | call fglFlush |
| | 1205 | end |
| | 1206 | |
| | 1207 | subroutine mouse (button, state, x, y) |
| | 1208 | implicit none |
| | 1209 | include 'GL/fglut.h' |
| | 1210 | include 'GL/fgl.h' |
| | 1211 | include 'GL/fglu.h' |
| | 1212 | common /vertices/ n, vertex(2, 100) |
| | 1213 | integer n, i |
| | 1214 | real vertex |
| | 1215 | integer button, state, x, y |
| | 1216 | integer viewport(4) |
| | 1217 | real*8 mvmatrix(16), projmatrix(16) |
| | 1218 | real*8 wx, wy, wz ! returned world x, y, z coords |
| | 1219 | real*8 px, py, pz ! picked window coortinates |
| | 1220 | integer status |
| | 1221 | |
| | 1222 | if (button .eq. GLUT_LEFT_BUTTON) then |
| | 1223 | if (state .eq. GLUT_DOWN) then |
| | 1224 | call fglGetIntegerv (GL_VIEWPORT, viewport) |
| | 1225 | call fglGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix) |
| | 1226 | call fglGetDoublev (GL_PROJECTION_MATRIX, projmatrix) |
| | 1227 | note viewport(4) is height of window in pixels |
| | 1228 | px = x |
| | 1229 | py = viewport(4) - y - 1 |
| | 1230 | pz = 0.0 |
| | 1231 | print *, ' Coordinates at cursor are ', px, py |
| | 1232 | status = fgluUnProject (px, py, pz, mvmatrix, |
| | 1233 | projmatrix, viewport, wx, wy, wz) |
| | 1234 | print *, 'World coords at z=0.0 are ', wx, wy, wz |
| | 1235 | n = n + 1 |
| | 1236 | vertex(1, n) = wx |
| | 1237 | vertex(2, n) = wy |
| | 1238 | call fglutPostRedisplay |
| | 1239 | end if |
| | 1240 | end if |
| | 1241 | end |
| | 1242 | |
| | 1243 | |
| | 1244 | program main |
| | 1245 | external redraw |
| | 1246 | external mouse |
| | 1247 | include 'GL/fglut.h' |
| | 1248 | call fglutinit |
| | 1249 | call fglutinitdisplaymode(ior(GLUT_SINGLE,GLUT_RGB)) |
| | 1250 | call fglutInitWindowSize (500, 500) |
| | 1251 | call fglutInitWindowPosition (100, 100) |
| | 1252 | window = fglutcreatewindow('Click in window') |
| | 1253 | call fglutdisplayfunc(redraw) |
| | 1254 | call fglutMouseFunc(mouse) |
| | 1255 | call fglutmainloop |
| | 1256 | end |
| | 1257 | \end{verbatim} |
| | 1258 | } |
| | 1259 | |
| | 1260 | |
| | 1261 | |
| | 1262 | \subsection{Kvaternionska rotacija} |
| | 1263 | Naslednji program prikazuje vrtenje osenčenega čajnika z |
| | 1264 | miško. V ta namem se uporabi že izdelam podprogram v jeziku C, |
| | 1265 | ki ga kličemo iz fortrana in nam omogoča kvaternionsko |
| | 1266 | rotacijo. Za vrtenje enotske krogle je potrebno zaznati tako |
| | 1267 | začetni pritisk na gumb (podprogram \emph{mouse}) kot vse |
| | 1268 | naslednje pomike miške (podprogram \emph{motion}). |
| | 1269 | |
| | 1270 | |
| | 1271 | {\scriptsize\begin{verbatim} |
| | 1272 | subroutine display |
| | 1273 | implicit none |
| | 1274 | include 'GL/fgl.h' |
| | 1275 | common /quaternion/ last(4), cur(4) |
| | 1276 | real last, cur, m(4,4) |
| | 1277 | call build_rotmatrix(m, cur) |
| | 1278 | call fglLoadIdentity |
| | 1279 | call fglMultMatrixf(m) |
| | 1280 | call fglclear(GL_COLOR_BUFFER_BIT+GL_DEPTH_BUFFER_BIT) |
| | 1281 | call fglutSolidTeapot(dble(0.5)) |
| | 1282 | call fglutSwapBuffers |
| | 1283 | end |
| | 1284 | |
| | 1285 | subroutine motion (x, y) |
| | 1286 | include 'GL/fglut.h' |
| | 1287 | include 'GL/fgl.h' |
| | 1288 | implicit none |
| | 1289 | integer x, y |
| | 1290 | common /quaternion/ last(4), cur(4) |
| | 1291 | common /mousestart/ beginx, beginy |
| | 1292 | common /viewport/ width, height |
| | 1293 | integer width, height |
| | 1294 | integer beginx, beginy |
| | 1295 | real last, cur |
| | 1296 | real p1x, p1y, p2x, p2y |
| | 1297 | p1x = (2.0*beginx - width)/width |
| | 1298 | p1y = (height - 2.0*beginy)/height |
| | 1299 | p2x = (2.0 * x - width) / width |
| | 1300 | p2y = (height - 2.0 * y) / height |
| | 1301 | call trackball(last,p1x, p1y, p2x, p2y) |
| | 1302 | call add_quats(last, cur, cur) |
| | 1303 | beginx = x |
| | 1304 | beginy = y |
| | 1305 | call fglutPostRedisplay |
| | 1306 | end |
| | 1307 | |
| | 1308 | subroutine mouse (button, state, x, y) |
| | 1309 | implicit none |
| | 1310 | integer button, state, x, y |
| | 1311 | include 'GL/fglut.h' |
| | 1312 | include 'GL/fgl.h' |
| | 1313 | include 'GL/fglu.h' |
| | 1314 | common /mousestart/ beginx, beginy |
| | 1315 | integer beginx, beginy |
| | 1316 | beginx = x |
| | 1317 | beginy = y |
| | 1318 | end |
| | 1319 | |
| | 1320 | subroutine reshape(w, h) |
| | 1321 | include 'GL/fgl.h' |
| | 1322 | integer w, h |
| | 1323 | real*8 l |
| | 1324 | common /viewport/ width, height |
| | 1325 | integer width, height |
| | 1326 | width=w |
| | 1327 | height=h |
| | 1328 | l = 1 |
| | 1329 | call fglViewPort(0, 0, w, h) |
| | 1330 | call fglMatrixMode(GL_PROJECTION) |
| | 1331 | call fglLoadIdentity |
| | 1332 | call fglOrtho(-l,l,-l,l,-l,l) |
| | 1333 | call fglMatrixMode(GL_MODELVIEW) |
| | 1334 | call fglLoadIdentity |
| | 1335 | end |
| | 1336 | |
| | 1337 | program trackballdemo |
| | 1338 | implicit none |
| | 1339 | include 'GL/fglut.h' |
| | 1340 | include 'GL/fgl.h' |
| | 1341 | include 'GL/fglu.h' |
| | 1342 | external display |
| | 1343 | external motion |
| | 1344 | external mouse |
| | 1345 | external reshape |
| | 1346 | integer window |
| | 1347 | common /quaternion/ last(4), cur(4) |
| | 1348 | real last, cur |
| | 1349 | call trackball(cur, 0.0, 0.0, 0.0, 0.0) |
| | 1350 | call fglutinit |
| | 1351 | call fglutinitdisplaymode(GLUT_DOUBLE+GLUT_RGB+GLUT_DEPTH) |
| | 1352 | window = fglutcreatewindow('Use mouse to rotate') |
| | 1353 | call fglutdisplayfunc(display) |
| | 1354 | call fglutmousefunc(mouse) |
| | 1355 | call fglutmotionfunc(motion) |
| | 1356 | call fglutreshapefunc(reshape) |
| | 1357 | call fglEnable(GL_LIGHTING) |
| | 1358 | call fglEnable(GL_LIGHT0) |
| | 1359 | call fglEnable(GL_DEPTH_TEST) |
| | 1360 | call fglutmainloop |
| | 1361 | end |
| | 1362 | \end{verbatim} |
| | 1363 | } |
| | 1364 | Predstavljeni program je sestavljen iz dveh delov. Kodo za rotacijo v |
| | 1365 | jeziku C \texttt{trackball.c} uporabimo kod zunanje podprograme. |
| | 1366 | Rezultat osenčenega model, ki je bil obrnjen z miško, prikazuje |
| | 1367 | slika \ref{fig:teapot}. |
| | 1368 | \begin{figure}[htbp] |
| | 1369 | \centering |
| | 1370 | \includegraphics[width=2.1in]{teapot} |
| | 1371 | \caption{Osenčen model čajnika} |
| | 1372 | \label{fig:teapot} |
| | 1373 | \end{figure} |
| | 1374 | |
| | 1375 | = Razvojno okolje = |
| | 1376 | Za šolske probleme smo pripravili razvojno okolje, ki omogoča |
| | 1377 | prevajanje kode v jezikih F77, C++ in C za okenski sistem Windows. |
| | 1378 | Razvojno okolje deluje v načunu ukazne vrstice in nima |
| | 1379 | priloženega integriranega vmesnika. Vsi ukazi za popravljanje |
| | 1380 | programov in prevajanje se tako podajajo v ukazni vrstici DOS okna |
| | 1381 | (\emph{Start-Programs-Command Prompt}). |
| | 1382 | |
| | 1383 | Osnova okolja je Borlandov C++ prevajalnik, ki ga lahko dobimo |
| | 1384 | zastonj. Besedilo dogovora uporabe se nahaja v datoteki |
| | 1385 | \emph{linence.txt}. Prevajanje v jeziku Fortran pa dosežemo z |
| | 1386 | pretvorbo fortranske kode v C, nato sledi prevajanje v C-ju in |
| | 1387 | povezovanje v končni program (\emph{.exe}). Končni program |
| | 1388 | lahko zaženemo z DOS okna ali z dvoklikom na izvršni program. |
| | 1389 | Poleg Novega grafičnega okna vsak GLUT program uporablja še |
| | 1390 | konzolo za morebiten vnos ali izpis z ukazoma \texttt{print *,} ali |
| | 1391 | \texttt{read *,} |
| | 1392 | |
| | 1393 | \subsection{Namestitev} |
| | 1394 | Namestitev je možna z CD-ROMA ali datoteke |
| | 1395 | \texttt{bcc-fgl-full.zip}. V slednjem primeru je potrebno paketno |
| | 1396 | datoteko odpakirati v začasen imenik, nakar sledi namestitev tako |
| | 1397 | kot iz CD-ROM-a. |
| | 1398 | |
| | 1399 | \begin{enumerate} |
| | 1400 | \item Za namestitev je na disku C potrebnih 60 MB prostora! |
| | 1401 | \item Dvolkikni na \texttt{install.bat} |
| | 1402 | \end{enumerate} |
| | 1403 | |
| | 1404 | |
| | 1405 | |
| | 1406 | \subsection{Dokumentacija} |
| | 1407 | Po namestitvi se navodila z nahajajo v imeniku |
| | 1408 | \verb|c:\bcc55\doc|. Priližena so naslednja navodila v obliki PDF: |
| | 1409 | \begin{description} |
| | 1410 | \item[redbook-*.pdf] OpenGL Programming Guide |
| | 1411 | \item[opengl-intro.pdf] Ta dokument |
| | 1412 | \item[fgl.pdf] OpenGL reference |
| | 1413 | \item[fglu.pdf] OpenGL Utility reference |
| | 1414 | \item[fglut.pdf] GLUT |
| | 1415 | \item[f2c.pdf] Prevajalnik za Fortran |
| | 1416 | \end{description} |
| | 1417 | |
| | 1418 | V datoteki \texttt{} se nahaja ta dokument. Iskanje |
| | 1419 | po dokumentaciji za OpenGL ( \texttt{fgl.pdf, fglu.pdf}), izvedemo |
| | 1420 | tako, da natipkamo npr. \texttt{fglVertex3f(} |
| | 1421 | |
| | 1422 | |
| | 1423 | \subsection{Prevajanje} |
| | 1424 | Primeri so v imeniku \verb|c:\bcc55\examples|. |
| | 1425 | |
| | 1426 | Pred prevajanjem je potrebno odpreti okno DOS |
| | 1427 | \emph{Start-Run-command-OK} in nastaviti pot do prevajalnikov z |
| | 1428 | ukazom |
| | 1429 | \begin{verbatim} |
| | 1430 | PATH=\BCC55\bin;%PATH% |
| | 1431 | \end{verbatim} |
| | 1432 | |
| | 1433 | |
| | 1434 | Pomik v imenik naredimo z ukazom |
| | 1435 | \begin{verbatim} |
| | 1436 | c: |
| | 1437 | cd \bcc55\examples |
| | 1438 | \end{verbatim} |
| | 1439 | |
| | 1440 | |
| | 1441 | Pot lahko nastavimo tudi za celoten sistem: |
| | 1442 | \emph{Start-Settings-Control Panel-System-evironment->PATH} in dopišemo |
| | 1443 | \verb|c:\bcc55\bin;| na začetku ali koncu obstoječe poti. |
| | 1444 | |
| | 1445 | Za prevajanje fortranskih datotek ne smemo uporabiti končnice \texttt{.f} |
| | 1446 | Primer prevajanja začetnega primera \texttt{line.f} za izris črte: |
| | 1447 | \begin{verbatim} |
| | 1448 | f77 line |
| | 1449 | \end{verbatim} |
| | 1450 | Za najzahtevnejši primer čajnika uporabimo hkratno prevajanje |
| | 1451 | fortranskega in C programa, ki oba med seboj tudi poveže v program |
| | 1452 | \texttt{tblight.exe} |
| | 1453 | \begin{verbatim} |
| | 1454 | f77 tblight trackball.c |
| | 1455 | \end{verbatim} |
| | 1456 | če imamo več modulov, potem lahko že prevedene podprograme |
| | 1457 | \texttt{.obj} le povežemo v izvršno kodo. Primer: |
| | 1458 | \begin{verbatim} |
| | 1459 | f77 tblight trackball.obj |
| | 1460 | \end{verbatim} |
| | 1461 | |
| | 1462 | Prevajanje in povezovanje v jeziku C se izvede z klicem prevajalnika |
| | 1463 | \emph{bcc32}. Primer: |
| | 1464 | \begin{verbatim} |
| | 1465 | bcc32 teapot.c |
| | 1466 | \end{verbatim} |
| | 1467 | |
| | 1468 | |
| | 1469 | \subsection{Urejanje fortranskih in C programov:} |
| | 1470 | Na urejanje kode lahko uporabimo DOS-ov urejevalnik EDIT ali Windows |
| | 1471 | notepad. Oba imata svoje slabosti; EDIT ima težave z daljšimi |
| | 1472 | imeni datotek, NOTEPAD pa nima prikaza trenutne vrstice in ob prvem |
| | 1473 | shranjevanju datoteke lepi končnico \texttt{.txt}, tako da moramo |
| | 1474 | datoteko kasneje preimenovati v \texttt{.f}. Kljub slabostim sta oba |
| | 1475 | urejevalnika primerna za šolske probleme. |
| | 1476 | |
| | 1477 | V trenutnem imeniku DOS okna odtipkamo izbrani ukaz: |
| | 1478 | \begin{verbatim} |
| | 1479 | notepad teapot.f |
| | 1480 | edit teapot.f |
| | 1481 | \end{verbatim} |
| | 1482 | |