Postupci teksturiranja upotrebom grafičkog procesora

Postupci preslikavanja tekstura

U ovom će poglavlju biti detaljnije razrađeno i objašnjeno nekoliko različitih postupaka preslikavanja tekstura upotrebom programirljivih grafičkih procesora. Također će biti prikazano koje su prednosti  i namjene određenih postupaka.

Teksturiranje je izvedeno korištenjem jedinice za sjenčanje fragmenata, a sav kod za program fragmenata je napisan u programskom jeziku GLSL.

Osnovna korištena metoda je dohvat teksture (eng. texture fetching).

 

Osnovno teksturiranje

Najjednostavnije teksturiranje je preslikavanje 2D slike na površinu 3D modela. U programskom jeziku GLSL za to se koristi varijabla tipa sampler2D koja se predaje funkciji texture2D() zajedno s koordinatama elementa teksture. Budući da se radi o 2D teksturi, koordinate su tipa vec2.

 

 

Slika 2-1: Preslikavanje teksture

 

Budući da su koordinate elementa teksture pridružene pojedinom vrhu poligona, a vrhovi se obrađuju u programu za sjenčanje vrhova, potrebno je koordinate iz tog programa prenijeti u program za sjenčanje fragmenata.

Za to nam služe varying varijable, koje program za sjenčanje vrhova može postavljati, a program za sjenčanje fragmenata može čitati.

Kako bi se teksturi moglo pristupiti iz programa za sjenčanje, potrebno ju je prvo učitati i postaviti u glavnom programu koristeći OpenGL API.

Ovaj postupak opisan je u sljedećem odsječku koda:

/* glavni program */

 

GLuint texID;      //identifikacijski broj teksture

 

// prvo se generira identifikacija teksture, svaka tekstura dobiva

// različiti broj preko kojeg joj se pristupa

glGenTextures(1, &texID);

 

// tekstura se postavlja kao radna (trenutna)

glBindTexture(GL_TEXTURE_2D, texID);

 

// postave se načini filtriranja ukoliko je potrebno povećati ili

// smanjiti teksturu

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

 

// ova funkcija čita podatke iz data i gradi potrebne mip-mape

// data      - pokazivač na učitanu teksturu u memoriji

// components – broj komponenti boje

// width     - širina slike

// height    - visina slike

// uzima još format (GL_RGB) i tip podataka (GL_UNSIGNED_BYTE)

gluBuild2DMipmaps( GL_TEXTURE_2D, components, width, height,

                   GL_RGB, GL_UNSIGNED_BYTE, data );  

Nakon što je učitana u glavnom programu, teksturi se može pristupati u programima za sjenčanje pomoću ugrađenih varijabli pa bi osnovni program za sjenčanje vrhova s podrškom za teksture izgledao ovako:

/* Program za sjenčanje vrhova */

void main() {

// varying vec4 gl_TexCoord[ ] – polje kojim se transformirane

// koordinate teksture prenose iz programa vrhova u program fragmenata

// gl_TextureMatrix[] – matrica za transformaciju koordinata tekstura

// attribute vec4 gl_MultiTexCoord0 – izvorne koordinate tekstura

  gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;

gl_Position = ftransform();

}

Program za vrhove uzima koordinate tekstura iz ugrađene varijable attribute vec4 gl_MultiTexCoord0 koja je obilježje pridruženo pojedinom vrhu te ih zatim transformira množeći s matricom za transformaciju tekstura gl_TextureMatrix[] i dobiveni rezultat prenosi programu za sjenčanje fragmenata preko varijable varying vec4 gl_TexCoord[] kao jedan od izlaza iz programa.

Program za sjenčanje fragmenata dobiva interpolirane vrijednosti varijable gl_TexCoord[] ovisno o položaju fragmenta između pripadnih vrhova.

Osnovni program fragmenata koji obavlja teksturiranje izgleda ovako:

/* Program za sjenčanje fragmenata */

 

uniform sampler2D tex;

 

void main(){

       gl_FragColor = texture2D(tex,gl_TexCoord[0].st);

}

U programu za sjenčanje fragmenata potrebno je imati varijablu za uzorkovanje teksture (sampler2D) kako bi se pozivom funkcije texture2D() dobila vrijednost elementa teksture na koordinatama gl_TexCoord[0] .

Dobivena vrijednost u programu se može iskoristiti na više načina, a u gornjem primjeru se izravno dodjeljuje boja fragmenta.

 

