| 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 | |