Programi za sjenčanje geometrije

Kontakt

Autora možete kontaktirati na jmaricevic@nightirion.com

Korisni linkovi

Programi za sjenčanje geometrije

Model programa za sjenčanje geometrije je prvi put predstavljen kao dio Unificiranog modela programa za sjenčanje (engl. Unified Shader Model) u DirectX-u 10. Nvidia GeForce 8800 grafički procesori su bili prvi koji su imali sklopovsku podršku za izvođenje ovih programa. U OpenGL standard su uključeni od verzije 3.2, a iz prijašnjih verzija im se može pristupati pomoći EXT_geometry_shader4 ekstenzije. Za razliku od ostalih programa za sjenčanje, programi za sjenčanje geometrije su opcionalni.

Izvršavaju se poslije programa za sjenčanje vrhova, a prije programa za sjenčanje fragmenata. Zbog toga, ako postoji potreba da se radi na vrhovima koji nisu već projekcijski transformirani, onda se i izračunavanje transformacija mora prebaciti u njih, i ukloniti iz programa za sjenčanje vrhova.

Pojednostavljeni prikaz cjevovoda u unificiranom modelu programa za sjenčanje.


Osnovna namjena programa za sjenčanje geometrije je stvaranje novih grafičkih primitiva ili uklanjanje već postojećih. Standardne primitive koje može primati su točka, linija i trokut, uz mogućnost i prosljeđivanja informacija o neposrednom susjedstvu.

Unificirani model programa za sjenčanje, poznat i pod nazivom model 4.0, koristi dosljedan skup instrukcija za sve tipove programa. Također, svi tipovi programa imaju gotovo iste mogućnosti: čitanje iz tekstura, međuspremnika podataka i obavljanje istog skupa aritmetičkih instrukcija. Međutim, skup instrukcija nije identičan između različitih tipova. Na primjer, samo programi za sjenčanje fragmenata mogu čitati teksture s implicitnim koordinatama, samo programi za sjenčanje geometrije mogu emitirati dodatne primitive itd.

Raniji modeli 1.x su koristili potpuno različite instrukcije za programe za sjenčanje vrhova i fragmenata, dok se sa kasnijim verzijama (2.x i 3.0) razlika sve više smanjivala, približavajući se tako sadašnjem unificiranom modelu.

Kada grafičko sklopovlje podržava unificirani model, onda je obično dizajnirano tako da procesorske jedinice za izvršavanje programa za sjenčanje mogu izvoditi bilo koji tip programa. Takva arhitektura se naziva unificirana arhitektura (engl. Unified Shader Architecture), te ima veliku prednost u brzini izvođenja nad sklopovljem koje ne implementira unificiranu arhitekturu. Prednost dolazi od mogućnosti dinamičkog organiziranja i balansiranja opterećenja između procesorskih jedinica. Na primjer, ako imamo nekoliko prolaza koji koriste samo programe za sjenčanje vrhova i fragmenata, tada možemo svim procesorskim jedinicama dodijeliti određene zadaće, iskorištavajući tako sve dostupne resurse, dok bi u slučaju zasebnih arhitektura dio procesorskih jedinica za izvođenje programa za sjenčanje geometrije ostao besposlen. Dobar primjer su također scene koje su iznimno zahtjevne za jednu vrstu programa, dok su ostali trivijalni, pa se u skladu tome raspoređuje broj jedinica koje rade na kojim vrstama programa.

Unatoč tome, sklopovlje ne mora nužno implementirati unificiranu arhitekturu da bi moglo podržavati unificirani model programa za sjenčanje i obrnuto.

Ograničenja i mogućnosti programa za sjenčanje geometrije

Prvo i najznačajnije ograničenje programa za sjenčanje geometrije je relativno nizak broj primitiva koje se mogu generirati. U prvim generacijama grafičkih kartica koje su to podržavale, taj broj je, ovisno o tipu izlaznih primitiva, bio vrlo nizak, ponegdje čak i ispod 64. No napretkom sklopovlja korištenog u grafičkim karticama, danas oni iznose nekoliko tisuća. Na primjer, grafička kartica GeForce 9400M na kojoj je izrađen ovaj rad, ima mogućnost generiranja do maksimalno 1024 izlazna trokuta za svaki ulazni trokut. Ta vrijednost za druge primitive može rasti ili padati u ovisnosti o tipu primitiva. Ako je izlazna primitiva tipa traka trokuta (engl. triangle strip), njihov maksimalan broj opada na 128, dok za točku maksimalan broj raste na 4096.

