Vous êtes sur la page 1sur 11

8/10/2014

FACULTATEA
DE
AUTOMATICA SI
CALCULATOARE

SISTEME DE PRELUCRARE
GRAFICA

Laborator 1

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Recapitulare
Introducere
In acest laborator vom recapitula cunostiinte de anul trecut de la EGC, continutul acestui suport de
laborator continand explicatii esentiale din laboratoarele de la EGC.
Pentru a lucra cu OpenGL trebuie sa folosim mai multe librarii:
- GLUT, ne ofera contextul, inputul si fereastra in care ruleaza programul.
- GLEW, ne ofera extensiile OpenGL
- GLM, ne ofera functii de matematica
OpenGL-ul modern (3.0+) este bazat exclusiv pe shadere. Un shader e un program executat de GPU si
e scris in limbajul GLSL(GL Shading Language). Acest program e atasat la banda grafica unde este
executat la nivel de vertex sau de fragment, dupa cum putem vedea in imaginea de mai jos.
Geometry shader-ul este subliniat pentru ca este ultima etapa invatata la EGC.

Meshe
O mesha este un obiect tridimensional definit prin varfuri si indecsi. In laborator exista cod
pentru a incarca obiecte din format .obj (https://en.wikipedia.org/wiki/Wavefront_.obj_file , in
detaliu aici: http://paulbourke.net/dataformats/obj/ ). Acest format este popular in mediul
aplicatiilor tridimensionale. Pentru a incarca o mesha puteti folosi functia:
Lab::loadObj(fisier.obj,vao, vbo, ibo, count);
VAO Vertex Array Object, reprezinta un container de stare
VBO Vertex Buffer Object, reprezinta un container de vertecsi
IBO Index Buffer Object, reprezinta un container de indecsi (mai sunt numiti si elemente)
Count - reprezinta numarul de indecsi ai obiectului incarcat si este necesar pentru comenzile
de desenare indexata.
Un vertex buffer object reprezinta un container in care stocam TOATE datele ce tin de
continutul vertecsilor. Pozitia in coordonate de modelare, normala (vom invata in laboratorul
Pagina 2

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

urmator la ce e folosita), culoarea, etc., toate sunt caracteristici ale unui vertex, care sunt numite in
OpenGL atribute.
Un vertex buffer object este create cu comanda:
Un vertex buffer object este distrus cu comanda:

glGenBuffers(1, &vbo_id);
glDeleteBuffers(1,&vbo_id);

Pentru a putea pune date intr-un buffer trebuie intai sa il MAPAM la un punct de legatura.
Pentru vertex buffer acest binding point se numeste GL_ARRAY_BUFFER, deci codul de stocare de
date intr-un VBO este:
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
glBufferData(GL_ARRAY_BUFFER,sizeof(Format)*nr_vertecsi,&vertecsi[0],GL_STATIC_D
RAW);

Prima comanda leaga VBO-ul la punctul de legare GL_ARRAY_BUFFER iar a doua comanda
citeste de la adresa primului vertex din array-ul de vertecsi si copiaza in VBO-ul mapat la
GL_ARRAY_BUFFER sizeof(Format)*nr_vertecsi bytes de memorie. GL_STATIC_DRAW reprezinta un
hint pentru driverul video in ceea ce priveste metoda de utilizare a bufferului. Format reprezinta
structura unui vertex, de exemplu:
struct VertexFormat{
glm::vec3 pozitie;
glm::vec3 culoare;
glm::vec3 viteza;
}
Un index buffer object (numit si element buffer object) reprezinta un container in care
stocam TOTI indecsii. Cum VBO si IBO sunt buffere, ele sunt extrem de similare in constructie,
incarcare de date si destructie.
glGenBuffers(1, &ibo_id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(unsigned
&indecsi[0], GL_STATIC_DRAW);

int)*nr_indecsi,

La fel ca la VBO, creem un IBO si apoi il legam la un punct de legatura, doar ca de data
aceasta punctul de legatura este GL_ELEMENT_ARRAY_BUFFER. Datele sunt trimise catre bufferul
mapat la acest punct de legatura, in cazul indecsilor toti vor fi de dimensiunea unui singur intreg.
Intr-un vertex array object putem stoca toata informatia legata de starea geometriei
desenate. Putem folosi un numar mare de buffere pentru a stoca fiecare din diferitele atribute
(separate buffers). Putem stoca mai multe(sau toate) atribute intr-un singur buffer (interleaved
buffers). Datorita acestei complexitati nu putem ca de fiecare data inainte de comanda de desenare
sa dam comenzile de binding pentru toate bufferele si atributele, de aceea se foloseste un vertex
array object care tine minte toate aceste legaturi.
Un vertex array object este creat cu :

glGenVertexArrays(1 , &vao_id);
Este legat cu: glBindVertexArray(vao_id);
Si este distrus cu: glDeleteVertexArrays(1,&vao_id);

Inainte de a crea VBO-urile si IBO-ul necesar pentru un obiect se leaga VAO-ul obiectului si
acesta va tine minte automat toate legaturile. Inainte de comanda de desenare este suficient sa
legam doar VAO-ul ca OpenGL sa stie toate legaturile create la constructia obiectului.

Pagina 3

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Desi am create toate aceste buffere cum specificam legatura intre date si shadere? prin
legare de atribute.
Legarea de atribute se poate face prin multe moduri distincte, ce au evoluat o data cu banda
grafica. In lumea OpenGL-ului modern (3.3+) este recomandata prima metoda, numita si metoda pe
layout-uri. In aceasta metoda se folosesc pipe-uri ce leaga un atribut din OpenGL de un nume de
atribut in shader.
OpenGL:
glEnableVertexAttribArray(5);
glVertexAttribPointer(5,3,GL_FLOAT,GL_FALSE,sizeof(MyVertexFormat),(void*)0);

Prima comanda seteaza pipe-ul cu numarul 5 ca fiind folosibil.


Cu a doua comanda trimitem pe pipe-ul 5 (argument 1) cate 3(argument 2) floaturi
(argument 3) pe care nu le normalizam(argument 4), adresa urmatorului atribut se afla peste
sizeof(MyVertexFormat) bytes (argument 5, se numeste stride), iar toate datele le citesc din buffer-ul
legat la GL_ARRAY_BUFFER, plecand cu offsetul initial 0 (argument 6).
Pe partea de shader folosim

layout(location = 5) in vec3 atributul_meu;

Daca nu exista acces la OpenGL 3.3, in care e introduce metoda pe layouturi, se poate folosi
metoda bazata pe cautare de locatie. Pe partea de OpenGL gasim pe ce pipe trimite OpenGL date
atributului cu urmatoarea comanda:
Unsigned int pipe = glGetAttribLocation(gl_program_shader, "atributul_meu");

Iar apoi putem folosi comenzile introduse in paragraful ulterior, doar ca pe noul pipe.
glEnableVertexAttribArray(pipe);
glVertexAttribPointer(pipe,3,GL_FLOAT,GL_FALSE,sizeof(MyVertexFormat),(void*)0);
Pe partea de GLSL definem atributele cu: attribute vec3 atributul_meu;

Dezavantajul acestei metoda fata de prima este ca avem nevoie ca shaderul sa existe pentru
a putea apela comenzile. Mai mult, cu aceasta metoda nu putem folosi mai multe shadere cu inputuri
diferite in acelasi timp.

Daca nici aceasta metoda nu este disponibila datorita necesitatii de a lucra cu o versiune mai
veche de OpenGL, atunci putem folosi metoda bazata pe setare de locatie. Pe partea de OpenGL
putem folosi urmatoarea comanda pentru a lega la nivel de shader un pipe de un nume de atribut:
glBindAttribLocation(gl_program_shader, pipe , "in_position");

Ca mai apoi sa folosim comenzile introduse cu prima metoda:


glEnableVertexAttribArray(pipe);
glVertexAttribPointer(pipe,3,GL_FLOAT,GL_FALSE,sizeof(MyVertexFormat),(void*)0);
Pe partea de GLSL definem atributele cu: attribute vec3 atributul_meu;
Dupa acest proces trebui sa re-linkam shaderul!!!

Pagina 4

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Dezavantajul acestei metode este ca avem nevoie ca shaderul sa existe, nu putem folosi
tehnica de legare cu mai multe shadere in acelasi timp si ca trebuie sa re-linkam shaderul dupa
crearea legaturilor.
In cazul putin probabil in care nu dorim sa folosim nici un din metodele deja prezentate
putem folosi legarea implicita, in care pe partea de OpenGL folosim doar :
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(MyVertexFormat),(void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(MyVertexFormat),(void*)0);
...

Iar pe partea de GLSL nu avem decat:


in vec3 atributul_meu;

sau
attribute vec3 atributul_meu

Ordinea in care declaram atributele in shader este TEORETIC aceeasi cu ordinea de legatura
de date, deci primul declarat e legat implicit la pipe-ul 0, al doilea la pipe-ul 1, samd. Dezavantajul
acestei metode este ca nu avem garantia acestui comportament si putem ajunge usor la
comportament nedefinit.

Tipuri de shadere
Un VERTEX SHADER e un program care se executa pentru fiecare vertex trimis catre banda
grafica. Este neaparata nevoie ca un vertex shader sa scrie ceva in gl_Position, pentru ca valoarea
scrisa reprezinta coordonata post-proiectie a vertexului procesat care e folosita apoi de banda
grafica. Un vertex shader are tot timpul o functie numita main. Un exemplu de vertex shader:
#version 330
layout(location = 0) in vec3 in_position;
uniform mat4 model_matrix, view_matrix, projection_matrix;
void main(){
gl_Position = projection_matrix*view_matrix*model_matrix*vec4(in_position,1);
}
Un FRAGMENT SHADER e un program ce este executat pentru fiecare fragment generat de
rasterizator. Fragment shader are tot timpul o functie numita main. Un exemplu de fragment shader:
#version 330
layout(location = 0) out vec4 out_color;
void main(){
out_color = vec4(1,0,0,0);
}

GEOMETRY SHADER-ul este singura etapa programabila ce lucreaza direct la nivel de


primitiva, avand access la toate informatiile din toti vertecsii primitivei de input. Primitivele de input
pot fi puncte, linii, triunghiuri sau variantele acestora cu adiacenta (pe care nu le vom folosi).
Geometry shader-ul poate primi primitive de un tip si poate scoate primitive de un tip complet
diferit! Un exemplu de geometry shader:
Pagina 5

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

#version 330
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
in vec2 in_texcoord[];
out vec2 texcoord;
void main(){
texcoord = in_texcoord[0];
gl_Position = gl_in[0].gl_Position;
EmitVertex();
texcoord = in_texcoord[1];
gl_Position = gl_in[1].gl_Position;
EmitVertex();
texcoord = in_texcoord[2];
gl_Position = gl_in[2].gl_Position;
EmitVertex();
EndPrimitive();
}
layout(triangles) in ne spune ca geometry shaderul citeste triunghiuri de la etapele precedente din
banda grafica (Vertex shader) iar layout(triangle_strip, max_vertices =3) out ne spune ca geometry
shaderul trimite mai departe la impartirea perspectiva si apoi rasterizare triangle strip-uri, cu un
numar maxim de 3 varfuri.
Cu structura gl_in[] putem citi propietatile tinute default pentru fiecare vertex in banda grafica,
printre care este si pozitia. Acest lucru nu este valabil pentru toate atributele, pe celelalte trebuie sa
le trimitem manual. Dupa cum se poate observa in exemplu, inputul pentru atribute este de tip array
iar outputul este de valoare, consistent cu ideea ca geometry shaderul citeste primitive iar apoi pe
baza lor creeaza noi vertecsi si topologie noua.
EmitVertex() este o comanda ce emite un vertex cu atributele de output setate pana la comanda
curenta. Ex: gl_Position (pe care trebuie sa il outputam) si atributul manual texcoord.
EndPrimitive() este o comanda ce semnaleaza terminarea primitivei. Cu aceasta comanda putem
crea topologie.
In exemplul dat, geometry shaderul outputeaza exact vertecsii si topologia primita, acest tip de
geometry shader fiind numit si pass-through.
Geometry shader-ul mai poate fi folosit si pentru instantiere, un proces prin care se deseneaza de
mai multe acelasi obiect cu transformari diferite.
De exemplu, in imaginea urmatoarea arborele a fost instantiat in geometry shader de N=6 ori, fiecare
din instante fiind translatata progresiv. Trebuie mentionat totusi ca geometry shader-ul nu este
etapa programabila ideala pentru procesul de amplificare de geometrie, acest proces fiind mult mai
eficient cu etapele de teselare (pe care nu le invatam).

Pagina 6

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Texturi
Texturile nu sunt doar imagini. Sunt imagini esantionate de un proces. Esantionatorul, numit si filtru
este elementul cheie care face diferenta intre o imagine si o textura. Texturile sunt legate de
geometrie prin coordonate de textura si se face intr-un spatiu parametric, deci in intervalul (0,1).
Pentru a construi o textura in OpenGL avem nevoie in primul rand de pixelii imaginii ce va fi folosita
ca textura. Acestia trebuie ori generati functional ori trebuie incarcati dintr-o imagine, iar acest task
este independent de OpenGL. In codul de laborator folosim incarcare de imagini din format BMP
datorita faptului ca este un format comun si usor de parsat. Exista mai multe librarii de incarcare de
imagini, inclusiv incarcare automata a imaginilor ca si texturi de OpenGL precum Corona, DevIL, etc.
Dupa ce avem pixelii imaginii incarcate putem genera o textura de OpenGL cu comanda:
unsigned int gl_texture_object;
glGenTextures(1, &gl_texture_object);

Similar cu toate celelalte procese din OpenGL nu lucram direct cu textura ci trebuie sa o mapam la un
punct de legare. Mai mult, la randul lor punctele de legare pentru texturi sunt dependente de
unitatile de texturare. O unitate de texturare e foarte similara ca si concept cu pipe-urile pe care
trimitem atribute.
Setam unitatea de texturare cu comanda (o singura unitate de texturare poate fi activa):
glActiveTexture(GL_TEXTURE0+nr_unitatii_de_texturare_dorite);

Iar pentru a lega obiectul de tip textura generat anterior la unitatea de textura activa folosim:
glBindTexture(GL_TEXTURE_2D, gl_texture_object);

Pagina 7

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Pentru a incarca date intr-o textura folosim comanda:


glTexImage2D(GL_TEXTURE_2D,
data);

0,GL_RGB,

width,

height,

0,

GL_RGB,

GL_UNSIGNED_BYTE,

comanda incarca o imagine definita prin un array de unsigned char-uri pe textura legata a punctul de
legare GL_TEXTURE_2D al unitatii de texturare legate curent, nivelul 0 (o sa luam aceasta constanta
ca atare pentru moment), cu formatul intern GL_RGB cu lungimea width si cu inaltimea height, din
formatul GL_RGB. Datele citite sunt de tip GL_UNSIGNED_BYTE (adica unsigned char) si sunt citite de
la adresa data.
Pentru a folosi o textura in shader trebuie sa urmarit acest proces:
- Trebuie activata unitatea de texturare pe care dorim sa o folsim, cu comanda:
glActiveTexture(GL_TEXTURE0+unitate_texturare);
Trebuie legata textura pe care dorim sa o folosim la unitatea de texturare
glBindTexture(GL_TEXTURE_2D, textura);
Trebuie gasita locatia in shader folosind numele din shader al texturii
unsigned int locatie =glGetUniformLocation(gl_program_shader, "textura")

Trebuie trimis pe aceasta locatie numarul unitatii de texturare pe care e legata textura

glUniform1i( locatie, unitate_texturare);

Urmatorul shader este un exemplu de shader ce poate folosi legarea precedenta, unde texcoord
reprezinta coordonatele de texturare primite ca atribute in vertex shader si apoi pasate catre
rasterizator pentru interpolare:
#version 330
layout(location = 0) out vec4 out_color;
uniform sampler2D textura1;
in vec2 texcoord;
void main(){
out_color = texture(textura1, texcoord).xyz;
}

Pentru a utiliza o textura nu este suficient sa citim doar imaginea si sa o incarcam ca un obiect de tip
textura ci sa o si filtram. Ce inseamna filtrare? Teoria sistemelor ne spune ca este o metoda de
esantionare si reconstructie pe un semnal. Semnalul este realitatea(reala sau virtuala!) prinsa in poza
iar pixelii imaginii rezultate reprezinta esantioanele pe care le stocam din aceasta realitate.

Filtrare Texturi
Reconstructia reprezinta procesul prin care utilizand acesti pixeli putem obtine valori pentru oricare
din pozitiile in textura (adica nu neaparat exact la coordonatele din mijlocul pixelului, acolo unde a
fost esantionata realitatea in spatiul post proiectie).
Cea mai simpla metoda de filtrare e de a alege cel mai apropiat texel (sample de realitate) relativ la
coordonata de texturare unde ne dorim sa reconstruim realitatea. Cand folosim filtrarea nearest
alegem tot timpul cel mai apropiat punct de esantionare si ii folosim valoarea ca rezultat pentru
citirea de la coordonatele de texturare initiale. In imaginea de mai jos am folosi valoarea texelului
dreapta jos dintre texeli cu centrele colorate cu verde.

Pagina 8

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Putem face filtrare nearest cu comenzile:


glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

In cazul in care vom folosi filtrare biliniara, vom folosi cei 4 cei mai apropiati texeli si vom interpola
liniar intre perechile sus si jos si apoi liniar intre rezultatele obtinute. Astfel vom avea tot timpul
continuitate in spatiul culorii. Putem face filtrare bilineara cu comezile:
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Totusi, nu tot timpul texelii din imagine se vor mapa perfect pe pixelii ecranului. Aproape tot timpul
vom avea un raport diferit de 1. Putem observa acest fenomen in imaginea urmatoare:

Motivatia pentru un algoritm mai bun este urmatoarea: daca suntem in situatia in care raportul
texeli/pixeli este subunitar, adica am mai multi pixeli pentur un singur texel sunt in pozitia in care
fara algoritmi foarte avansati de reconstructie predictiva nu am solutie. Dar daca sunt in situatia in
care am raportul texeli/pixeli supraunitar atunci inseamna ca am mai multi texeli pe acelasi pixel si se
pune intrebarea : cum calculam culoare pixelului? Prin medie? Prin interpolare? Mai mult, trebui sa
consideram si performanta: de exemplu putem citi cate 500 de texeli per pixel daca geometria pe
care mapam textura e mica!
De aceea introducem un algoritm care rezolva toate acest probleme de filtare: filtrul trilinear. Atunci
cand incarc textura dintr-o imagine folosim functia:

Pagina 9

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

glGenerateMipmap(GL_TEXTURE_2D); se creeaza urmatoare structura de memorie (numita mipmap):

Atunci cand folosesc filtrare trilineara banda grafica determina pe baza raportului texeli/pixeli (1 e
ideal!) care sunt cele 2 cele mai apropiate nivele din mipmap. Se face filtrare biliniara pentru citirea
din amandoua nivelele alese iar apoi se face filtrare liniara intre rezultatele obtinute. Putem face
filtrare biliniara cu setarea:
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Chiar si aceasta metoda are probleme atunci cand privim geometria suport (pe care e mapata
textura) la unghiuri extreme,dupa cum se poate observa in imaginea urmatoare.

Pagina 10

SISTEME DE PRELUCRARE GRAFICA

Laborator 1

Daca in apropiere filtrarea de textura are o calitate similara in distanta mipmapurile duc la un
fenomen de blur datorita interpolarii. Ce se intampla? Problema este ca la distanta intra foarte multi
texeli pe acelasi pixel DAR modul in care intra e unul particular atunci cand lucram la un un unghi. In
cazul acesta foarte multi texeli de pe o SINGURA directie intra in pixel in timp ce putini de pe cealalta
directie conteaza. Acest fenomen se numeste ANISOTROPIC din cauza faptului ca nu este ISOTROPIC,
adica nu toate axele conteaza la fel de mult in rezultatul final.
Pentru a rezolva si aceasta problema, deci pentru a obtine rezultatul din imaginea din dreapta de mai
sus folosim filtrarea anisotropica, prin urmatoarele comenzi:
float maxAnisotropy;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);

Mai mult, atunci cand adresam textura la niste coordonate de texturare precum 1.1 sau -0.5 avem
mai multe optiuni. Putem considera ca exista o margine dupa care toate valorile sunt egale cu
aceasta margine, iar acest comportament de limitare il numim clamping. Practic, in acest caz 1.1 =
1 si -0.5 = 0; Il putem seta cu comenzile:
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

In acelasi timp putem considera ca dupa ce trec de marginile texturii aceasta se repeta, deci avem 1.1
= 0.1 si -0.5 = 0.5. Acest comportament de repetare il putem seta cu comenzile:
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

Observatie: exista multe alte subiecte care nu au fost atinse ce tin tot de texture: alinierea in
memorie, conversiile implicite din diferite formate, compresia, filtrarea functionala, etc, deci daca
intampinati o problema este necesar sa consultati documentatia!

Pagina 11

Vous aimerez peut-être aussi