Slika 2-2: Rezultat izvođenja osnovnog teksturiranja pomoću grafičkog procesora

 

 

Višestruko teksturiranje

Višestruko teksturiranje je postupak preslikavanja više tekstura na istu površinu (poligon). Takav postupak otvara skoro neograničene mogućnosti kombiniranja različitih tekstura pri čemu svaka može određivati različita svojstva materijala.

Tako na primjer možemo postaviti jednu teksturu kao izvor osnovne boje, drugu teksturu kao izvor osvjetljenja (eng. ligth map), treću kao sliku okoline (eng. environment map) itd.

Teksturama možemo uvjerljivo prikazivati i detaljne neravnine (eng. bump) na površinama.

Sve novije grafičke kartice podržavaju višestruko teksturiranje, a pogotovo je zanimljivo taj postupak obavljati na jedinicama za sjenčanje jer nam omogućava da sami odredimo kako će se pojedina tekstura upotrijebiti.

Ako se koristi GLSL kao jezik za sjenčanje, dodavanje tekstura relativno je jednostavan postupak i sličan je osnovnom teksturiranju s tim da je potrebno koristiti više varijabli za pristup teksturama.

U programu za sjenčanje vrhova potrebno je samo dodati transformaciju koordinata za svaku teksturu koja se koristi:

/* Program za sjenčanje vrhova */

void main() {

// prva tekstura

gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;

 

// druga tekstura

gl_TexCoord[1] = gl_TextureMatrix[1] * gl_MultiTexCoord1;

 

gl_Position = ftransform();

}

 

Program za sjenčanje fragmenata mora za svaku korištenu teksturu imati po jednu varijablu za uzorkovanje tekstura.

Nakon što se dohvate vrijednosti različitih tekstura, s tim se vrijednostima može postupati kao i sa bilo kojom drugom varijablom pa je na programeru da odredi koja će biti krajnja boja fragmenta i kako će pojedina tekstura na to utjecati.

Program za sjenčanje fragmenata mora na izlazu dati boju fragmenta, ali nije određeno kako će tu boju dobiti niti je obvezno koristiti sve dostupne teksture.

 

Jedan primjer korištenja više tekstura u programu fragmenata:

/* Program za sjenčanje fragmenata */

 

uniform sampler2D tex1, tex2;

 

void main(){

vec4 color1 = texture2D(tex1,gl_TexCoord[0].st);

vec4 color2 = texture2D(tex2,gl_TexCoord[1].st);

 

gl_FragColor = color1*color2;

}

U ovom primjeru pretpostavljeno je da obje teksture predstavljaju boju materijala, a krajnja boja fragmenta dobivena je množenjem boja tekstura.

 

Slika 2-3: Množenje dviju tekstura preslikanih na kocku

 

Rezultat navedenih programa za sjenčanje prikazan je na Slici 2-3 gdje se može uočiti kako je pojedina tekstura preslikana i kako su obje spojene u završnu boju fragmenta.

 

 

Preslikavanje s kugle

Preslikavanje s kugle (eng. sphere mapping) je način preslikavanja okoline (eng. environment mapping) tako da se pomoću vektora odbijanja svjetlosti pronalazi koordinata teksture koja prikazuje sliku okoline na zamišljenoj zrcalnoj površini kugle (eng. sphere map). Ovakve teksture se mogu dobiti različitim postupcima, ali za potrebe ovog rada, biti će korištene gotove teksture dok će dobivanje potrebnih koordinata biti detaljnije objašnjeno.

Ukoliko se zamisli beskonačno udaljeni promatrač koji promatra zrcalnu kuglu, može se uočiti kako promatrač vidi odbijenu svjetlost koja iz svih smjerova dolazi na površinu kugle kao na Slici 2-4.

 

Slika 2-4: Odbijanje svjetlosti na površini kugle

 

 

Ako se sada zamisli da je kugla beskonačno mala tj. kugla postaje točka, u toj točki je moguće prikazati cijelu sliku okoline. Ta se slika projicira na 2D površinu kako bi dobili teksturu okoline (Slika 2-5).

 

Slika 2-5: Primjer teksture za preslikavanje s kugle

 

Postavlja se pitanje kako sada iz te 2D slike, ponovo napraviti zrcalni odraz okoline na 3D modelu. Odgovor na to pitanje leži u računanju vektora odbijanja svjetlosti na površini.