Uzrok tome je ograničena količina memorije u međuspremniku koja se može proslijediti dalje, te zbog toga broj maksimalnih primitiva ovisi o tome koliko memorije koji tip primitive zauzima.

Sljedeći problem je nedostupnost informacija o ostatku modela te o netom generiranoj geometriji. Naime, predviđeni način prosljeđivanja informacija o susjedima za bilo koji tip primitive je ograničen samo na neposredne susjede, a i tada mi moramo u programu generirati i proslijediti tu informaciju. Tako nešto često zahtjeva ili mijenjanje načina učitavanja i prikazivanja modela u memoriji, ili dinamičko generiranje informacija o susjednim vrhovima, što može usporiti program, a i stavlja više opterećenja na procesor. Također, do informacija o novonastaloj geometriji ne možemo doći do idućeg prolaza.

Zbog ovih gore navedenih razloga, implementacija metode koju predlaže Fleischer u svom radu (Fleischer et al, 1995) bi bila prekompleksna i neefikasna. Prvenstveno zbog potrebe da stanica ima informacije o svim svojim susjednim stanicama i njihovom trenutačnom stupnju i načinu razvoja, a potom i zbog kompleksnosti novonastale geometrije.

Iako su se unatoč ovim problemima programi za sjenčanje geometrije uz neke pomoćne i zaobilazne metode koristili za vrlo širok raspon problema, uvođenje novih vrsta programa za sjenčanje i teselaciju u DirectX-u 11 eliminira potrebu korištenja programa za sjenčanje geometrije kao programa čija je primarna namjena generiranje nove geometrije. Ovime se problem generiranja nove geometrije prebacuje na programe za sjenčanje ljuske (engl. hull shaders), programe za sjenčanje područja (engl. domain shaders) i programe za teselaciju (engl. tesselator).

U tom slučaju najčešći tip uporabe programa za sjenčanje geometrije neće biti generiranje, već uklanjanje geometrije za npr. mijenjanje razine detalja prikaza. Generiranje geometrije u programima za sjenčanje geometrije će biti ograničeno na specifične primjene za koje nisu potrebne dodatne informacije, poput generiranja točkastih sličica (engl. point sprite).

Konceptualni prikaz Direct3D 11 cjevovoda.


Primjena programa za sjenčanje geometrije

Zbog generičkog skupa instrukcija koji je dio unificiranog modela, način mogućih primjena programa za sjenčanje geometrije je vrlo raznolik i proteže se u rangu od osnovnih problema poput razine detalja prikaza do egzotičnih primjena koje nemaju nužno veze sa samom grafikom poput modeliranja ponašanja nekih likova unutar igre. Dobar primjer je demo koji je napravio ATI za HD 4800 seriju kartica, naziva Froblins, u kojem ogromnim brojem “froblina” koji kopaju kamen, nose ga i spremaju, jedu kada nađu gljive i na kraju kad se umore odlaze na spavanje, upravljaju programi za sjenčanje geometrije. No mi ćemo ovdje ukratko opisati samo najbitnije primjene programa za sjenčanje geometrije.

Razina detalja prikaza

U računalnoj grafici, ovaj pojam podrazumijeva mijenjanje kompleksnosti prikaza trodimenzionalnih objekata u ovisnosti o udaljenosti od točke gledišta, važnosti objekta, brzini ili o nekom drugom parametru. Koristi se da poboljša performanse prikaza smanjujući opterećenje grafičkog sklopovlja, ponajprije onog zaduženog za transformaciju vrhova. Smanjena kvaliteta modela se često ne primjećuje zbog puno manjeg utjecaja objekta na izgled scene kad je udaljen ili se brzo kreće.

Iako se u većini slučajeva primjenjuje samo na geometriju, ova tehnika se može generalizirati. Nedavno su se počele implementirati i tehnike za upravljanje programima za sjenčanje fragmenata kontrolirajući kompleksnost prikaza.

Odbacivanje po projekcijskom volumenu

Odbacivanje po projekcijskom volumenu (engl. frustum-culling) koristi činjenicu da su svi poligoni izvan projekcionog volumena nevidljivi, tj. ono što kamera trenutno ne gleda, nije potrebno iscrtavati, te se ti poligoni odbace u programu za sjenčanje geometrije, stvarajući tako manje posla za programe za sjenčanje fragmenata.

Često se zna dogoditi da neki objekt leži na granici volumena. Takvi objekti se podijele na dijelove duž te granice, te se tada odbacuju samo oni dijelovi koji su van granica.

Preslikavanje s kocke u jednom prolazu

