Postupci teksturiranja upotrebom grafičkog procesora

Programski jezik GLSL

Programski jezik GLSL (eng. OpenGL Shading Language) je jezik visokog nivoa namijenjen  isključivo programima za procesor vrhova i procesor fragmenata.

Specifikacija GLSL-a definirana je od strane ARB odbora (eng. Architectural Review Board) koji je zadužen i za sve odredbe u vezi OpenGL-a.

Jezik GLSL omogućuje da se na jednostavan način, koristeći poznatu sintaksu programskog jezika C, isprogramira funkcionalnost grafičkog procesora tj. jedinica za sjenčanje u grafičkom protočnom sustavu. Na taj se način daje sloboda programeru da utječe na obradu podataka u grafičkom protočnom sustavu i ostvari učinke koji inače ne bi bili mogući na unaprijed ugrađenoj (fiksnoj) funkcionalnosti grafičke kartice.

Osim GLSL-a postoji još nekoliko jezika koji pružaju slične mogućnosti, npr. Cg koji je razvila nVidia za svoje grafičke kartice ili Microsoftov HLSL koji radi s Direct3D API-jem.

GLSL je ovdje odabran zbog njegove potpune kompatibilnosti s OpenGL API-jem koji je korišten za izradu aplikacije koja prikazuje rezultate ovog rada.

.

Sintaksa

Sintaksa GLSL-a vrlo je slična sintaksi C-a uz neke dodatke i neka ograničenja koja odgovaraju bitno različitoj arhitekturi grafičkog procesora, u usporedbi s centralnim procesorom, na kojem se programi napisani u GLSL-u izvode.

Glavna funkcija u programu grafičkog procesora je funkcija main() od koje počinje izvršavanje. Stil pisanja je slobodan, a komentari se označuju ili između „/*“ i „*/“ ili se započinju s „//“ ako su u jednom redu.

 

Kao i u C-u postoji kontrola toka pomoću if i if else naredbi te petlji for, while i do while.

Pri korištenju kontrole toka bitno je znati koji model jedinica za sjenčanje podržava grafička kartica na kojoj će se program izvoditi.

Naime stariji modeli grafičkih procesora imaju ograničenu kontrolu toka do određene razine ili ju uopće ne dopuštaju. Tako npr. grafički procesori vrhova modela SM2.0 (eng. Shader Model) podržavaju samo kontrolu toka do dubine 24, dok model SM3.0 nema to ograničenje.

Također u programu vrhova treba razlikovati statičku kontrolu toka od dinamičke. Statička kontrola toka znači da tok ovisi o konstanti koja je ista za sve vrhove koji se crtaju jednim pozivom funkcije za crtanje, dok dinamička kontrola toka može mijenjati tok za svaki pojedini vrh.

Mogu se koristiti i ključne riječi break i continue.

 

Tipovi podataka

Osim osnovnih tipova podataka GLSL podržava i vektore, matrice i tipove podataka za uzorkovanje tekstura, a mogu se i definirati strukture podataka, kao i u C-u.

 

Osnovni tipovi podataka u GLSL-u:

·           int               - cjelobrojni tip podatka

·           float            - realni broj

·           bool            - Booleov tip podatka

·           void            - koristi se samo za funkcije koje ne vraćaju vrijednost

 

Vektori:

·           vec2, vec3, vec4     - vektori s dvije, tri ili četiri realne vrijednosti

·           ivec2, ivec3, ivec4  - vektori s dvije, tri ili četiri cjelobrojne vrijednosti

·           ivec2, ivec3, ivec4  - vektori s dvije, tri ili četiri Booleove vrijednosti

Pojedinim komponentama vektora može se pristupiti kao članovima strukture, a imena komponenata su:

·           x, y, z, w

·           r, g, b, a

·           s, t, p, q

Dozvoljeno je i referenciranje više članova odjednom pri čemu je redoslijed proizvoljan, ali sva imena komponenata moraju biti iz iste skupine.

 

Na primjer:

vec3 a = vec3(2.0, 4.0, 1.0);