Kut upadne zrake na površinu lika i normale te ravnine jednak je kutu odbijene zrake ali na suprotnu stranu.

 

Slika 2-6: Upadna i odbijena zraka

 

 

Ako je poznat vektor normale i upadni vektor, uz uvjet da su ti vektori prethodno normirani, odbijenu zraku računamo po formuli:

 

Programski jezik GLSL nudi ugrađenu funkciju reflect() koja računa vektor smjera odbijanja.

Kada je pronađen vektor smjera odbijanja potrebno ga je projicirati na dvodimenzionalnu ravninu kako bi dobili potrebne koordinate teksture.

Projekcija se dobije prema formulama:

 

 

 

Svi vektori su jedinični vektori, a pojedine komponente vektora su u intervalu [-1, 1].

Budući da koordinate tekstura moraju biti u intervalu [0, 1], dobivene je brojeve potrebno skalirati na sljedeći način:

 

Varijable s i t  su tražene koordinate teksture u postupku preslikavanja s kugle.

Koristeći navedene formule može se napisati program za sjenčanje vrhova koji radi potrebne transformacije koordinata tekstura.

/* program za sjenčanje vrhova */

void main()

{

gl_Position = ftransform();        

 

gl_TexCoord[0] = gl_MultiTexCoord0;

 

vec3 U = normalize( vec3(gl_ModelViewMatrix * gl_Vertex) );

vec3 N = normalize( gl_NormalMatrix * gl_Normal );

vec3 R = reflect( U, N );

float m = 2.0 * sqrt( R.x*R.x + R.y*R.y + (R.z+1.0)*(R.z+1.0) );

gl_TexCoord[1].s = R.x/m + 0.5;

gl_TexCoord[1].t = R.y/m + 0.5;

}

Zapisane koordinate tekstura, čitaju se u programu za sjenčanje fragmenata u kojem se i računa krajnja boja fragmenta koristeći jednu običnu teksturu i jednu teksturu okoline kako bi se dobio izgled zrcaljenja.

/* program za sjenčanje fragmenata */

 

uniform sampler2D colorMap;

uniform sampler2D envMap;

 

const float reflection = 0.4; // faktor odbijanja svjetlosti

 

void main (void)

{

vec4 color = texture2D( colorMap, gl_TexCoord[0].st);

vec4 env = texture2D( envMap, gl_TexCoord[1].st);

 

gl_FragColor = color + env*reflection;

}

Izgled zrcalne površine možemo dodatno upotpuniti osvjetljenjem.

Osvjetljenje možemo računati po Phongovom modelu koji svjetlost rastavlja na tri komponente: svjetlost iz okoline (eng. ambient), raspršenu svjetlost (eng. diffuse) i zrcaljenu svjetlost (eng. speccular).

 

Slika 2-7: Odbijanje svjetlosti po Phongovom modelu

 

Prema Phongovom modelu osvjetljenje je najjače u smjeru odbijanja te zatim opada s kutem.

Potpuna jednadžba osvjetljenja glasi:

 

 

Pri čemu su s I označeni intenziteti svjetlosti (intenziteti pojedinih komponenti, svojstvo izvora svjetlosti), k koeficijenti odbijanja svjetlosti (svojstvo materijala), L vektor smjera svjetla, N vektor normale, R vektor smjera odbijanja i V vektor pogleda. Svi vektori moraju biti prethodno normirani.

Operator predstavlja skalarni produkt vektora, a u GLSL-u postoji i ugrađena funkcija koja obavlja ovu operaciju:

float dot( vec4, vec4 );

 

U kombinaciji s ovim modelom osvjetljenja, preslikavanje okoline s površine kugle daje relativno dobre rezultate i izgled zrcaljenja na površini modela.

Međutim, ovo je poprilično neprecizna metoda preslikavanja okoline jer je stvarna okolina samo aproksimirana teksturom. Također, zbog načina projiciranja okoline, projekcija je najkvalitetnija u sredini teksture, a prema rubu joj kvaliteta drastično opada.

Ponekad se pri uzorkovanju rubova teksture javljaju lako uočljive pogreške (artefakti).

 

Slika 2-8: Preslikavanje okoline s površine kugle i Phongov model osvjetljenja

 

Unatoč mnogim poteškoćama koje se javljaju kod ove metode, u nekim slučajevima daje vrlo  dobre rezultate, a zbog svoje iznimne jednostavnosti još se uvijek često koristi.

 