Osnovna ideja prilikom preslikavanja s kocke u jednom prolazu je da programu za sjenčanje geometrije proslijedimo šest matrica projekcije, i za svaku primitivu modela koji prikazujemo, generiramo šest drugih primitiva koje su projicirane svaka na svoju stranu kocke.

Ovim načinom izbjegli smo potrebu šesterostrukog prolaza kroz scenu, koji je dosad bio uobičajeni način preslikavanja s kocke, a samim time i smanjili broj poziva API funkcija šest puta.

Postoje optimizacije gore navedenog algoritma, koje ga još dodatno ubrzavaju koristeći instanciranje geometrije, no postupak u osnovi ostaje isti.

Implementacija

Kao demonstraciju navedenih tehnika i mogućnosti primjene programa za sjenčanje geometrije, za potrebe ovog rada implementirana su dva programa. Kao osnovno polazište su izabrane metode koje predlaže Fleischer u svom radu (Fleischer et al, 1995), uz modifikacije koje su uzimale u obzir ograničenja programa za sjenčanje geometrije, te potrebu da se program izvodi u realnom vremenu. Za razliku od predloženih metoda, koje samo za generiranje potrebne geometrije trebaju od od nekoliko sekundi pa do nekoliko sati, ovisno o tipu stanica, metode razvijene za potrebu ovog rada moraju generirati i izračunati projekciju cijele scene unutar 40-100ms. Zbog toga je kompleksnost nove stanične geometrije koja se generira za zadani model smanjena na razinu koja omogućava izvođenje u realnom vremenu na današnjem grafičkom sklopovlju.

Generiranje bodlji

Originalna ideja je vrlo jednostavna, izabrati slučajne nakupine trokuta na površini modela kojima susjedi nemaju već generirane bodlje, te za svaku od njih izvršiti program koji simulira rast bodlji sa određenim parametrima.

Prva bitnija promjena koja je napravljena s obzirom na originalnu zamisao je da se ne izabiru pojedine primitive za koje se program izvršava, već se izvršava za sve primitive. Razlog tomu je da dohvaćanje informacije o susjedima i već generiranoj geometriji u tom prolazu kroz scenu zahtjeva značajnije promjene u samom učitavanju i prikazu modela u memoriji, te implementaciju programa za sjenčanje geometrije koja podržava povratnu vezu transformacija (engl. transform feedback) što nadilazi okvire ovog rada.

Druga značajna promjena je u generiranju teksture za novu geometriju. Dok se u originalnoj metodi boja pojedinih vrhova generira ovisno o parametrima i proteklom vremenu simulacije ponašanja stanica, ovdje se uzima modifikacija Worleyeve metode generiranja staničnih tekstura. Dakle, na generiranu Worleyevu teksturu, dodajemo ili oduzimamo dodatni intenzitet koji ovisi o udaljenosti ostalih vrhova početnog poligona od vrha bodlje. Da bi se naglasila razlika, izabiremo boju koja je različita od boje korištene za prvobitno generiranu teksturu.

Originalni model iscrtan bez dodatnih programa za sjenčanje geometrije. Kugla je teksturirana običnom Worleyevom teksturom:

Isti model sa uključenim programima za sjenčanje geometrije:

Generiranje dlake

Originalni Fleischerov prijedlog postupka pomoću kojeg se generira dlaka se razlikuje od ostalih postupaka samo u programima koji se koriste za simulaciju rasta stanica. Razlika je ponovno u tome da se „rast“ novih stanica ne simulira izvodeći programe koji oponašaju ponašanje stanice, već da se cijela stanica generira u jednom prolazu.

Pošto je u ovom slučaju stanica oblika linije, kao zamjenu sam izabrao Bezierove krivulje. Bezierove krivulje su odabrane jer iako nisu računski prezahtjevne, još uvijek omogućavaju prilično fleksibilno modeliranje krivulje, što nam pomaže u postizanju prirodnog izgleda zakrivljene dlake.

Bezierove krivulje

Bezierove krivulje su parametarske krivulje koje se često koriste u računalnoj grafici i ostalim srodnim poljima računalnih znanosti. Generalizacija Bezierovih krivulja na više dimenzije su Bezierove površine.

U vektorskoj grafici, Bezierove krivulje se koriste za modeliranje glatkih krivulja kojima možemo proizvoljno mijenjati veličinu. Često se koriste i u vremenskoj domeni, naročito za animacije i dizajniranje sučelja. Na primjer, Bezierova krivulja se može koristiti da odredi brzinu objekta u različitim trenucima u vremenu, što kao posljedicu ima rezultat prirodnijeg gibanja pri micanju objekta s jednog mjesta na drugo, nego da samo postavimo brzinu na fiksni iznos.