vec2 b = a.zx;

Nakon inicijalizacije, vektor b će imati vrijednosti {1.0, 2.0}.

U navedenom primjeru, za inicijalizaciju je korišten konstruktor vec3(). Konstruktori pretvaraju jednu ili više vrijednosti nekog tipa u jednu vrijednost drugog tipa, a koriste se kao funkcije čije je ime jednako kao naziv tipa podatka koji se dobiva konstruktorom.

 

Matrice:

·           mat2, mat3, mat4               - matrice realnih vrijednosti (2x2, 3x3, 4x4)

·           mat2x2, mat2x3, mat2x4    - matrice različitih dimenzija

Podržane su matrice dimenzija 2×2, 2×3, 2×4, 3×2, 3×3, 3×4, 4×2, 4×3, i 4×4 realnih vrijednosti. Prvi broj u nazivu tipa je broj stupaca, a drugi broj redova.

Npr. tip matrice s četiri stupca i dva retka označava se sa:  mat4x2

Elementima matrice može se pristupiti kao da se radi o dvodimenzionalnom polju, npr:

 

mat2x3 m = mat2x3( 1.0, 1.0, 1.0,    //prvi stupac

                   2.0, 2.0, 2.0 );  //drugi stupac

 

m[0] = vec3(0.5, 0.5, 0.5);          //postavlja prvi stupac matrice

m[1][2] = 2.5;                       //postavlja 3 element u 2 stupcu

 

Nad matricama i vektorima definirani su operatori množenja (+), oduzimanja (-), množenja (*) i dijeljenja (/) na način da operacije odgovaraju matematičkom rješavanju istih operacija.

 

Podaci za uzorkovanje tekstura:

·           sampler1D  - tip podatka za uzorkovanje 1D teksture

·           sampler2D  - tip podatka za uzorkovanje 2D teksture

·           sampler3D  - tip podatka za uzorkovanje 3D teksture

·           samplerCube                       - tip podatka za uzorkovanje s kocke

·           sampler1DShadow             - tip podatka za preslikavanje 1D sjena

·           sampler2DShadow             - tip podatka za preslikavanje 2D sjena

 

 

Ovi tipovi podataka omogućuju pristupanje teksturama. Koriste se u kombinaciji s funkcijama za pretraživanje tekstura kako bi se dobio željeni element određene teksture.  Nad njima nisu definirani matematički ni logički operatori.

Pristupanje teksturama bit će prikazano u kasnijim primjerima.

 

Strukture se mogu definirati koristeći ključnu riječ struct na jednak način kao i u programskom jeziku C, te se isto tako pristupa pojedinim elementima strukture, npr:

struct struktura {

float foo;

float bar;

} foobar;

 

foobar.foo = 0.87;

Postoje strukture koje su unaprijed definirane, a koriste se za pristup postavkama scene pomoću automata OpenGL-a (eng. OpenGL state machine). Primjer su postavke svjetla i materijala.

 

U GLSL-u se mogu koristiti samo jednodimenzionalna polja. Veličina polja mora biti određena konstantom prije nego se polje može koristiti.

 

 Kvalifikatori i ugrađene varijable

Kvalifikator je oznaka koja određuje namjenu varijable tj. način na koji se može koristiti.

Varijablama označenim kao attribute i uniform može se pristupiti iz glavnog programa i mijenjati im vrijednost. Ovo je glavni način komunikacije između glavnog programa i programa vrhova i fragmenata.

 

Kvalifikator const

Kvalifikator const označava varijable čija se vrijednost nakon prevođenja programa ne može više mijenjati.

Ovim kvalifikatorom mogu se deklarirati lokalne i globalne varijable. Ovako deklarirane varijable mogu se samo čitati pa ih je potrebno inicijalizirati pri deklaraciji.

Primjer deklariranja const varijabli:

const float PI = 3.14;

const vec3 osX = vec3(1.0, 0.0, 0.0);

 

Kvalifikator uniform

