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
1
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
2
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
3
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.
4
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
5
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
6
#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.
7
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
9
8 5 4 3
13 12
17
14
11
1 2
7
15
6
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
8
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).
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
Top Related