Popularizirao ih je 1962. francuski inžinjer Pierre Bezier, koji ih je koristio za dizajniranje automobila. Razvijene su 1959. kada je Paul de Casteljau pronašao Casteljauov algoritam koji je rekurzivnom metodom izračunavao vrijednosti polinoma u Bernsteinovom obliku. Iako je algoritam sporiji nego izravni pristup, numerički je stabilniji.

Kubična Bezierova krivulja

Za potrebe ovog programa, izabrana je kubična Bezierova krivulja. Četiri kontrolne točke P0, P1, P2 i P3 u ravnini ili trodimenzionalnom prostoru definiraju kubičnu Bezierovu krivulju.

Krivulja počinje iz točke P0 ide prema točki P1 i dospijeva do P3 iz smjera P2. Obično, ne prolazi kroz točke P1 i P2; ove točke su jedino za određivanje smjera. Udaljenost između P0 i P1 određuje koliko dugo se krivulja kreće prema točki P2 prije nego skrene prema točki P3.

Implementacija u GLSL-u

Funkcije za izračunavanje krivulje implementirane su kao dio programa za sjenčanja geometrije, jer za svaku ulaznu primitivu moramo generirati jednu ili više Bezierovih krivulja. Postupak je podijeljen u tri logičke cjeline.

Prva je implementirana u funkciji evaluateBezierPosition koja kao parametre prima četverodimenzionalni vektor koordinata kontrolnih točaka v i vrijeme t, a vraća koordinate izračunate točke. Sama implementacija funkcije je sljedeća:

Druga funkcija koju smo implementirali je funkcija za računanje tangente u određenoj točci Bezierove krivulje. Naziv funkcije je evaluateBezierTangent, prima iste parametre kao evaluateBezierPosition, a vraća vektor tangente. Računanje vektora smjera tangente je jednostavno računanje prve derivacije:

Zadnja funkcija je emit_bezier koja dijeli krivulju na željeni broj segmenata, u ovom slučaju 8, i potom šalje segmente funkciji za transformaciju koja ih proslijeđuje programu za sjenčanje fragmenata. Parametri koje prima su kontrolne točke.

Na sljedećim slikama možemo vidjeti usporedbu rezultata sa samo pokrenutim programom koji učitava i iscrtava model, i identičnog programa sa uključenim programima za sjenčanje.


Glavni program

Zbog određenih zahtjeva ovog dijela rada u odnosu na dio koji se bavio samo programima za sjenčanje vrhova i fragmenata, za ovaj dio je napravljen zaseban i puno kompleksniji glavni program. Također, implementirana je mogućnost učitavanja Milkshape3D modela. Kako ovaj program sadrži mnogo veću količinu koda, nastala je potreba da ga se napravi u duhu objektno orijentiranog programiranja zbog lakšeg snalaženja. Druga bitnija razlika je da je ovaj program implementiran koristeći samo Win32 API i nativne OpenGL 3.2 funkcije, dakle uklonili smo potrebu za zastarjelim GLUT i GLEW bibliotekama, te ARB ekstenzijama.

Dijagram razreda projekta:

Razred Model implementiran je kao apstraktni razred, što nam olakšava posao ako budemo imali potrebu dodavati učitavanje različitih tipova modela. Ako želimo napraviti nekoliko prolazaka kroz scenu sa različitim programima za sjenčanje, samo trebamo napraviti željeni broj instanci razreda Shader i svakoj doznačiti željene programe. Isto tako, razred Shader se lako može nadograditi da prihvaća nove tipove programa za sjenčanje, poput programa za sjenčanje ljuske i površine koji su dio novog modela 5.0. Jedine poteškoće su bile rezultat toga da je OpenGL sučelje zamrznuto na verziju 1.1 na svim verzijama Windowsa, i standardne .h i .lib datoteke koje dolaze sa Microsoftovim prevoditeljima nisu ažurirane od 1995. Rješenje je korištenje novih glext.h, wglext.h i gl3.h zaglavnih datoteka koje se mogu naći na službenoj stranici OpenGL-a, a definiraju sve nove simbole i funkcije. Potom da bi pristupili novim OpenGL funkcijama, moramo deklarirati pokazivače na te funkcije na sljedeći način:

#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/wglext.h>

extern PFNGLACTIVETEXTUREPROC glActiveTexture;
PFNGLACTIVETEXTUREPROC glActiveTexture;  

Te potom dohvatiti pokazivače na funkcije pomoću wglGetProcAddress funkcije:

glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress("glActiveTexture");