Preslikavanje s kocke

Za razliku od preslikavanja s kugle, metoda preslikavanja s kocke (eng. cube mapping) koristi teksturu na kojoj je okolina prikazana kao rastvorena kocka (slika 2-9). Zbog toga je ovakva tekstura zapravo sastavljena od šest manjih slika koje odgovaraju stranicama kocke.

Ovaj način preslikavanja okoline daje puno bolje rezultate od preslikavanja s kugle i nema nikakvih problema s pogreškama u prikazu jer projekcija nije zaobljena kao u slučaju preslikavanja s kugle te je potpuno neovisna o smjeru pogleda.

Nedostatak ovakvog postupka je nešto složenija izvedba i potreba za šest tekstura.

Šest strana kocke odgovara poluosima u trodimenzionalnom koordinatnom sustavu.

 

Slika 2-9: Preslikavanje okoline s površine kocke

 

Pri preslikavanju okoline ovim postupkom, svi vektori moraju biti izraženi u koordinatnom sustavu „svijeta“ tj. ne koristi se lokalni koordinatni sustav kamere i gledanog objekta već koordinatni sustav okoline s obzirom na objekt, koji je neovisan o pogledu.

Današnji grafički sklopovi nude podršku za ovaj način preslikavanja pa tako u programskom jeziku GLSL postoji tip varijable za uzorkovanje ovakvih tekstura samplerCube i pridružena funkcija textureCube() koja dohvaća željeni element teksture.

Funkcija textureCube() uzima varijablu za uzorkovanje te trodimenzionalni vektor koji odgovara smjeru odbijanja vektora pogleda na određeni vrh. Iz tog vektora, funkcija određuje koju stranu kocke treba uzorkovati, te zatim projicira vektor na tu plohu kako bi se dobile dvodimenzionalne koordinate elementa teksture.

 

Program za sjenčanje vrhova koji koristi metodu preslikavanje okoline s kocke u kombinaciji s osnovnom teksturom izgleda ovako:

/* Program za sjenčanje vrhova */

 

// matrica koja određuje položaj vrha u odnosu na okolinu

uniform mat4 ModelWorld4x4;

// pozicija pogleda u odnosu na okolinu

uniform vec4 vViewPosition;

 

void main( void )

{

   gl_Position = ftransform();     

    

   // podmatrica za transformaciju normale

   mat3 ModelWorld3x3 = mat3( vec3( ModelWorld4x4[0]),

                              vec3( ModelWorld4x4[1]),

                              vec3( ModelWorld4x4[2]) );

  

   // pozicija u odnosu na okolinu

   vec4 WorldPos = ModelWorld4x4 *  gl_Vertex;  

  

   // normala u odnosu na okolinu

   vec3 N = normalize( ModelWorld3x3 * gl_Normal );

  

   // vektor pogleda u odnosu na okolinu

   vec3 Eye = normalize( WorldPos.xyz - vViewPosition.xyz );  

  

   // vektor odbijanja svjetlosti

   gl_TexCoord[1].xyz = reflect( Eye, N );

 

   gl_TexCoord[0].xy  = gl_MultiTexCoord0.xy;     

}

 

 

Pripadajući program za sjenčanje fragmenata:

/* program za sjenčanje fragmenata */

 

uniform sampler2D baseMap;

uniform samplerCube cubeMap;

 

void main( void )

{ 

   // boja dobivena osvjetljenjem i osnovnom teksturom

   vec4 base_color = texture2D( baseMap, gl_TexCoord[0].xy );

     

   // dohvacanje elementa teksture okoline

   vec3 cube_color = textureCube(cubeMap, gl_TexCoord[1].xyz).rgb;

 

// konacna boja je kombinacija osnovne teksture i teksture okoline

   gl_FragColor = vec4((base_color.xyz+cube_color)/2, 1.0);     

}

 

Rezultat ovih programa za sjenčanje je model koji primjenjuje dvije teksture, jednu kao osnovnu boju površine, a drugu metodom preslikavanja okoline s površine kocke što daje vrlo uvjerljiv izgled zrcaljenja okoline.

Ako tome dodamo i Phongov model osvjetljenja, dobivena slika jako dobro oponaša stvarni izgled zrcaljenja na glatkim površinama.

 

Slika 2-10: Preslikavanje okoline s površine kocke