Varijabla deklarirana kao unifom ima istu vrijednost za sve vrhove jednog lika. Koristi se za deklariranje globalnih varijabli kojima se želi pristupati iz glavnog programa kako bi se utjecalo na izvođenje programa za sjenčanje vrhova i programa za sjenčanje elemenata slike.

Kako bi se ovakvoj varijabli pristupalo iz glavnog programa, potrebno je prvo dobiti njenu memorijsku lokaciju pozivom funkcije:

GLint glGetUniformLocation(GLuint program, const char *name);

Parametri su:

program – upravljačka varijabla programa za sjenčanje

name – ime varijable

 

Navedena funkcija vraća lokaciju varijable kojoj se zatim dodjeljuje vrijednost nekom od sljedećih funkcija, ovisno o tipu varijable:

void glUniform1f(GLint location, GLfloat v0);
void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);

Parametri su:

location – lokacija varijable

v0, v1, v2, v3 – realne vrijednosti

 

Kvalifikator attribute

Kvalifikator attribute označava globalne varijable koje sadrže informacije o pojedinom vrhu, te za svaki vrh mogu biti različite. Ovakve varijable se mogu koristiti samo u procesoru vrhova i to samo za čitanje.

One također služe za prijenos podataka između glavnog programa i programa za sjenčanje vrhova, a pristupa im se na sličan način kao i uniform varijablama s time da se vrijednost ovih varijabli može mijenjati za svaki pojedini vrh.

 

 

Prvo se dohvaća lokacija varijable u memoriji funkcijom:

GLint glGetAttribLocation(GLuint program,char *name);

Parametri su:

program – upravljačka varijabla programa za sjenčanje

name – ime varijable

 

Zatim se dodjeljuje vrijednost ovisno o tipu varijable pozivom neke od funkcija:

void glVertexAttrib1f(GLint location, GLfloat v0);
void glVertexAttrib2f(GLint location, GLfloat v0, GLfloat v1);
void glVertexAttrib3f(GLint location, GLfloat v0, GLfloat v1,GLfloat v2);
void glVertexAttrib4f(GLint location, GLfloat v0, GLfloat v1,,GLfloat v2, GLfloat v3);

Parametri su:

location – lokacija varijable

v0, v1, v2, v3 – realne vrijednosti

 

Kvalifikator varying

Vrijednost globalne varijable deklarirane kvalifikatorom varying interpolira se na prijelazu iz procesora vrhova u procesor fragmenata. U procesoru vrhova u ovakvu varijablu se može pisati, a u procesoru fragmenata samo čitati.

Ove varijable služe za prijenos podataka iz programa za sjenčanje vrhova u program za sjenčanje elemenata slike.

 

 

Ugrađene varijable

Postoje varijable koje su unaprijed definirane u OpenGL API-u, a koriste se za postavljanje scene (svjetla, materijali, matrice) te omogućuju procesoru vrhova da na izlazu pošalje transformirane koordinate vrha, a procesoru fragmenata da upiše boju fragmenta u međuspremnik boje.

 

Imena tih varijabli počinju sa „gl_“, na primjer:

uniform gl_LightSourceParametars gl_LightSource[gl_MaxLights] ;

//sadrži sve podatke o svjetlima na sceni

 

attribute vec4 gl_Vertex;

//varijabla iz koje program vrhova čita koordinate vrha

 

vec4 gl_Position;

//program vrhova mora na izlazu dati transformirane koordinate vrha //tako da ih upiše u ovu varijablu

 

Ugrađene funkcije

Programski jezik GLSL nudi mnogo ugrađenih funkcija nad vektorskim i skalarnim podacima koje se mogu koristiti u programu za sjenčanje vrhova i programu za sjenčanje elemenata slike.

Ugrađene funkcije uvelike olakšavaju manipulaciju podacima, a u nekim slučajevima su i nezamjenjive kao što su funkcije za pregled tekstura.

Neke od operacija koje su podržane ovakvim funkcijama uključuju trigonometrijske funkcije, eksponencijalne funkcije, geometrijske funkcije, operacije nad matricama, relacije nad vektorima itd.

