63
ELEKTROTEHNIČKI FAKULTET BEOGRAD Jezik za senčenje grafičkog paketa OpenGL 2.0 Nikola Stojiljković Beograd 26.09.2005

ELEKTROTEHNIČKI FAKULTET BEOGRADrti.etf.bg.ac.rs/rti/ri5rg/materijali/OpenGLShadingJezik.pdf3 1. Uvod OpenGL Shading jezik je proceduralni jezik za senčenje grafi čkog paketa OpenGL,

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

  • E L E K T ROT EHN I Č K I   F AKU L T E T B EO G RA D 

    Jezik za senčenje grafičkog paketa OpenGL 2.0 

    Nikola Stojiljković 

    Beograd 26.09.2005

  • Sadržaj 

    1. UVOD ............................................................................................... 3 

    2. TRADICIONALAN OPENGL ................................................................ 4 

    2.1  Revizije i ekstenzije OpenGLa ......................................................................... 4 

    2.2  Model izvršavanja............................................................................................. 6 

    2.3  Pipeline ............................................................................................................7 2.3.1 Iscrtavanje geometrijskih primitiva ...................................................................... 7 2.3.2 Iscrtavanje slika ................................................................................................ 9 

    3. OSNOVE OPENGL SHADERA........................................................... 10 

    3.1 Uvod u OpenGL Shading jezik............................................................................. 10 

    3.2 Zašto pisati shadere ..........................................................................................10 

    3.3 Programabilni procesori ..................................................................................... 11 3.3.1 Verteks procesori............................................................................................. 11 3.3.2 Fragment procesori .......................................................................................... 13 

    3.4 Pregled jezika..................................................................................................... 15 3.4.1 Dodaci u odnosu na jezik C ............................................................................... 15 3.4.2 Dodaci iz jezika C++........................................................................................ 16 3.4.3 Svojstva jezika C koja nisu podržana.................................................................. 16 3.4.4 Ostale razlike .................................................................................................. 16 

    3.5 Model drajvera ................................................................................................... 17 

    3.6 Proširenja OpenGL APIja................................................................................... 18 

    3.7 Primer inicijalizacije i korišćenja shadera u OpenGL programu......................... 21 

    4. SINTAKSA OPENGL SHADING JEZIKA............................................. 25 

    4.1 Jednostavan primer............................................................................................25 

    4.2 Tipovi podataka..................................................................................................26 4.2.1 Skalarni tipovi ................................................................................................. 26 4.2.2 Vektori ........................................................................................................... 26 4.2.3 Matrice ........................................................................................................... 27 4.2.4 Sempleri......................................................................................................... 27 4.2.5 Strukture........................................................................................................ 28 4.2.6 Nizovi............................................................................................................. 28 4.2.7 Void ............................................................................................................... 29 4.2.8 Deklaracije i opseg važenja............................................................................... 29 4.2.9 Automatska konverzija tipova............................................................................ 30 

    4.3 Inicijalizatori i konstruktori................................................................................30 

    4.4 Konverzija tipova................................................................................................ 31

  • 4.5 Kvalifikatori i interfejs ka shaderima ................................................................32 4.5.1 Atributske promenljive ..................................................................................... 32 4.5.2 Uniformne promenljive ..................................................................................... 33 4.5.3 Interpolirajuće promenljive ............................................................................... 33 4.5.4 Konstante ....................................................................................................... 33 4.5.5 Promenljive bez kvalifikatora............................................................................. 33 

    4.6 Kontrola toka......................................................................................................34 4.6.1 Funkcije.......................................................................................................... 34 4.6.2 Prenos parametara........................................................................................... 34 4.6.3 Ugrađene funkcije............................................................................................ 35 

    4.7 Operatori ............................................................................................................36 4.7.1 Indeksiranje.................................................................................................... 36 4.7.2 Operacije po komponentama složenih tipova ....................................................... 37 

    4.8 Preprocesor i direktive ....................................................................................... 38 

    4.9 Preprocesorski izrazi ..........................................................................................39 

    4.10 Rukovanje greškama ........................................................................................40 

    5. SPREGA PROGRAMABILNIH PROCESORA SA PIPELINEOM............ 41 

    5.1 Verteks procesor ................................................................................................41 5.1.1 Atributivne promenljive .................................................................................... 42 5.1.2 Uniformne promenljive ..................................................................................... 44 5.1.3 Specijalne izlazne promenljive........................................................................... 45 5.1.4 Interpolirajuće promenljive ............................................................................... 45 

    5.2 Fragment procesor ............................................................................................. 46 5.2.1 Interpolirajuće promenljive ............................................................................... 47 5.2.2 Uniformne promenljive ..................................................................................... 47 5.2.3 Specijalne ulazne promenljive ........................................................................... 47 5.2.4 Specijalne izlazne promenljive........................................................................... 48 5.2.5 Ugrađene konstante ......................................................................................... 48 

    6. PRIMERI......................................................................................... 50 

    6.1 Spljošteni modeli................................................................................................50 

    6.2 Toon shader........................................................................................................51 

    6.3 Shaderi i teksture..............................................................................................53 

    6.4 Proceduralne teksture ........................................................................................55 

    7. ZAKLJUČAK .................................................................................... 61 

    8. LITERATURA................................................................................... 62

  • 1. Uvod 

    OpenGL Shading jezik je proceduralni jezik za senčenje grafičkog paketa OpenGL, vodećeg platformskinezavisnog APIja (eng. Applications Programming Interface). Pojava ovog jezika je najveći napredak koji je OpenGL doživeo poslednjih godina, jer omogućava programerima da  preuzmu  kontrolu  nad  najvažnijim  stupnjevima  u  grafičkoj  obradi.  Specifikacija  prve verzije OpenGL APIja se pojavila daleke 1992e. Do sada je bilo 6 revizija ovog standarda, a trenutna verzija je 2.0. Ovaj rad će se bazirati na verziji 2.0, sa osvrtom na verziju 1.5. 

    Programi  pisani  u  OpenGL  Shading  jeziku  se  nazivaju  shaderi.  Ovaj  rad  je  namenjem OpenGL  programerima  i  predstavlja  vodič  za  pisanje  shadera,  koje  grafički  čip  može  da izvršava u određenim stupnjevima grafičke obrade tj. pipelinea. Delom tutorijal, a delom  i referenca,  rad  objašnjava  prelaz  sa  grafičkog  hardvera  fiksne  funkcionalnosti  na  novi  sa programabilnim  mogućnostima.  Prvo  je  objašnjen  mehanizam  proširivanja  mogućnosti OpenGLa i protočna obrada u ranijim verzijama OpenGLa koje ne koriste shadere. Potom su opisane novine u pipelineu koje donosi novi hardver, kao i funkcije OpenGL APIja koje služe kao podrška OpenGL Shading jeziku. Dat je i primer inicijalizacije i korišćenja shadera u OpenGL  aplikaciji. Dalje  sledi  detaljna  specifikacija  sintakse  jezika  i  opis mehanizama za spregu  shadera  sa  OpenGL  aplikacijom.  Za  kraj  je  ostavljena  demonstracija  mogućnosti OpenGL Shading jezika kroz nekoliko jednostavnih primera.

  • 2. Tradicionalan OpenGL 

    OpenGL  je  platformskinezavistan  API  (eng.  Applications  Programming  Interface)  i predstavlja  softverski  interfejs ka  grafičkom hardveru.  Prva  specifikacija OpenGLa,  verzija 1.0, je napisana 1992., a prve implementacije su se pojavile 1993. Do sada je bilo 6 revizija ovog standarda, a trenutna verzija je 2.0. Ovaj rad će se bazirati na verziji 2.0, sa osvrtom na verziju 1.5. Podrazumeva se da je čitalac upoznat bar sa osnovama rada OpenGLa 1.0. 

    2.1  Revizije i ekstenzije OpenGLa 

    Veliki  deo  funkcionalnosti  OpenGLa  se  bazira  na  ekstenzijama.  Postoji  jasno  definisan mehanizam proširivanja,  i  proizvođači grafikih  čipova mogu  sami definisati  i  implementirati ekstenzije  koje  omogućuju  korišćenje  novih  hardverskih mogućnosti.  OpenGL  je  prvobitno definisan kao konačan automat, tj. mašina stanja sa fiksnim mogućnostima i jedini način za proširenje  OpenGLa  je  kroz  mehanizam  ekstenzija.  Ne  postoji  način  da  se  prošire mogućnosti  OpenGLa  direktno  iz  aplikacije,  ali  to  mogu  uraditi  proizvođači  čipova,  tzv. implementatori OpenGLa. Ekstenzije koje su podržane od strane više od jednog proizvođača imaju prefiks EXT, dok one koje su detaljno pregledane i odobrene od strane ARBa (OpenGL Architectural  Review  Board    http://www.opengl.org/about/arb/)  imaju  prefiks  ARB.  S obzirom da je ARB nezavistan konzorcijum koji je osnovan 1992e radi upravljanja razvojem OpenGLa,  ekstenzije  sa  prefiksom  ARB  su  najpoželjniji  način  za  iskorišćavanje  novih mogućnosti hardvera. Ekstenzije koje su dobile prefiks ARB u narednim revizijama OpenGLa ulaze  direktno  u  specifikaciju  kao  standard.  Detaljan  spisak  ekstenzija  se  može  naći  na http://oss.sgi.com/projects/oglsample/registry. 

    Spisak podržanih ekstenzija na nekoj konkretnoj implementaciji se može dobiti pozivanjem funkcije:

    glGetString(GL_EXTENSIONS); 

    koja daje listu svih podržanih ekstenzija u jednom stringu. Ova lista najčešće sadrži  i preko 100 ekstenzija (od oko 300 objavljenih), tako da je programerima veoma teško da izađu na kraj sa svim podržanim ekstenzijama. 

    Da bi se inicijalizovala neka ekstenzija potrebno je prvo proveriti da li je ona podržana na konkretnoj implementaciji OpenGLa: 

    GLubyte *ekstenzije = glGetString(GL_EXTENSIONS); 

    if (strstr((char*)ekstenzije, „ime_ekstenzije“) != NULL) ... 

    Za korišćenje ekstenzija potrebno je koristiti i sledeća dva header fajla kako bi se uopšte moglo pristupiti ekstenzijama: 

    #include  

    #include 

  • Kad  je  utvrđeno  da  je  tražena  ekstenzija  podržana  na  implementaciji,  na  red  dolazi omogućavanje  pristupa  funkcijama  ekstenzije.  Kako    ove  funkcije  nisu  deo  standardne OpenGL  biblioteke,  u  Windows  operativnim  sistemima  se  mora  koristiti  funkcija wglGetProcAddress da bi se dobio pointer ka željenoj funkciji: 

    PROC wglGetProcAddress(LPSTR lpszProc); 

    Na  primer,  za  pristupanje  funkciji  glActiveTextureARB  koja  je  definisana  u  ekstenziji GL_ARB_multitexture treba korisiti sledeći kod: 

    PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL; 

    glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) 

    wglGetProcAddress(„glActiveTextureARB“); 

    Ova procedura se mora ponoviti za sve funkcije koje se koriste. 

    Ovaj  rad  se  bazira na OpenGLu  2.0 u  kojem  je  podrška  za OpenGL Shading  jezik  deo standarda.  Zašto  onda  obrađujemo  ekstenzije?  Zato  što  se  zapravo  svaka  nova  verzija OpenGLa može  tretirati  kao ekstenzija kad  je u pitanju pristup novim  funkcijama OpenGL APIja.  Jedina  razlika  u  odnosu  na  korišćenje  pravih  ekstenzija  je  što  se  verzija  koja  je podržana od strane implementacije OpenGLa ispituje sa: 

    glGetString(GL_VERSION); 

    dok je proces „inicijalizacije“ funkcija isti kao u slučaju korišćenja ekstenzija. Jasno je da je ovo mukotrpan  i komplikovan posao,  jer samo podrška OpenGL APIju za OpenGL Shading jezik sadrži preko 30  funkcija, a  i  treba  imati na umu da se u Windows  i Unix operativnim sistemima koriste različiti mehanizmi za nalaženje pokazivača na funkcije (na Unix sistemima nije  dostupna  funkcija  wglGetProcAddress  već  se  mora  koristiti  glXGetProcAddressARB). 

    Zbog toga se često koriste pomoćne biblioteke koje olakšavaju rad sa ekstenzijama. U takve spadaju:

    • GLEW (http://glew.sourceforge.net/)

    • OglExt (http://www.julius.caesar.de/oglext/)

    • GLsdk (http://oss.sgi.com/projects/oglsample/sdk.html) 

    Najpopularnija biblioteka ove namene je GLEW (OpenGL Extension Wrangler Library) pa će samo  ona  biti  opisana  u  daljem  tekstu.  Za  korišćenje GLEW biblioteke  je  potrebno  korisiti header  fajl  glew.h,  kao  i  biblioteku  glew32.lib  (ili  glew32s.lib  u  slučaju  statičkog 

    linkovanja).  Prvo  treba  na  standardan  način  inicijalizovati  OpenGL,  a  onda  treba  pozvati funkciju glewInit koja inicijalizuje funkcije iz svih podržanih ekstenzija i verzija OpenGLa. Ako glewInit vrati GLEW_OK  inicijalizacija  je uspešna  i može se nastaviti sa  izvršavanjem 

    programa. Primer: 

    #pragma comment( lib, "glew32s.lib") 

    #define GLEW_STATIC 1  // ovaj red ne stoji kod linkovanja glew32.lib 

    // biblioteke, već samo za glew32s.lib

  • #include  

    ... 

    GLenum err = glewInit(); 

    if (GLEW_OK != err) 

    fprintf(stderr, "Greška: %s\n", glewGetErrorString(err)); 

    ... 

    Pored već opisanog načina za utvrđivanje podržanih ekstenzija i verzija OpenGLa, mogu se koristiti i globalne promenljive iz GLEW biblioteke. Za ekstenzije se koriste promenljive sa imenom  GLEW_{ime_ekstenzije},  a  za  verzije  OpenGLa  se  koriste  promenljive GLEW_VERSION_{verzija}. Primer: 

    if (GLEW_ARB_vertex_program) 

    /* Implementacija podržava ARB_vertex_program ekstenziju */ 

    if (GLEW_VERSION_2_0) 

    /* OpenGL 2.0 je podržan! */ 

    2.2  Model izvršavanja 

    OpenGL  je dizajniran kao mašina stanja, gde stanje,  između ostalog, određuje način na koji  će  se  primitive  prikazati  (renderovati) na  izlazu. OpenGL API  se  bazira na  iscrtavanju primitiva (kao što su tačke, linije i poligoni) i slika na bafer okvira (eng. frame buffer). Bafer okvira,  koji  je  sastavni  deo  stanja  OpenGLa,  se  sastoji  od:  prednjeg  bafera  (eng.  front buffer),  bafera  dubine  (eng.  depth  buffer),  bafera  šablona  (eng.  stencil  buffer),  bafera akumulacije (eng. accumulation buffer) i multisample bafera. 

    Model  izvršavanja  je  klijentserver.  Aplikacija  (klijent)  izdaje  OpenGL  komandu  serveru koji zapravo predstavlja implementaciju. Klijent i server ne moraju biti na istom računaru, a veći deo promenljivih stanja se nalazi na serveru. 

    Komande  se  izvršavaju  redosledom  kojim  su  došle,  iako  se  izvršavanje  komandi  može odložiti  (komande se stavljaju u red).  Izvršavanje van naznačenog redosleda (outoforder) nije dozvoljeno, što znači da se neće početi sa iscrtavanjem primitive dok prethodna u redu nije završena.

  • 2.3  Pipeline 

    S obzirom da se operacije OpenGLa izvršavaju u određenom redosledu, rad OpenGLa se može opisati i pipelineom (protočnom obradom): 

    Obrada fragmenta 

    Operacije sa verteksima 

    Aplikativna memorija 

    Pakovanje piksela 

    Otpakivanje piksela 

    Transfer piksela 

    Sklapanje primitiva 

    Odsecanje i projekcije 

    Operacije sa baferom okvira 

    Operacije sa fragmentima 

    Bafer okvira 

    Grupe piksela Verteksi 

    Teksture 

    Fragmenti 

    (Pikseli) 

    (Geometrija) 

    Teksturna memorija 

    Rasterizacija 

    (Geometrija) 

    (Pikseli) 

    Kontrola čitanja 

    16  10 

    8 5 4 3 

    13 12 

    17 

    14 

    11 

    1 2 

    15 

    Slika 2.1. Pipeline fiksne funkcionalnosti OpenGLa 

    Na slici 2.1 je prikazan pipeline fiksne funkcionalnosti OpenGLa koji je definisan do verzije 1.5. Treba naglasiti da redosled izvršavanja operacija ne mora biti po brojevima prikazanim na  slici,  već  je  bitno  da  izlaz  bude  konzistentan  sa  specifikacijama  OpenGLa.  Mnoge implementacije  zapravo  nemaju  ni  približno  ovakvu  strukturu  (zbog  iskorišćavanja  raznih optimizacija), ali je njihov rezultat identičan pipelineu sa slike, tako da će se dalja analiza i opis bazirati na ovom dijagramu. 

    2.3.1 Iscrtavanje geometrijskih primitiva 

    Sa  slike  2.1  se  vidi  da  iscrtavanje  geometrijskih  primitiva  (tačaka,  linija  i  poligona) započinje u  aplikaciji  i memoriji  koju  ona kontroliše  (1).  Postoje  tri  načina  da  se  podaci  o ovim primitivama pošalju OpenGLu:

    • teme  po  teme,  sa  korišćenjem  glBegin  i  glEnd  za  početak  i  kraj  iscrtavanja  jedne 

    primitive.  Jedini  atributi koji se mogu pridružiti koordinatama na ovaj način su  samo oni  definisani  u  OpenGL  specifikaciji:  pozicija,  boja,  normala,  koordinata  teksture, sekundarna boja, podaci o ivicama i magli. U OpenGL terminologiji teme se naziva još i verteks (eng. vertex), pa će se u daljem tekstu koristiti taj termin.

    • preko  niza  verteksa  (eng.  vertex  array),  na  ovaj  način  aplikacija  smešta  atribute verteksa u odgovarajući niz. Ovo je poželjan način iscrtavanja kompleksnih modela jer

  • se izbegavaju višestruki pozivi glBegin i glEnd funkcija, i samim tim se dobija i brži i 

    manji  program.  Funkcije  koje  se  koriste  za  ovaj  način  slanja  podataka  su glDrawArrays,  glMultiDrawArrays,  glDrawElements,  glMultiDrawElements, glDrawRangeElements  i  glInterleavedArrays.  Počev  od  verzije  1.5  OpenGLa,  ovi nizovi  se  mogu  smestiti  i  na  serverskoj  strani  korišćenjem  glBindBuffer, glBufferData i glBufferSubData funkcija.

    • smeštanjem poziva funkcija iz prva dva metoda u tzv. listu za prikaz (eng. display list), koja je zapravo interna struktura podataka OpenGLa koja se nalazi na strani servera. Konkretni pozivi će se izvršiti čim se pozovu funkcije glCallList ili glCallLists. Liste 

    za prikaz, osim funkcija za iscrtavanje primitiva, mogu sadržati i funkcije za promenu stanja. 

    Prva dva načina predstavljaju tzv. neodložan mod  (eng.  immediate mode),  jer se primitive iscrtavaju čim su funkcije pozvane. Treći način, mod liste za prikaz, pruža bolje performanse jer se pruža mogućnost OpenGL implementaciji da bolje sortira pozive funkcijama kako bi se što bolje iskoristile mogućnosti hardvera. Takođe, moguće je i smeštanje ovih sortiranih lista direktno u memoriju grafičke kartice čime se dodatno dobija na brzini. Naravno, ova ubrzanja su vidljiva samo ako se jedna lista za prikaz poziva više puta, jer se na samo njeno kreiranje gubi određeno vreme. 

    Koji  god  od  prethodno  opisanih  načina  je  korišćen,  podaci  o  primitivama  dolaze  u  prvi stupanj  OpenGL  pipelinea  –  stupanj  obrade  verteksa  (2).  Ovde  se  pozicije  i  normale transformišu korišćenjem matrica projekcija, generišu se koordinate tekstura, primenjuje se računanje  osvetljenja  kako bi  se  promenila  boja  verteksa  i  računa  se veličina  tačaka. Ove operacije, kao i njihov redosled su striktno definisane standardom. S obzirom da su najbitnije operacije u ovom stupnju transformacija koordinata i primena osvetljenja, on se još naziva i Transformation and Lighting – T&L. Aplikacija nema nikakvog uticaja na algoritam rada ovog stupnja osim što može promeniti neke promenljive stanja OpenGLa koje utiču na obradu. 

    Posle  stupnja  obrade verteksa  su  poznati  atributi  verteksa,  i  ovi  podaci ulaze u  stupanj sklapanja primitiva (3). Od verteksa se formiraju tačke, linije, trouglovi itd. Ove primitive se dalje  prosleđuju  sledećem  stupnju  u  pipelineu  –  obradi  primitiva  (4).  Stupanj  obrade primitiva  se  zapravo  sastoji  od  više  podkoraka:  odsecanje,  projekcija  i  odabiranje  strana (eng. culling). 

    Ovako obrađene primitive se dalje prosleđuju stupnju za rasterizaciju (5) gde se primitiva dekomponuje na  jedinice koje odgovaraju pikselima u odredišnom baferu okvira. Svaka od ovih  jedinica  se  u  OpenGL  terminologiji  naziva  fragment.  Fragment  sadrži  informaciju  o poziciji na ekranu, dubini, kao i podatke o boji, teksturnim koordinatama itd. Ove informacije se dobijaju interpolacijom atributa verteksa koji čine primitivu. 

    Sledeći stupanj, kome se prosleđuju  fragmenti,  je  obrada  fragmenata  (6). Najbitniji  deo ovog stupnja je primena teksture na osnovu koordinata teksture i same teksture iz memorije za teksture (7). Pored ove operacije, u ovom stupnju se računa i magla i boja (kombinuje se primarna i sekundarna boja). Fragmenti se dalje šalju u stupanj sa operacijama po fragmentu (8), gde se vrše depth, stencil, alpha, scissor  i pixel ownership testovi. Posle ovih operacija fragmenti menjaju odredišni bafer okvira u stupnju obrade bafera okvira (9).

  • Krajnji  efekat  ovih  stupnjeva  je  konvertovanje  grafičkih  primitiva  u  piksele  na  baferu okvira. 

    2.3.2 Iscrtavanje slika 

    Pored podrške za isrtavanje geometrijskih primitiva, OpenGL može da iscrtava i slike koje se  nazivaju  pravougaonici  piksela  (eng.  pixel  rectangles).  Vrednosti  koje  definišu pravougaonik  piksela  se  nalaze  u  aplikativnoj memoriji.  Za  iscrtavanje  na  bafer  okvira  se koriste funkcije glDrawPixels  i glBitmap, a za upis slika u memoriju za teksture se koriste funkcije  glTexImage  i  glTexSubImage.  OpenGL  može  čitati  sliku  u  različitim  formatima. 

    Parametri o načinu na koji je slika smeštena u aplikativnoj memoriji (11) se zadaju funkcijom glPixelStore.  Proces  čitanja  slike  iz  aplikativne  memorije  i  kreiranja  koherentnog  niza 

    piksela (pogodnog za dalju obradu u OpenGLu) se naziva otpakivanje piksela (12). 

    U sledećem stupnju, transfera piksela (13), nad pikselima se vrše razne operacije kad god se  slika:  kopira  iz  aplikacije  u  OpenGL  (glDrawPixels,  glTexImage,  glTexSubImage), kopira  iz  OpenGLa  u  aplikaciju  (glReadPixels),  i  kopira  u  okviru  samog  OpenGLa (glCopyPixels,  glCopyTexImage,  glCopyTexSubImage).  Parametri  svih  ovih  kopiranja  se mogu promeniti  funkcijom glPixelTransfer. Posle  stupnja transfera piksela se vrši proces 

    rasterizacije na veoma sličan način kao za 3D geometriju (14). Rasterizacija uzima u obzir trenutnu  poziciju  rastera  (koja  se  podešava  funkcijama  glRasterPos  i  glWindowPos)  i trenutni  faktor  uveličavanja  (koji  se  podešava  sa  funkcijom  glPixelZoom).  Pošto  su  od 

    piksela sa  slike kreirani  fragmenti, dalja obrada ovih  fragmenata se nastavlja na  isti  način kao  i  za  geometrijske  primitive.  Pikseli  koji  su  kopirani  pomoću  funkcija  glTexImage  ili glTexSubImage ne idu u stupanj rasterizacije već se direktno kopiraju u specijalnu memoriju 

    za teksture (15) koja se nalazi na samoj grafičkoj karti. 

    U  slučaju  čitanja  piksela  iz  bafera  okvira  (sa  glReadPixels),  kopiranja  piksela  iz  bafer okvira u bafer okvira (sa glCopyPixels), ili kopiranja iz bafera okvira u memoriju za teksture (sa glCopyTexImage  ili  glCopyTexSubImage),  pikseli  koji  se  čitaju  iz  bafera  okvira  prolaze kroz  stupanj  kontrole  čitanja  (16).  Ovaj  stupanj  se  podešava  funkcijom  glReadBuffer. 

    Pročitani pikseli  dalje  idu u stupanj  transfera piksela  (13), odakle  se dalje, u  zavisnosti  od pozvane funkcije, mogu proslediti stupnju za rasterizaciju (radi kopiranja u bafer okvira),  ili direktno memoriji  za  teksture,  ili  stupnju  pakovanja  piksela  (17)  u  slučaju  kad  se  pikseli kopiraju u aplikativnu memoriju. Ovaj stupanj je obrnut stupnju otpakivanja piksela (12), a pikseli se kopiraju u aplikativu memoriju na način koji je definisan funkcijom glPixelStore.

  • 10 

    3. Osnove OpenGL Shadera 

    3.1 Uvod u OpenGL Shading jezik 

    OpenGL  Shading  jezik  (ili  skraćeno GLSL)  i  odgovarajuća ARB  ekstenzija  su  odobreni  u junu  2003.  Ovaj  jezik  je  postao  deo  standarda  OpenGLa  u  verziji  2.0.  Trend  u  razvoju grafičkog hardvera je da se fiksne funkcionalnosti zamene sa programabilnim u oblastima gde kompleksnost  naglo  raste.  To  je  slučaj  sa  obradom  verteksa  i  obradom  fragmenata.  Sa OpenGL Shading jezikom, stupnji obrade verteksa i fragmenata u pipelineu su zamenjeni sa programabilnim stupnjevima, tzv. programabilnim procesorima. Program pisan u ovom jeziku je predviđen da se izvršava na jednom od tih OpenGL programabilnih procesora i naziva se shader. Postoje dva tipa ovih programa: verteks shaderi i fragment shaderi. OpenGL sadrži mehanizam da shadere kompajlira i linkuje u jedan izvršni program. 

    OpenGL  Shading  jezik  je  baziran  na  programskom  jeziku  C.  Sadrži  bogat  set  tipova podataka, uključujući vektorske i matrične tipove. Podržani su i neki koncepti iz C++ jezika, kao  što  je  preklapanje  imena  funkcija  na  osnovu  tipa  argumenata,  i  mogućnost  da  se promenljive deklarišu na mestu na kome su prvi put potrebne a ne na početku bloka. Jezik podržava  petlje,  pozive  funkcijama  i  kondicionalne  izraze.  Sadrži  i  mnoštvo  ugrađenih funkcija koje se često koriste. Ukratko:

    • OpenGL Shading jezik je proceduralni jezik visokog nivoa

    • Isti jezik, sa malim izmenama, se koristi i za verteks i za fragment shadere

    • Zasnovan je na C i C++ jezicima

    • Podržava vektorske i matrične operacije

    • Striktniji je po pitanju tipova od C i C++ jezika

    • Koristi kvalifikatore tipa za ulaz i izlaz

    • Nema praktičnog limita dužini shadera. 

    3.2 Zašto pisati shadere 

    U  pipelineu  fiksne  funkcionalnosti  se  nijedna  od  osnovnih  operacija,  niti  redosled  tih operacija  ne  može  promeniti  kroz  OpenGL  API.  Ovo  dovodi  do  ograničavanja  mogućnosti OpenGLa. Jedan primer bi bio osvetljenje i činjenica da OpenGL računa svetla po verteksu, a ne  po  fragmentu  što  dovodi  do  neprirodnog  osvetljenja  kod  modela  sa  malim  brojem poligona. Svrha OpenGL shading jezika i APIja koji ga podržava je da se omogući aplikaciji da  definiše  obradu  koja  se  izvršava  u  ključnim  stupnjevima  OpenGL  pipelinea.  Ovo  daje mogućnost aplikaciji da iskoristi hardver do maksimuma i da postigne viši nivo u realističnosti slike na izlazu.

  • 11 

    3.3 Programabilni procesori 

    Najveća  promena  koju  je  OpenGL  doživeo  od  nastajanja  je  uvođenje  programabilnog verteks i fragment procesora. Ako se OpenGL shading jezika aktivira, stupnji obrade verteksa i fragmenata iz pipelinea fiksne funkcionalnosti se isključuju. Slika 3.1 prikazuje kako u tom slučaju  izgleda  OpenGL  pipeline.  Stupnji  obrade  verteksa  i  fragmenata  su  zamenjeni programabilnim procesorima. Ostali delovi pipelinea ostaju isti. 

    Slika 3.1. Pipeline OpenGLa sa uključenim programabilnim procesorima 

    3.3.1 Verteks procesori 

    Verteks procesor  je  programabilna  jedinica koja  vrši  operacije nad ulaznim verteksima  i podacima koji su njima pridruženi. Predviđeno je da verteks procesor izvršava tradicionalne operacije kao što su:

    • Transformacija verteksa

    • Transformacija normala i normalizacija

    • Generisanje teksturnih koordinata

    • Transformacija teksturnih koordinata

    • Osvetljenje

    • Računanje boje 

    Zbog svoje opšte svrhe, ovaj procesor se može koristiti  i za mnoštvo drugih izračunavanja. Shaderi koji su predviđeni da se izvršavaju na ovom procesoru se nazivaju verteks shaderi. Verteks shader koji vrši neka od izračunavanja sa prethodne liste je dužan da implementira i

  • 12 

    sve druge potrebne kalkulacije, tj. ne može se koristiti npr. transformacija verteksa iz fiksnog pipelinea  i  implementirati  samo  osvetljenje  u  shaderu.  Jedine  operacije  koje  zadržavaju fiksnu  funkcionalnost  i  nalaze  se  između  verteks  i  fragment  procesora  su:  deljenje perspektive i preslikavanje prikaznog prozora, sklapanje primitiva, odsecanje prostora (eng. frustum  clipping),  odabir  zadnje  strane  (eng.  backface  culling),  selekcija  osvetljenja  po stranama primitive, mod  i ofset poligona,  izbor ravnog senčenja  ili senčenja sa prelazom,  i određivanje ranga dubine. 

    Na slici 3.2 su prikazane vrednosti koje se koriste na ulazu verteks procesora i one koje su proizvod obrade. Kvalifikatori tipa su definisani kao deo OpenGL shading jezika i koriste se za ulaz i izlaz iz procesora. 

    Slika 3.2. Ulaz i izlaz verteks procesora 

    Postoji tri tipa promenljivih koje koriste verteks shaderi:

    • atributske promenljive

    • uniformne promenljive

    • interpolirajuće promenljive 

    Promenljive sva tri tipa mogu biti već ugrađene u jezik ili definisane od strane korisnika. Za korisnički definisane promenljive se koristi još i termin generičke. 

    Atributske  promenljive  sadrže  vrednosti  koje  aplikacija  često  šalje  procesoru.  Aplikacija može slati ove vrednosti između poziva glBegin i glEnd, kao i preko niza verteksa, tako da 

    se  ove  promenljive  mogu  menjati  za  svaki  verteks.  Ugrađene  atributske  promenljive  su između  ostalih  boja,  normale,  koordinate  tekstura,  kooridnate verteksa.  Pozivi  standardnih funkcija  kao  što  su  glColor,  glNormal,  glVertex  itd.  se  mogu  koristiti  da  se  postave 

    ugrađene  atributske  promenljive.  Verteks  shader  pristupa  ovim  promenljivama  preko

  • 13 

    ugrađenih  imena  gl_Color,  gl_Normal,  gl_Vertex...  Za  korisnički  definisane  atributske 

    promenljive je dodat novi  interfejs koji omogućava da se one proslede verteks procesoru iz aplikacije.  Ove  promenljive  su  referencirane  indeksom  koji  ide  od  0  do  proizvljnog  broja definisanog  implementacijom.  Komanda  glVertexAttrib  (glVertexAttribARB  za  OpenGL 

    1.5)  šalje  OpenGLu  atributske  promenljive  tako  što  specificira  indeks  niza  i  vrednost promenljive.  Još  jedna nova  komanda, glBindAttribLocation  (glBindAttribLocationARB 

    za OpenGL 1.5) služi da se svakom indeksu u nizu dodeli ime koje će koristiti verteks shader da bi referencirao dati atribut. 

    Uniformne  promenljive  se  koriste  za  prosleđivanje  podataka  iz  aplikacije  ka  verteks  ili fragment procesoru. Ovi podaci se obično retko menjaju, i promena ovih promenljivih se ne može  izvršiti  između  poziva  funkcija  glBegin  i glEnd.  Najčeće  se  koriste  za  prosleđivanje 

    nekih  opštih  parametara,  mada  se  mogu  menjati  najčešće  po  jednoj  primitivi.  Verteks  i fragment shaderi mogu pristupiti OpenGL stanju kroz ugrađene uniformne promenljive (koje sadrže  rezervisan  prefiks  „gl_“).  Funkcija  glGetUniformLocation (glGetUniformLocationARB za OpenGL 1.5) se može koristiti da se dobije lokacija korisnički 

    definisane  uniformne  promenljive  koja  je  definisana  kao  deo  shadera.  Podaci  se  mogu upisati  u  ovu  lokaciju  korišćenjem  funkcije  glUniform  (glUniformARB  za  OpenGL  1.5). 

    Postoje razne varijante ove funkcije koje pokrivaju različite tipove podataka. 

    Još jedna novina u pipelineu je da i verteks i fragment procesori mogu pristupiti memoriji sa teksturama. 

    Konceptualno,  verteks  procesor  radi  na  jednompojednom verteksu  (ali  implementacija može  imati  više  procesora  koji  rade  u  paraleli).  Ovo  znači  da  se  verteks  shader  izvršava jednom  za  svaki  verteks.  Dizajn  verteks  procesora  je  fokusiran  na  funkcionalnosti  koja  je potrebna  za  transformaciju  i  osvetljenje  jednog  verteksa.  Izlaz  iz  verteks  shadera  je realizovana  korišćenjem  specijalnih  promenljivih.  Verteks  shaderi  moraju  izračunati homogenu poziciju verteksa i smestiti je u promenljivu gl_Position. 

    Interpolirajuće promenljive se prosleđuju iz verteks ka fragment procesoru. Podržane su i ugrađene  i  korisnički  definisane  interpolirajuće  promenljive.  Nazivaju  se  interpolirajuće  jer vrednosti mogu biti različite od verteksa do verteksa i koristi se interpolacija da bi fragment procesor dobio vrednost za svaki fragment posebno. Ugrađene interpolirajuće promenljive su one  koje  su  definisane  u  standardnom OpenGLu  za  boju  i  koordinate  tekstura.  Korisnički definisane interpolirajuće promenljive se mogu koristiti za bilo koje vrednosti koje zahtevaju interpolaciju: boje, normale, koordinate modela, koordinate tekstura itd. 

    Verteks  shader  može  definisati  i  računati  više  interpolirajućih  promenljivih  nego  što fragment  shader  zaista  prima.  Ovako  se  jedino  gubi  na  performansama,  ali  se  zato omogućava  korišćenje  generičkih  verteks  shadera  koji  se mogu  koristiti  sa  više  fragment shadera. Ovim se smanjuju troškovi  razvoja  i održavanja shadera na uštrb par procenata performansi. 

    3.3.2 Fragment procesori 

    Fragment  procesor  je  programabilna  jedinica  koja  vrši  operacije  nad  fragmentima  i podacima koji su njima pridruženi. Predviđeno je da verteks procesor izvršava tradicionalne operacije kao što su:

  • 14

    • Operacije sa interpoliranim vrednostima

    • Pristup teksturama

    • Primena tekstura

    • Primena magle

    • Računanje finalne boje 

    Širok  spektar  drugih  izračunavanja  se  može  vršiti  na  fragment  procesoru.  Fragment shader  ne može  promeniti  x  i y  koordinatu  fragmenta.  Kao  i  u  slučaju  verteks  procesora, 

    fragment  procesor  koji  vrši  neka  od  izračunavanja  sa  prethodne  liste  je  dužan  da implementira i sva druga potrebna izračunavanja sa te liste. Fragment procesor ne podržava operacije koje rade sa više fragmenata u isto vreme. Da bi se podržao eventualni paralelizam u implementaciji, shaderi su pisani na taj način da zahtevaju samo jedan fragment, i pristup okolnim fragmentima nije dozvoljen. 

    Fragment  procesor  ne  zamenjuje  operacije  sa  fiksnom  funkcionalnošću  koje  dolaze  na kraju pipelinea kao što su coverage, pixel ownership test, scissor, stipple, alpha test, depth test, stencil test, alpha blending, logical operations, dithering i plane masking. 

    Slika 3.3. Ulaz i izlaz fragment procesora 

    Slika 3.3 pokazuje podatke koji predstavljaju ulaz fragment procesora, kao i podatke na izlazu.  Primarni  ulaz  fragment  procesora  su  interpolirane  interpolirajuće  promenljive  (i ugrađene i korisničke). Korisničke promenljive moraju biti definisane u fragment shaderu, i njihovi tipovi moraju biti isti kao u verteks shaderu. Promenljive koje se računaju u fiksnim stupnjevima  između  verteks  i  fragment  procesora  su  dostupne  fragment  procesoru  preko specijalnih promenljivih kao što su gl_FragCoord (pozicija) i gl_FrontFacing. Kao i verteks 

    shaderi,  i  fragment  shaderi mogu pristupiti  stanju OpenGLa  preko ugrađenih uniformnih

  • 15 

    promenljivih. Ovo omogućava da se tradicionalne verteks operacije kao što je npr. osvetljenje implementiraju  korišćenjem  fragment  shadera.  Istoj  uniformnoj  promenljivoj  se  može pristupiti i preko fragment i preko verteks shadera ukoliko je ona deklarisana u njima. 

    Jedna  od  najvećih  prednosti  fragment  procesora  je  što  može  pristupiti  memoriji  sa teksturama  proizvoljan  broj  puta.  Ova  osobina  se  može  iskoristiti  za  implementaciju  ray casting algoritma korišćenjem fragment shadera. 

    Parametri  koji  su  zadati  OpenGLu  definišu  način  filtriranja  tekstura.  Operacije  nad teksturom se vrše prilikom pristupa teksturi iz fragment shadera, a shader je dalje slobodan da iskoristi dobijenu vrednost na bilo koji način. 

    Za svaki fragment, fragment shader može izračunati boju i dubinu (upisujući ove vrednosti u promenljive gl_FragColor  i gl_FragDepth)  ili totalno odbaciti  fragment. Ovaj rezultat se 

    dalje prosleđuje za dalju obradu u pipelineu, kao što je opisano u poglavlju 2. 

    3.4 Pregled jezika 

    Ovde je dat samo kratak pregled OpenGL shading jezika. U narednim poglavljima će biti dat detaljniji prikaz. 

    Sledeći ciljevi su bili fundamentalni u razvoju OpenGL shading jezika:

    • definisati jezik koji radi dobro sa OpenGLom

    • podržati fleksibilnost novog hardvera

    • obezbediti hardversku nezavisnost

    • obezbediti dobre performanse

    • definisati jezik koji se lako koristi

    • definisati jezik koji će ostati standard duže vreme

    • ne sprečavati viši nivo paralelizma

    • obezbediti lakoću implementacije 

    Kao  što  je  već  napisano,  OpenGL  shading  jezik  je  zasnovan  na  sintaksi  ANSI  C programskog jezika, i na prvi pogled, programi pisani u ovom jeziku veoma podsećaju na one pisane u Cu. Sadrže main funkciju; konstante, identifikatori, operatori, izrazi, naredbe, petlje 

    itd. su praktično iste kao u Cu. 

    3.4.1 Dodaci u odnosu na jezik C 

    OpenGL shading jezik podržava vektorske tipove za realne i cele brojeve, kao i za boolean vrednosti. Za realne brojeve, vektorski tipovi su vec2 (dva broja), vec3 i vec4. Individualnim 

    vrednostima  vektora  se  može  pristupiti  preko  indeksa  ili  polja  strukture  (vektor  se  tad

  • 16 

    uslovno posmatra kao struktura). Tako npr. crvena boja se može dobiti dodavanjem .r na 

    ime vektora čime se pristupa prvoj komponenti vektora. 

    Matrice sa realnim brojevima su takođe podržane u jeziku. Tip mat2 se koristi za 2x2, mat3 za  3x3,  a mat4  za 4x4 matrice. Kolone matrica mogu biti  izabrane korišćenjem  indeksa, a 

    rezultat su vektori čijim komponentama se može pristupiti na već opisan način. 

    Za  pristup  teksturama  u  memoriji  su  obezbeđene  posebne  promenljive  tzv.  sempleri. Promenljiva  tipa  sampler1D  se  može  koristiti  za  pristup  jednodimenzionim  teksturama, sampler2D za dvodimenzione itd. 

    Kvalifikatori su dodati za upravljanje ulazom  i  izlazom shadera. Kvalifikatori attribute, uniform  i varying koriste se da naznače kom tipu ulaza i izlaza promenljiva služi. Shaderi 

    mogu koristiti ugrađene promenljive da bi pristupili stanju OpenGLa i da bi komunicirali sa fiksnim stupnjevima OpenGL pipelinea. 

    Postoji  mnoštvo  ugrađenih  funkcija  koje  olakšavaju  pisanje  programa  i  eventualno iskorišćavaju  mogućnosti  hardvera,  kao  što  su  trigonometrijske,  eksponencijalne, geomterijske i  razne druge matemičke  funkcije, kao i specijalne  funkcije za rad i efekte na teksturama. 

    3.4.2 Dodaci iz jezika C++ 

    OpenGL shading jezik, od svojstava  jezika C++, podržava preklapanje  funkcija (sa  istim imenom,  ali  različitim  brojem  ili  tipom  argumenata),  konstruktore  i  mogućnost  da  se promenljive deklarišu tamo gde su potrebne, a ne na samom početku bloka. Kao i u jeziku C++, funkcije moraju biti deklarisane pre upotrebe. 

    3.4.3 Svojstva jezika C koja nisu podržana 

    Za razliku od ANSI Ca, nema automatske konverzije podataka. Tako će npr. izraz float f = 0;  javiti grešku, dok je izraz float f = 0.0; ispravan. Dalje, OpenGL shading jezik ne 

    sadrži pokazivače, stringove, karaktere kao i operacije nad njima. Još neke osobine Ca su eliminisane u cilju pojednostavljenja jezika, kao što su unije, nabrojivi tipovi, polja tipa bitova u strukturama, kao i operacije sa bitovima. Konačno, jezik nije zasnovan na fajlovima tako da ne postoje #include direktive kao ni reference ka drugim fajlovima. 

    3.4.4 Ostale razlike 

    OpenGL shading jezik neke mogućnosti Ca pruža na različit način. Tako se konstruktori koriste  za konverziju  tipova  (umesto  castovanja),  kao  i  za  inicijalizaciju  promenljivih.  Čak uopšte i ne postoji podrška za konverziju tipova castovanjem. Dalje, za razliku od Ca koji koristi  prenos  parametara  funkcija  po  vrednosti,  OpenGL  shading  jezik  koristi  prenos parametara po vrednostima uz kopiranje vrednosti  izlaznih promenljivih nazad u pozivaoca po  izlasku  iz  funkcije  (eng.  call  by  valuereturn).  Parametri  funkcija  se  identifikuju kvalifikatorima in, out i inout, koji ukazuju na to da li je parametar izlazni, ulazni ili i ulazni i 

    izlazni. Parametri koji nemaju kvalifikator su ulazni parametri.

  • 17 

    3.5 Model drajvera 

    Drajver je softver koji kontroliše neki hardver i upravlja deljenim pristupom tom hardveru. OpenGL  u  svakom okruženju  spada  pod  drajver  jer  upravlja  deljenim  pristupom grafičkom čipu. Slika 3.4 pokazuje kako se pokreću shaderi u izvršnom okruženju OpenGLa. Aplikacija komunicira sa OpenGLom pozivajući funkcije APIja kao što je prikazano na slici 3.5. 

    Aplikacija 

    Izvorni kod shadera 

    OpenGL API 

    OpenGL driver 

    Shader objekat 

    Program objekat 

    Kompajler 

    Linker 

    kompajlirani kod 

    izvršni kod 

    Grafički hardver 

    Slika 3.4. Model izvršavanja OpenGL shadera 

    Slika 3.5. Redosled pozivanja funkcija OpenGL APIja pri korišćenju shadera

  • 18 

    Nova  funkcija  glCreateShader  (glCreateShaderObjectARB  za  OpenGL  1.5)  služi  da  se 

    alociraju  specijalne  strukture  podataka  koje  su  neophodne  za  smeštanje  Opengl  shadera. Ove strukture se nazivaju shader objekti. Pošto je shader objekat kreiran, aplikacija može da učita izvorni kod pozivanjem funkcije glShaderSource (glShaderSourceARB za OpenGL 1.5). 

    Kao  što  se  može  videti  na  slici  3.4,  prevodilac  za  OpenGL  shading  jezik  je  zapravo  deo OpenGL  drajvera.  Ovo  je  ključna  razlika  između  OpenGL  shading  jezika  i  drugih  shading jezika, kao što su HLSL (HighLevel Shader Language) iz Microsofta ili Cg iz Nvidiae. Pošto se  izvorni  kod  učita  u  shader  objekat,  on  se  može  kompajlirati  uz  pomoć  funkcije glCompileShader (glCompileShaderARB za OpenGL 1.5). Program objekat je takođe interna 

    struktura OpenGLa i ona je zapravo kontejner za shader objekte. Aplikacija mora da pridruži shader objekat program objektu korišćenjem komande glAttachShader (glAttachObjectARB 

    za OpenGL 1.5). Kad se shader objekti na ovaj način pridruže program objektu, oni mogu biti linkovani zajedno pozivanjem funkcije glLinkProgram  (glLinkProgramARB  za OpenGL 1.5). 

    Podrška za više shader objekata (i zahtev za postojanje linkera u OpenGL drajveru) je ključna razlika  u  odnosu  na  API  na  asemblerskom  nivou  koji  pruža  starija  ekstenzija ARB_vertex_program. Prilikom linkovanja, razrešavaju se spoljne reference između shadera, 

    proverava se kompatibilnost verteks  i fragmet shadera i alocira se memorija za uniformne promenljive.  Rezultat  je  izvršna  forma  koja  se  može  instalirati  kao  deo  trenutnog  stanja OpenGLa  pozivom  komande  glUseProgram  (glUseProgramObjectARB  za  OpenGL  1.5).  Na 

    slici 3.5 je dat grafički prikaz redosleda pozivanja funkcija. 

    3.6 Proširenja OpenGL APIja 

    U  OpenGLu  1.5  podrška  za  OpenGL  shading  jezik  je  obezbeđena  putem  ekstenzija.  U OpenGLu 2.0 shading jezik je postao deo standarda; ne treba uopšte koristiti ekstenzije, a funkcije  APIja  koje  služe  kao  podrška  Shading  jeziku  menjaju  imena  (najčešće  prostim odbacivanjem ARB sufiksa), kao i konstante i tipovi podataka (u okviru OpenGL APIja, ne u okviru Shading jezika). 

    Sledi  kratak  pregled  funkcija  koje  je  prvobitno  u  OpenGLu  1.5  omogućila  ekstenzija ARB_shader_object; prvo ime funkcije koje je dato u spisku je ono koje se koristi u OpenGL 

    u  2.0  (bez  korišćenja  ekstenzija),  a  u  zagradi  je  dato  ime  funkcije  koje  se  koristi  (sa ekstenzijom) u OpenGLu 1.5:

    • glCreateShader (glCreateShaderObjectARB) — kreiranje shader objekta

    • glCreateProgram (glCreateProgramObjectARB) — kreiranje program objekta

    • glDeleteProgram  i  glDeleteShader  (glDeleteObjectARB)  —  brisanje  shader  ili 

    program objekta

    • glShaderSource (glShaderSourceARB) — učitavanje izvornog koda u shader objekat

    • glCompileShader (glCompileShaderARB) — kompajliranje shadera

    • glAttachShader  (glAttachObjectARB)  —  pridruživanje  shader  objekta  program 

    objektu

  • 19

    • glDetachShader  (glDetachObjectARB)  —  odvajanje  shader  objekta  od  program 

    objekta

    • glLinkProgram  (glLinkProgramARB)  —  linkovanje  program  objekta  i  stvaranje 

    izvršnog koda

    • glUseProgram  (glUseProgramObjectARB)  —  postavljanje  izvršnog  koda  program 

    objekta u trenutno stanje OpenGLa

    • glValidateProgram  (glValidateProgramARB)  —  vraća  informacije  o  validnosti 

    program objekta

    • glUniform (glUniformARB) — postavlja vrednost uniformne promenljive

    • glGetActiveUniform  (glGetActiveUniformARB)  —  vraća  ime,  veličinu,  i  tip  aktivne 

    uniformne promenljive za program objekat

    • glGetAttachedShaders  (glGetAttachedObjectsARB)  —  vraća  listu  shader  objekata 

    koji su pridruženi program objektu

    • glGet za simboličku konstantu GL_CURRENT_PROGRAM (glGetHandleARB) — vraća ručku 

    za program objekat koji se trenutno koristi

    • glGetProgram  i  glGetShader  (glGetObjectParameterARB)  —  vraća  jedan  od 

    parametara objekta

    • glGetShaderSource (glGetShaderSourceARB) — vraća izvorni kod za određeni shader 

    objekat

    • glGetUniform (glGetUniformARB) — vraća trenutnu vrednost uniformne promenljive

    • glGetUniformLocation  (glGetUniformLocationARB)  —  vraća  lokaciju  koja  je 

    dodeljena uniformnoj promenljivoj od strane linkera

    • glGetProgramInfoLog  i  glGetShaderInfoLog  (glGetInfoLogARB)  —  daje  log  sa 

    informacijama za shader ili program objekat 

    Ekstenzija  ARB_vertex_shader  u  OpenGLu  1.5  je  direktna  podrška  programabilnom 

    verteks procesoru. Ona definiše kako se verteks procesor uklapa u postojeći OpenGL pipeline i definiše:

    • Kako se verteks shaderi kreiraju

    • Kako se verteks shaderi uključuju/isključuju

    • Koji deo fiksnog pipelinea se isključuje kad se uključi verteks shader

    • Kako se vrednosti prosleđuju verteks shaderu

    • Kako se rukuje generičkim verteks atributima

  • 20

    • Interfejs između stupnjeva pipelinea koji dolaze posle programabilne obrade verteksa i verteks procesora 

    Sledi  kratak  pregled  funkcija  koje  je  prvobitno  u  OpenGLu  1.5  omogućila  ekstenzija ARB_vertex_shader; prvo ime funkcije koje je dato u spisku je ono koje se koristi u OpenGL 

    u  2.0  (bez  korišćenja  ekstenzija),  a  u  zagradi  je  dato  ime  funkcije  koje  se  koristi  (sa ekstenzijom) u OpenGLu 1.5:

    • glVertexAttrib (glVertexAttribARB) — šalje generičke atribute verteksa OpenGLu, 

    verteks po verteks

    • glVertexAttribPointer  (glVertexAttribPointerARB) — šalje lokaciju i organizaciju 

    atributa verteksa preko nizova verteksa

    • glBindAttribLocation  (glBindAttribLocationARB)  —  pridružuje  određeni  indeks 

    generičkog atributa korisnički definisanom imenu u verteks shaderu

    • glEnableVertexAttribArray  (glEnableVertexAttribArrayARB) —  omogućuje  da  se 

    generički atributi verteksa šalju putem nizova verteksa

    • glDisableVertexAttribArray  (glDisableVertexAttribArrayARB)  —  onemogućuje 

    da se generički atributi verteksa šalju putem nizova verteksa

    • glGetVertexAttrib  (glGetVertexAttribARB)  —  vraća  trenutno  stanje  za  određeni 

    atribut verteksa

    • glGetVertexAttribPointer  (glGetVertexAttribPointerARB)  —  vraća  pokazivač  na 

    niz verteksa za određeni generički atribut verteksa

    • glGetActiveAttrib  (glGetActiveAttribARB)  —  vraća  ime,  veličinu,  i  tip  aktivnog 

    atributa za program objekat 

    Treća i poslednja ekstenzija koja je dodata kao podrška Shading jeziku u OpenGLu 1.5 je ARB_fragment_shader. Ova ekstenzija je slična ekstenziji ARB_vertex_shader, osim što ona 

    definiše mogućnosti  programabilnog  fragment  procesora.  Ova  ekstenzija  zapravo ne  pruža nikakve nove API funkcije zato što je zasnovana na funkcijama već definisanim u ekstenziji ARB_shader_objects. Ali, ona određuje:

    • Kako se fragment shaderi kreiraju

    • Kako se fragment shaderi uključuju/isključuju

    • Koji deo fiksnog pipelinea se isključuje kad se uključi fragment shader

    • Kako se vrednosti dobijene rasterizacijom prosleđuju fragment shaderu

    • Interfejs  između  stupnjeva  pipelinea  koji  dolaze  posle  programabilne  obrade fragmenta 

    Ovo je sve naravno ugrađeno u OpenGLu 2.0 bez ekstenzija. OpenGL 2.0 uvodi još dve nove funkcije koje služe kao podrška Shading jeziku:

  • 21

    • glIsProgram — određuje da je zadati parametar program objekat

    • glIsShader — određuje da je zadati parametar shader objekat 

    U ovom radu će  detaljno biti obrađivane samo neke od novih funkcija OpenGL APIja, kad se za to ukaže potreba. Detaljna specifikacija ovih funkcija se može naći u [2], [7] i [9]. 

    3.7 Primer inicijalizacije i korišćenja shadera u OpenGL programu 

    Ovo poglavlje će dati primer kako se kompajliraju,  linkuju i koriste shaderi u OpenGLu 2.0.  Ceo  primer  sadrži  samo  aplikacijski  izvorni  kod  sa  funkcijama  OpenGL  APIja.  Za jednostavan primer samih verteks i fragment shadera možete pogledati odeljak 4.1. Primer u ovom poglavlju prati sliku 3.5. Kreiranje objekata se vrši pomoći funkcije glCreateShader 

    čija deklaracija glasi: 

    GLuint glCreateShader(GLenum shaderType) 

    gde  shaderType  može  biti  GL_VERTEX_SHADER  ili  GL_FRAGMENT_SHADER  i  označava  tip 

    shadera koji se kreira. Funkcija vraća ručku ka objektu. Primer kreiranja verteks objekta  i fragment objekta: 

    GLuint vertexShader, fragmentShader;  // ručke za objekte 

    //Kreiranje verteks i fragment objekata 

    vertexShader = glCreateShader(GL_VERTEX_SHADER); 

    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 

    Po kreiranju shader objekata, u njih se  može učitati izvorni kod shadera pomoću funkcije glShaderSource čija deklaracija glasi: 

    void glShaderSource(Gluint shader, 

    GLsizei count, 

    const GLchar **string, 

    const GLint *length) 

    Primer učitavanja izvornog koda: 

    glShaderSource(vertexShader, 1, &vertexSource, NULL); 

    glShaderSource(fragmentShader, 1, &fragmentSource, NULL); 

    Ovde  su vertexSource  i fragmentSource  promenljive  tipa Glchar*  koje  sadrže  izvorne 

    kodove  odgovarajućih  shadera.  Ovi  stringovi  se  mogu  na  tradicionalan  način  učitati  iz spoljnih  fajlova.  Prvi  parametar  funkcije  glShaderSource  govori  kom  shader  objektu  se 

    pridružuje izvorni kod; drugi parametar je broj stringova koji se nalaze u trećem parametru; treći  parametar  je  niz  stringova  (GLchar**)  u  kojima  se  nalazi  izvorni  kod;  dok  je  četvrti 

    parametar niz koji sadrži dužinu svakog od stringova  iz  trećeg parametra. Ako  je poslednji

  • 22 

    parametar NULL, pretpostavlja se da se stringovi završavaju sa nullkarakterom. Ova funkcija 

    samo kopira stringove u odgovarajući objekat bez parsiranja i ispitivanja validnosti izvornog koda.  Po  učitavanju  koda,  on  se može  kompajlirati  sa  funkcijom  glCompileShader  čija  je 

    deklaracija: 

    void glCompileShader(Gluint shader) 

    Shaderi se onda mogu kompajlirati jednostavnim pozivom: 

    glCompileShader(vertexShader); 

    glCompileShader(fragmentShader); 

    Ovde je dobar trenutak da se očita status kompajliranja kako bi se videlo da li je došlo do neke  greške.  Za  tu  svrhu  se  može  koristiti  funkcija  glGetShaderiv  koja  vraća  jedan  od 

    parametara shadera. Njena deklaracija je: 

    void glGetShaderiv(GLuint shader, 

    GLenum pname, 

    GLint *params) 

    Ovde je shader ručka ka shader objektu, pname simboličko ime parametra koji se očitava, a  params  očitani  parametar.  Parametar  pname  može  biti  GL_SHADER_TYPE, GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, GL_SHADER_SOURCE_LENGTH i GL_DELETE_STATUS. U ovom stupnju nam je potreban parametar GL_COMPILE_STATUS koji vraća GL_TRUE ako je 

    kompajliranje uspešno završeno. Primer: 

    GLint  vertCompiled, fragCompiled;    // statusi 

    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertCompiled); 

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &vertCompiled); 

    if (!vertCompiled || !fragCompiled) 

    exit(1); 

    Ukoliko  je kompajliranje  neuspešno, može se pročitati  log  kompajlera.  Potrebno  je  prvo pročitati parametar GL_INFO_LOG_LENGTH (sa gore opisanom funkcijom glGetShaderiv) koji 

    govori koliko  je dugačak (u karakterima)  log sa  informacijama, alocirati memorijski prostor za log i pročitati ga korišćenjem funkcije glGetShaderInfoLog čija je deklaracija: 

    void glGetShaderInfoLog(GLuint shader, 

    GLsizei maxLength, 

    GLsizei *length, 

    GLchar *infoLog) 

    Ovde  je shader  objekat  čiji se  log čita, maxLength  najduži  string koji  se može upisati u promenljivu  log,  a  length  dužina  stringa  koji  je  upisan  u  log.  Primer  očitavanja  loga  za vertexShader objekat: 

    int length = 0;

  • 23 

    int charsWritten  = 0; 

    GLchar *log; 

    glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length); 

    if (length > 0) 

    log = (GLchar *)malloc(length); 

    if (log == NULL) 

    printf("ERROR: Could not allocate InfoLog buffer\n"); 

    exit(1); 

    glGetShaderInfoLog(vertexShader, length, &charsWritten, log); 

    printf("Shader InfoLog:\n%s\n\n", log); 

    free(log); 

    Posle uspešnog kompajliranja shader objekta, on se može pridružiti program objektu. Prvo se mora kreirati program objekat pomoću funkcije glCreateProgram: 

    GLuint glCreateProgram(void) 

    koja vraća ručku ka kreiranom program objektu. Potom se priduživanje shader  objekata program objektu vrši sa funkcijom glAttachShader: 

    void glAttachShader(GLuint program, 

    GLuint shader) 

    gde je program ručka ka program, a shader ručka ka shader objektu. Primer: 

    GLuint prog;  // ručka za program objekat 

    prog = glCreateProgram(); 

    glAttachShader(prog, vertexShader); 

    glAttachShader(prog, fragmentShader); 

    Po  kreiranju  program  objekta  i  pridruživanju  shader  objekata,  a  pre  upotrebe  program objekta,  je  potrebno  linkovati  shader  objekte  koji  su  pridruženi  jednom  program  objektu pozivom funkcije glLinkProgram: 

    void glLinkProgram(GLuint program) 

    gde je program ručka ka program objektu koji se linkuje. Primer: 

    glLinkProgram(prog);

  • 24 

    S  obzirom  da  se  program  objektu  mogu  pridružiti  verteks  i  fragment  shaderi  koji  nisu kompatibilni (na primer, kad fragment shader pokušava da pročita promenljivu koju verteks shader  nije  ni  deklarisao),  dobra  je  praksa  posle  linkovanja  iščitati  status  linkovanja,  na sličan način na koji je pročitan status posle kompajliranja: 

    GLint linked; 

    glGetProgramiv(prog, GL_LINK_STATUS, &linked); 

    if (!linked) 

    exit(1); 

    Takođe  se  može  iščitati  i  log  linkovanja  korišćenjem  funkcija  glGetProgramiv  i glGetProgramInfoLog koje su analogne onima za shader objekte: 

    int length = 0; 

    int charsWritten  = 0; 

    GLchar *log; 

    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &length); 

    if (length > 0) { 

    log = (GLchar *)malloc(length); 

    if (log == NULL) 

    printf("ERROR: Could not allocate InfoLog buffer\n"); 

    exit(1); 

    glGetProgramInfoLog(prog, length, &charsWritten, log); 

    printf("Shader InfoLog:\n%s\n\n", log); 

    free(log); 

    Konačno, ako je proces linkovanja prošao bez greške, program objekat koji sadrži izvršni kod  za  grafički  procesor  treba  postaviti  kao  deo  OpenGL  stanja  sa  API  funkcijom glUseProgram koja prima kao parametar ručku ka program objektu: 

    glUseProgram(prog); 

    Inicijalizacija shadera se uvek odvija na ovaj način, s tim da je moguće jednom program objektu  pridružiti  više  verteks  i  više  fragment  shader  objekata.  Bitno  je  samo  da  među verteks objektima postoji tačno jedna main funkcija, kao i među fragment objektima. 

    Primer korišćenja OpenGL APIja za prosleđivanje promenljivih iz aplikacije u shadere je dat u poglavlju 5.1.2.

  • 25 

    4. Sintaksa OpenGL Shading jezika 

    U  ovom poglavlju  će biti  prezentovan OpenGL shading  jezik  sa  svim  svojim  osobinama. Prvo  će  biti  dat  jednostavan primer koji  pokazuje  osnovnu  strukturu  shadera. Svi  aspekti jezika će onda biti obrađeni. 

    4.1 Jednostavan primer 

    Program tipično sadrži dva shader programa: jedan verteks i jedan fragment shader. Može biti  prisutno više  shadera  svakog  tipa, ali mora  postojati  tačno  jedna main  funkcija među verteks  i  tačno  jedna  main  funkcija  među  fragment  shaderima.  Sledeći  par  verteks  i 

    fragment shadera je najjednostavniji primer, tzv. Hello World za OpenGl shading jezik. Vrše se samo najosnovnije radnje: transformacije verteksa i renderovanje primitive u jednoj boji. Prvo će biti dat verteks shader koji se izvršava nad svakim verteksom: 

    void main() 

    gl_Position = ftransform(); 

    Funkcija  ftransform  je  ugrađena  funkcija  u  OpenGL  Shading  jezik  i  obezbeđuje 

    transformaciju ulaznog verteksa na isti način na koji to radi i fiksna funkcionalnost OpenGLa. Tranformisana  koordinata  verteksa  se  upisuje  u  ugrađenu  promenljivu gl_Position.  Posle 

    ovog verteks shader dolazi na red sklapanje primitiva, koje će dati stupnju za rasterizaciju dovoljno  informacija  za  renderovanje  primitive.  Za  svaki  frament  se  onda  izvršava  sledeći fragment shader: 

    void main() 

    gl_FragColor = vec4(0.8, 0.4, 0.4, 1.0); 

    gl_FragColor  je ugrađena promenljiva u koju se upisuje boja fragmenta. Više detalja o 

    ugrađenim promenljivama sledi kasnije u ovom poglavlju. Iscrtavanjem standardne čajanke komandom glutSolidTeapot se dobija sledeći izlaz: 

    Kao što se može videti sa slike, u ovom primeru se zanemaruju svetla, teksture, materijal i boja koja je pridružena primitivama.

  • 26 

    4.2 Tipovi podataka 

    4.2.1 Skalarni tipovi 

    Od skalarnih tipova jezik podržava:

    • float – broj sa pokretnim zarezom (realan broj)

    • int – ceo broj

    • bool – Boolean vrednost 

    Promenljive se deklarišu kao i u jeziku C++: 

    float f; 

    float g, h = 2.4; 

    int NumTextures = 4; 

    bool skipProcessing; 

    Za  svaku  promenljivu  se  mora  naznačiti  tip,  a  deklaracije  mogu  stajati  gde  god  su potrebne  a  ne  samo na  početku  bloka.  Realni  brojevi  i  operacije  sa  njima  su  iste  kao  i  u jeziku C. Kod pisanja realnih brojeva neme sufiksa za preciznost jer postoji samo jedan tip realnih brojeva. Celi brojevi nisu  isti kao u  jeziku C. Po specifikaciji, u hardveru ne moraju postojati  registri  za  celobrojne  vrednosti.  Dalje,  rezultat  je  nedefinisan  kod  operacija  kod kojih  dolazi  do  prekoračenja.  Operacije  pomeranja  (

  • 27 

    OpenGL shading jezik ne pravi razliku između vektora sa bojom i vektora sa pozicijom ili bilo kog drugog vektora sa realnim brojevima. Komponentama vektora se može pristupiti  ili preko indeksa  ili preko polja  (kao sa strukturama). Npr., ako  je promenljiva pozicija  tipa vec3, onda se ta promenljiva može posmatrati kao vektor (x, y, z), i pozicija.x će ukazivati 

    na  prvu  komponentu  vektora.  Sledeća  imena  su  na  raspolaganju  za  selektovanje komponenata vektora:

    • x, y, z, w – vektor se posmatra kao pozicija ili smer

    • r, g, b, a – vektor se posmatra kao boja

    • s, t, p, q – vektor se posmatra kao koordinata teksture 

    Nema eksplicitnog načina da se za neki vektor naznači da on predstavlja boju, koordinatu ili nešto treće. Ova imena su omogućena samo radi lakšeg programiranja i preglednijeg koda. Ako se pristupa preko indeka pozicija.x je zapravo  pozicija[0]  itd. 

    4.2.3 Matrice 

    Ugrađeni matrični tipovi postoje samo za realne brojeve:

    • mat2 – 2x2 matrica realnih brojeva

    • mat3 – 3x3 matrica realnih brojeva

    • mat4 – 4x4 matrica realnih brojeva 

    Matrice su pogodne za smeštanje podataka za linearne transformacije. Moguće je množiti vektor i matricu odgovarajuće veličine  i tada se kao rezultat dobija vektor. Komponentama matrice  se  pristupa  preko  indeksa  –  ako  je  transformacija  tipa  mat4,  onda  je transformacija[2]  treća  kolona  matrice  transformacija  i  ona  je  tipa  vec4.  Pošto  je transformacija[2] vektor, a vektori se mogu tretirati kao nizovi, transformacija[2][1] je 

    druga komponenta treće kolone matrice transformacija. Važno je naglasiti da prvi  indeks, u skladu sa tradicionalnim OpenGLom, selektuje kolonu, a ne red. 

    4.2.4 Sempleri 

    Čitanje  iz  tekstura  zahteva  znanje  iz  koje  teksture  i/ili  teksturne  jedinice  se  čita.  Pošto OpenGL shading jezik ne zavisi od implementacije OpenGLa, obezbeđene su ručke za rad sa teksturama, tzv. sempleri. Tipovi semplera koji postoje:

    • sampler1D – pristupa jednodimenzionim teksturama

    • sampler2D – pristupa dvodimenzionim teksturama

    • sampler3D – pristupa trodimenzionim teksturama

    • samplerCube – pristupa cubemapped teksturama

    • sampler1DShadow – pristupa jednodimenzionim teksturama dubine sa poređenjem

    • sampler2DShadow – pristupa dvodimenzionim teksturama dubine sa poređenjem

  • 28 

    Kad  aplikacija  inicijalizuje  sempler,  OpenGL  implementacija  u  njega  smešta  informacije koje su dovoljne da se pristupi teksturi. Shaderi ne mogu sami  inicijalizovati semplere niti menjati  vrednost  semplera.  Oni  mogu  samo  primiti  sempler  od  aplikacija  kao  uniformnu promenljivu, i dalje slati taj sempler korisniku ili ugrađenoj  funkciji. Npr., sempler se može deklarisati kao: 

    uniform sampler2D Grass; 

    Kvalifikator  uniform  će  biti  objašnjen  kasnije.  Promenljiva  Grass  se  može  proslediti 

    funkciji za čitanje teksture na sledeći način: 

    vec4 color = texture2D(Grass, coord); 

    gde je coord tipa vec2  i sadrži dvodimenzionu poziciju koja ukazuje na piksel koji se treba 

    pročitati  iz  teksture.  Dobijeni  rezultat  je  boja  izabranog  piksela.  Detaljniji  primer  je  dat  u odeljku 6.3. 

    4.2.5 Strukture 

    OpenGL shading jezik podržava strukture slične onima iz jezika C. Npr.: 

    struct light 

    vec3 position; 

    vec3 color; 

    }; 

    Kao  i  u  C++  jeziku,  ime  strukture  je  ime  novodefinisanog  tipa  i  ne  koristi  se typedef. Ipak, typedef je rezervisana reč za moguća dalja unapređenja jezika. Promenljiva tipa light 

    iz prethodnog primera se jednostavno deklariše sa: 

    light ceilingLight; 

    Mnogi  drugi  aspekti  preslikavaju  mogućnosti  struktura  iz  Ca:  strukture  mogu  biti deklarisane unutar struktura i mogu se ugnježdavati. 

    Trenutno, strukture su  jedini tipovi koje korisnik može da specificira. Ključne reči union, enum i class su za sad samo rezervisane za moguću upotrebu u budućnosti. 

    4.2.6 Nizovi 

    OpenGL shading jezik podržava nizove bilo kog tipa. Npr., deklaracija: 

    vec4 points[10]; 

    kreira niz od deset vec4 vektora. Indeksi kreću od nule. Nema pokazivača, i jedini način da 

    se  deklariše  niz  je  uz  pomoć  srednjih  zagrada.  Ipak,  nizovi  ne moraju  imati  specificiranu dužinu. Deklaracija: 

    vec4 points[]; 

    je dozvoljena u sledećim slučajevima:

  • 29 

    1) Pre nego što se niz referencira, ponovo se deklariše ovog puta sa veličinom, i naravno sa tipom kao u prvoj deklaraciji. Veličina niza sa jedanput definisanom dužinom se ne može ponovo menjati: 

    vec4 points[];  // niz nepoznate veličine 

    vec4 points[10];  // niz veličine 10 

    vec4 points[20];  // nedozvoljeno 

    vec4 points[];  // i ovo je nedozvoljeno 

    2) Svi indeksi koji statički referenciraju niz su konstante pri kompilaciji. U ovom slučaju, kompajler  će  napraviti  dovoljno  dugačak  niz  da  primi  vrednost  sa  najvećim  indeksom.  Na primer: 

    vec4 points[];  // niz nepoznate veličine 

    points[2] = vec4(1.0);  // niz je sad veličine 3 

    points[7] = vec4(2.0);  // niz je sad veličine 8 

    U vreme izvršavanja niz će imati samo jednu veličinu, određenu najvećim indeksom. Ova osobina  je  naročito  zgodna  za  rukovanje  sa  ugrađenim  nizovima  teksturnih  koordinata. Interno, ovaj niz je deklarisan kao: 

    varying vec4 gl_TexCoord[]; 

    Ako program koristi samo indekse 0 i 1, veličina niza će se automatski postaviti na 2. Ako shader koristi promenljivu za indeksiranje niza, onda će morati da eksplicitno naznači veličinu niza. Treba napomenuti da  je veličina nizova, pogotovu varying nizova, veoma bitna zbog 

    ograničenosti hardvera. 

    Ako više  shadera deli  isti  niz  svaki  od njih može  deklarisati  različitu veličinu.  Linker  će promeniti veličinu niza na najveću koja se pojavljuje u svim shaderima koji se linkuju. 

    4.2.7 Void 

    Tip void je omogućen za funkcije koje ne vraćaju nikakvu vrednost. Npr., funkcija main je tipa void: 

    void main() 

    ... 

    4.2.8 Deklaracije i opseg važenja 

    Promenljive se deklarišu slično kao u jeziku C++. Mogu se definisati tamo gde su potrebne i  imaju opseg važenja kao u C++ jeziku. Opseg promenljivih u for petlji je do kraja petlje, dok se u if izrazima promenljive ne mogu deklarisati. Kao u Cu, imena promenljivih mogu 

    sadržati samo brojeve, slova  i donju crtu (_), a mogu počinjati samo sa slovom ili donjom crtom (_). Korisnički definisane promenljive ne mogu počinjati sa „gl_“, kao ni sa više donjih 

    crta (__).

  • 30 

    4.2.9 Automatska konverzija tipova 

    OpenGL shading jezik je veoma striktan po pitanju tipova podataka. Ne postoji automatska konverzija  tipova  što  uprošćuje  izradu  kompajlera  i  sprečava  dvosmislene  situacije  kod preklapanja funkcija. 

    4.3 Inicijalizatori i konstruktori 

    Promenljiva može biti inicijalizovana prilikom deklarisanja, slično jeziku C: 

    float a, b = 3.0, c; 

    Konstante moraju biti inicijalizovane prilikom deklaracije: 

    const int Veličina = 4; 

    Atributske,  uniformne  i  interpolirajuće  promenljive  se  ne  mogu  inicijalizovati  pri deklaraciji: 

    attribute float Temperatura; //inicijalizacija nije dozvoljena, 

    //OpenGL API postavlja ovu promenljivu 

    uniform int Size;  //inicijalizacija nije dozvoljena, 

    //OpenGL API postavlja ovu promenljivu 

    varying float density;  //inicijalizacija nije dozvoljena, 

    //verteks shader postavlja ovu promenljivu 

    Za  inicijalizaciju  složenih  (neskalarnih)  tipova,  bilo  prilikom  deklaracije  ili  na  bilo  kom drugom mestu, se koriste konstruktori. Nema inicijalizacije korišćenjem velikih zagrada kao u Cu, mogu se koristiti samo konstruktori. Sintaksno, konstruktor je funkcija sa imenom istim kao i tip promenljive koja se inicijalizuje, npr.: 

    vec4 v = vec4(1.0, 2.0, 3.0, 4.0); 

    Postoje  ugrađeni  konstruktori  za  sve  ugrađene  tipove,  kao  i  za  strukture  (osim  za semplere). Neki primeri: 

    vec4 v = vec4(1.0, 2.0, 3.0, 4.0); 

    ivec2 c = ivec2(3, 4); 

    vec3 color = vec3(0.2, 0.5, 0.8); 

    mat2 m = mat2(1.0, 2.0, 3.0, 4.0); 

    struct light 

    vec4 position;

  • 31 

    struct 

    vec3 color; 

    float intensity; 

    } lightColor; 

    } light1 = light(v, lightColor(color, 0.9)); 

    Za  matrice,  komponente  se  popunjavaju  po  kolonoma.  Promenljiva  m  iz  prethodnog 

    primera je matrica: 

    Ugrađeni konstruktori za vektore mogu primiti i samo jedan argument, koji se zatim kopira u sve komponente. Tako na primer, izraz: 

    vec3 v = vec3(0.6); 

    je ekvivalentan izrazu: 

    vec3 v = vec3(0.6, 0.6, 0.6); 

    Ugrađeni konstruktori za matrice  takođe mogu imati samo jedan argument, ali se on ne kopira u sve komonente matrice, već samo na dijagonalu matrice, dok se ostale komponente inicijalizuju na 0.0. Tako na primer, izraz: 

    mat2 m = mat2(1.0);  // 2x2 matrica identiteta 

    je ekvivalentan izrazu: 

    mat2 m = mat2(1.0, 0.0, 0.0, 1.0); 

    Konstruktori mogu imati matrice i vektore kao argumente. Jedino pravilo je da promenljiva koja se prosleđuje kao argument mora imati dovoljno komponenti da inicijalizuje sve članove objekta koji se kreira: 

    vec4 v = vec4(1.0); 

    vec2 u = vec2(v);  // prve dve komponente vektora v inicijalizuju u 

    mat2 m = mat2(v); 

    vec2 t = vec2(1.0, 2.0, 3.0);  // dozvoljeno; 3.0 se ignoriše 

    4.4 Konverzija tipova 

    Eksplicitna konverzija tipova se takođe radi sa konstruktorima. Na primer: 

    float f = 2.3;

  • 32 

    bool b = bool(f); 

    će postaviti b na tačno (true). Ovo je korisno za if izraze jer on prihvata samo Boolean vrednosti. Boolean konstruktori konvertuju svaku nenula vrednost u tačno (true), a nulu u netačno (false). Kad se Boolean vrednost konvertuje u broj, netačno se konvertuje u nulu, a 

    tačno u 1 ili 1.0. 

    4.5 Kvalifikatori i interfejs ka shaderima 

    Kvalifikatori se mogu javiti kao prefiks ispred promenljivih  i  ispred formalnih parametara funkcije. Kvalifikatori koji se koriste za promenljive su:

    • attribute – za podatke koje se često menjaju, a prosleđuju iz aplikacije ka verteks 

    procesoru

    • uniform –  za podatke koje  se ne menjaju  često, dostupne  i  iz verteks  i  iz  fragment 

    shadera

    • varying  –  za  interpolirane  podatke  koji  se  prosleđuju  od  verteks  ka  fragment 

    procesoru

    • const – za konstantne podatke 

    Podaci  se  prosleđuju  u  i  iz  shadera  na  drugačiji  način  nego  kod  tipičnih  programskih okruženja:  čitanjem  i  upisom  ugrađenih  promenljivih  i  korisnički  definisanih  atributskih, uniformnih i interpolirajućih promenljivih. Neke od ugrađenih promenljivih su date u odeljku 5.  Promenljive  koje  se  deklarišu  kao  atributske,  uniformne  ili  interpolirajuće  moraju  biti globalne zbog toga što su one vidljive i izvan shadera, i za jedan program, svi shaderi dele promenljive. Kvalifikatori u deklaraciji uvek idu ispred tipa promenljive: 

    attribute float Temperatura; 

    const int BrojSvetala = 3; 

    uniform vec4 PozicijaSvetla[BrojSvetala]; 

    varying floar IntezitetSvetla; 

    4.5.1 Atributske promenljive 

    Atributske promenljive se koriste za podatke koje se često menjaju, i koji se prosleđuju iz aplikacije  ka  verteks  procesoru. One  se mogu menjati  za  svaki  verteks.  Postoje  ugrađene promenljive  kao npr.  gl_Vertex  i  gl_Normal,  za  čitanje  stanja  OpenGLa,  a moguće  je  i 

    definisati  korisničke  promenljive.  Atributske  promenljive  su  ograničene  na  sledeće  tipove: realni  skalari,  realni  vektori  i  matrice.  Nema  podrške  za  nizove,  strukture,  cele  brojeve  i Boolean  vrednosti  kao  atributske  promenljive,  i  sprečavanjem  korišćenja  ovih  tipova  je obezbeđen najbrži prenos podataka iz aplikacije u shader. Verteks shaderi ne mogu menjati ove promenljive. Atributske promenljive se ne mogu deklarisati u fragment shaderu.

  • 33 

    4.5.2 Uniformne promenljive 

    U uniformne promenljive, kao i u atributske, se može upisivati samo izvan shadera. Oni se mogu menjati najviše jednom po primitivi i zbog toga su vidljivi i u fragment shaderima. Svi  verteks  i  fragment  shaderi  koji  obrazuju  jedan  program  dele  globalne  uniformne promenljive sa istim imenom, ali ih ne mogu menjati, jer se može desiti da više procesora u paraleli radi sa istom promenljivom. 

    Sve  promenljive  sempler  tipa  (npr.  sampler2D)  moraju  koristiti  kvalifikator  uniform 

    ukoliko  te  promenljive  nisu  parametri  funkcije.  Ovo  je  zbog  toga  što  aplikacija  mora inicijalizovati sempler, a shader ga ne sme menjati. 

    4.5.3 Interpolirajuće promenljive 

    Interpolirajuće  promenljive  su  jedini  način  da  se  rezultat  verteks  shadera  prosledi fragment shaderu. Ova veza je dinamička u smislu da se za svaki fragment vrednosti ovih promenljivih  dobijaju  interpolacijom  odgovarajućih  promenljivih  iz  verteks  shadera.  Radi poboljšanja  performansi,  preporučuje  se  da,  ukoliko  je  neka  vrednost  ista  na  nekoj  većoj oblasti, aplikacija postavi uniformnu promenljivu koju će kasnije očitati  i verteks i fragment shaderi.  Izuzetak  su  slučajevi  u  kojima  aplikacija  postavlja  nove  vrednosti  uniformne promenljive  za  svaki  trougao  ili  mali  set  trouglova,  jer  tada  česta  promena  uniformnih promenljivih  može  uticati  na  performanse.  Interpolacija  vrednosti  interpolirajućih promenljivih se vrši u odnosu na perspektivu kako bi promena vrednosti bila glatka. 

    U  interpolirajuću  promenljivu  se  može  upisati  samo  iz  verteks  shadera,  dok  fragment shader ove vrednosti može samo da čita. 

    4.5.4 Konstante 

    Promenljive koje su deklarisane kao konstante nisu vidljive izvan shadera. Postoji podrška za neskalarne konstante. Primer: 

    const int numIterations = 10; 

    const float pi = 3.14159; 

    const vec2 v = vec2(1.0, 2.0); 

    const vec3 u = vec3(v, pi); 

    const struct light 

    vec3 position; 

    vec3 color; 

    } fixedLight = light(vec3(1.0, 0.5, 0.5), vec3(0.8, 0.8, 0.5)); 

    4.5.5 Promenljive bez kvalifikatora 

    Promenljive koje su deklarisane bez kvalifikatora (ali ne kao parametri funkcija), shaderi mogu i čitati i upisivati. Više shadera istog tipa (verteks ili fragment) koji se nalaze u jednom

  • 34 

    programu,  mogu  deliti  ovakve  promenljive  ako  su  definisane  na  globalnom  nivou.  Ove promenljive, kao i konstante, nisu vidljive izvan shadera. Promenljive definisane u verteks shaderu  nisu  vidljive  iz  fragment  shadera  i  obratno.  Životni  vek  promenljive  bez kvalifikatora  je  ograničen  na  jedno  pokretanje  shadera.  Ne  postoji  koncept  analogan statičkim promenljivama u jeziku C, kako bi se olakšalo paralelno procesiranje shadera. 

    4.6 Kontrola toka 

    Kontrola toka je vrlo slična C++ jeziku. Ulazna tačka shadera je main funkcija. Program koji sadrži i verteks i fragment shadere ima dve main funkcije, po jednu za svak tip shader a. Petlje se mogu napraviti korišćenjem for, while, i dowhile sintaksi. Promenljive se mogu deklarisati u for  i while  izrazima  i njihov opseg  traje do kraja petlje. Ključne  reči break  i continue su takođe podržane, na isti način kao u jeziku C. 

    Selekcija se može napraviti sa if i ifelse, s tim da se promenljiva ne može deklarisati u if naredbi. Selekcija može da se napravi i korišćenjem operatora (?:) a drugi i treći operand 

    moraju biti istog tipa. Izraz za sve uslove mora davati rezultat Boolean tipa. 

    Posebna  ključna  reč,  discard,  može  sprečiti  fragment  da  ažurira  bafer  okvira.  Od 

    implementacije zavisi da li će nastaviti izvršavanje shadera kad naiđe na ovu ključnu reč, ali se garantuje da se bafer okvira neće promeniti. 

    Ključne reči iz jezika C, goto i switch, kao i labele nisu podržane. 

    4.6.1 Funkcije 

    Funkcije mogu biti  preklopljene sa  različitim  tipovima parametara, ali ne  samo sa  tipom rezultata. Definicija ili bar deklaracija moraju biti u opsegu pozivanja. Tipovi parametara se striktno proveravaju. Kako nema automatske konverzije tipova, tako ni nema dvosmislenosti prilikom  određivanja  koja  će  se  od  preklopljenih  funkcija  pozvati.  Funkcije  koje  nisu  void tipa, moraju vratiti  rezultat korišćenjem ključne  reči return. Funkcije se ne smeju pozivati 

    rekurzivno, bilo da je taj poziv direktan ili indirektan. 

    4.6.2 Prenos parametara 

    OpenGL  shading  jezik  koristi  prenos  parametara  funkcija  po  vrednostima  uz  kopiranje nazad  izlaznih  promenljivih.  Ulazni  parametri  se  kopiraju  u  funkciju,  tj.  pošto  nema pokazivača, prosleđuju se po vrednosti. Izlazni parametri se upisuju nazad u pozivaoca tek po izl