Ukoliko već postoji ugrađena funkcija za neku operaciju, preporuča se koristiti ju radije nego pisati svoju jer su ugrađene funkcije visoko optimirane, a mogu biti i sklopovski ubrzane.

 

Tablica 1-1: Primjeri nekih funkcija za pregled tekstura

Prototip funkcije

Opis funkcije

vec4 texture1D (sampler1D sampler,

float coord [, float bias] )

Iz teksture određene varijablom za uzorkovanje teksture sampler, pronalazi traženi element čije su koordinate određene vektorom realnih vrijednosti coord.

Pri tome se ovisno o postavkama može koristiti metoda mip-map kako bi tekstura bila pravilno filtrirana.

Funkcije vraćaju vektor koji predstavlja vrijednost elementa teksture, najčesće boju (4 kanala, crvena, zelena, plava i alfa kanal)

vec4 texture2D (sampler2D sampler,

vec2 coord [, float bias] )

vec4 texture3D (sampler3D sampler,

vec3 coord [, float bias] )

vec4 textureCube (samplerCube sampler,

vec3 coord [, float bias] )

 

Najbitnije u ovom radu su funkcije za pregled tekstura (neke su navedene u Tablici 1-1). Njima se kao argument predaje varijabla za uzorkovanje teksture (eng. sampler) i vektor koordinate teksture. Ove funkcije su dostupne u programu za sjenčanje vrhova kao i u programu za sjenčanje fragmenata.

 

Prevođenje, spajanje i učitavanje

Napisani programi vrhova i fragmenata se najčešće spremaju svaki u zasebnu datoteku te se onda iz glavnog programa učitaju, prevedu (kompiliraju) i spoje.

Programsko sučelje OpenGL nudi ugrađene funkcije za prevođenje, spajanje i učitavanje programa napisanih u programskom jeziku GLSL, a redoslijed izvođenja tih funkcija dan je slikom 1-1.

 

Slika 1-1: Prevođenje i spajanje programa vrhova i fragmenata funkcijama OpenGL-a

 

Pozivanjem funkcije glCeateShader() stvara se spremnik za program vrhova ili fragmenata.

GLuint glCreateShader(GLenum shaderType);

Argumenti:             shaderType – GL_VERTEX_SHADER ili GL_FRAGMENT_SHADER.

 

Funkcija vraća upravljačku varijablu za taj program koju zatim kao argument, zajedno sa pokazivačem na kod programa u memoriji, predajemo funkciji glShaderSource().

void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings);

Argumenti:             shader – upravljačka varijabla programa

numOfStrings – broj nizova znakova u polju

strings – polje nizova znakova

lenOfStrings – polje sa podacima o duljini nizova znakova

 

Program se zatim prevodi pozivanjem funkcije glCompileShader().

void glCompileShader(GLuint shader);

Argumenti: shader – upravljačka varijabla programa

 

Ovaj postupak se mora ponoviti za oba programa, program vrhova i program fragmenata.

Prevedeni programi se zatim moraju spojiti u jedan zajednički program na sljedeći način:

Stvori se spremnik za zajednički program funkcijom glCreateProgram() koja vraća upravljačku varijablu za taj program.

GLuint glCreateProgram(void);

 

Zajedničkom programu pridruže se zasebno program vrhova i program fragmenata pozivima funkcije glAttachShader().

void glAttachShader(GLuint program, GLuint shader);

Argumenti:           program – upravljačka varijabla zajedničkog programa

shader – upravljačka varijabla programa koji se pridružuje

 

Programi se spoje funkcijom glLinkProgram().

void glLinkProgram(GLuint program);

Argumenti:           program - upravljačka varijabla zajedničkog programa

 

Nakon spajanja program se stavlja u uporabu funkcijom glUseProgram() te se tako zamjenjuje ugrađena funkcionalnost grafičkog procesora ili prijašnji učitani program.

void glUseProgram(GLuint program);

Argumenti:           program – upravljačka varijabla zajedničkog programa

 

Ovakvih se programa može napraviti više i tako po potrebi koristiti program koji trenutno želimo.