Un obiect de tip textură reprezintă o secvență continuă de informație în interiorul memoriei RAM a procesorului grafic. În particular, în cadrul acestui laborator, ne referim la obiecte de tip textură ce păstrează informația din interiorul unei grile de două dimensiuni.
Într-o grilă de două dimensiuni, putem stoca informația unei imagini :) în care, în fiecare celulă, păstrăm informația unui pixel.
Pentru a vizualiza toate tipurile posibile, puteți consulta documentația oficială: https://www.khronos.org/opengl/wiki/Texture#Theory.
Conținutul informației unei imagini se poate proiecta pe suprafața unui model 3D, respectiv pe suprafața triunghiurilor din care este compusă rețeaua de triunghiuri ce descrie modelul 3D, prin utilizarea unei informații suplimentare, în fiecare vârf, denumită coordonată de textură. O astfel de coordonată are două dimensiuni și fiecare componentă a ei, respectiv x și y, se află în intervalul [0,1]. Aceasta este coordonata în spațiul finit al grilei 2D stocate în obiectul de tip textură, spațiu delimitat în colțul din stânga-jos de coordonata (0, 0) și în colțul din dreapta-sus de coordonata (1, 1). Un astfel de spațiu poate fi utilizat pentru a eșantiona valorile din grilă.
Pentru a exemplifica, în partea stângă a imaginii de jos se poate observa un triunghi pentru care s-au asociat în vârfuri coordonate de textură: (0.7, 0.75), (0.1, 0.3) și (0.95, 0.2). Presupunem că prin procesul de rasterizare, s-a obținut un pixel ce se află în interiorul acestui triunghi și care a obținut prin interpolare de la vârfuri coordonata de textură (0.7, 0.55). Prin transformarea acestei coordonate în spațiul grilei 2D, obținem valoarea (716.8, 563.2).
Avem mai multe posibilități pentru a eșantiona o valoare din informația celulelor din grila 2D.
Se eșantioneaza direct valoarea celulei de la coordonata în spațiul grilei 2D, cu valoarea fiecărei componente a coordonatei rotunjită în jos. Astfel, pentru exemplul de sus, unde coordonata de texturare este (716.8, 563.2), se eșantionează valoarea celulei (716, 563).
O altă variantă utilizează o eșantionare a informației prin interpolare liniară de la toate celulele din jurul coordonatei, respectiv:
glm::vec3 c1 = glm::vec3(716, 563); glm::vec3 c2 = glm::vec3(716, 564); glm::vec3 c3 = glm::vec3(717, 563); glm::vec3 c4 = glm::vec3(717, 564); glm::vec3 c12 = glm::mix(c1, c2, 0.8); // din partea fractionara a componentei x, 716.8 glm::vec3 c34 = glm::mix(c3, c4, 0.8); // din partea fractionara a componentei x, 716.8 glm::vec3 c1234 = glm::mix(c12, c34, 0.2) // din partea fractionara a componentei y, 563.2
glm::mix()
reprezintă interpolare liniară:
$$ mix(a, b, t) = (1-t) \cdot a + t \cdot b $$
Conceptul de piramidă „mipma” a fost propus de Lance Williams în anul 1983 și constă în stocarea unei „piramide” de versiuni de diferite rezoluții ale unei imagini. Un astfel de exemplu se poate observa în imaginea de mai jos, în care în aceeași grilă, în partea stângă este stocată imaginea la rezoluția inițială, iar în partea dreaptă sunt stocate versiunile imaginii inițiale la rezoluție de 2, 4, 8, până la 128 de ori mai mică.
Această piramidă este utilă în momentul în care este dorită o eșantionare într-o vecinatate mai mare de 2×2. Situația aceasta apare în momentul în care prin procesul de interpolare, doi pixeli vecini obținuti prin rasterizare au atribuite coordonate de texturare ce in grila 2D se află la distanță mai mare de o celulă. De exemplu, dacă doi pixeli vecini se află la distanță de 50 de celule în grila 2D, putem considera că dimensiunea pixelului, în spațiul grilei 2D este de 50 de celule. Astfel, putem utiliza versiunile imaginii ce au rezoluția cea mai apropiată de dimensiunea pixelului. Pentru imaginea de mai sus, aceste versiuni au rezoluție de 64×64 și 32×32. Pentru eșantionare, avem două opțiuni.
Se eșantionează direct sau liniar DOAR versiunea imaginii ce are rezoluția cea mai apropiată. Pentru exemplul de mai sus, se alege versiunea imaginii de rezoluție 64×64.
Se eșantionează direct sau liniar cele două versiuni ale imaginii ce au rezoluția cea mai apropiată și se interpolează liniar între cele două valori obținute din eșantionarea celor două versiuni.
Pentru gestionarea unui obiect de tip textură în API-ul grafic OpenGL, trebuie creat un astfel de obiect:
unsigned int texture_id; glGenTextures(1, &texture_id);
Pentru a modifica informațiile stocate de obiectul de tip textură sau pentru a utiliza obiectul, el trebuie legat:
glBindTexture(GL_TEXTURE_2D, texture_id);
Pentru a asocia informație obiectului de tip textură, se utilizează:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glTexImage2D()
asociază primele celule din memorie cu linia din partea de jos a grilei 2D. Din acest motiv, putem considera că API-ul grafic OpenGL încarcă informația în memorie în formă rasturnată pentru componenta y.
Suplimentar, se pot specifica parametri pentru diferite procese în care este implicat obiectul de tip textură.
Pentru procesul de eșantionare, se pot specifica parametrii:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Pentru parametrul de filtrare, în situația în care doi pixeli vecini obținuti prin rasterizare au coordonate de textură la distanță mai mare de o celulă din grila 2D, prin parametrul GL_TEXTURE_MIN_FILTER
se poate specifica:
GL_NEAREST
- eșantionare directă doar din imaginea inițială.GL_LINEAR
- eșantionare liniară doar din imaginea inițială.GL_NEAREST_MIPMAP_NEAREST
: eșantionare directă din versiunea imaginii inițiale ce are rezoluția cea mai apropiată de dimensiunea pixelului în spațiul grilei 2D.GL_LINEAR_MIPMAP_NEAREST
: eșantionare liniară din versiunea imaginii inițiale ce are rezoluția cea mai apropiata de dimensiunea pixelului în spațiul grilei 2D.GL_NEAREST_MIPMAP_LINEAR
: interpolare liniară între valorile obținute prin eșantionare directă a versiunilor imaginii inițiale ce au rezoluțiile cele mai apropiate de dimensiunea pixelului în spațiul grilei 2D.GL_LINEAR_MIPMAP_LINEAR
: interpolare liniară între valorile obținute prin eșantionare liniară a versiunilor imaginii inițiale ce au rezoluțiile cele mai apropiate de dimensiunea pixelului în spațiul grilei 2D.
Pentru parametrul de filtrare, în situația în care doi pixeli vecini obținuti prin rasterizare au coordonate de textură la distanța mai mică de o celulă din grila 2D, prin parametrul GL_TEXTURE_MAG_FILTER
se poate specifica:
GL_NEAREST
- eșantionare directă doar din imaginea inițială.GL_LINEAR
- eșantionare liniară doar din imaginea inițială.Pentru mai multe detalii în legatură cu parametrii ce pot fi specificați, puteți consulta documentația oficială: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glTexParameter.xhtml.
Pentru a genera piramida „mipmap” se utilizează:
glGenerateMipmap(GL_TEXTURE_2D);
Procesul de eșantionare este realizat de o entitate autonomă implementată hardware sub forma unui procesor în interiorul procesorului grafic sau software în driver-ul companion procesorului. Această entitate autonomă este numită unitate de texturare și numărul lor este în general limitat la 32.
Pentru a utiliza procesul de eșantionare, trebuie să asociem obiectul de tip textură pe care dorim sa îl eșantionăm cu o unitate de texturare, numerotat cu identificatori de la 0 la 31 și să transmitem la un program de tip shader numărul unității de texturare:
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture_id); glUniform1i(glGetUniformLocation(shader->program, "texture_unit"), 0);
Pentru a utiliza unitatea de texturare, putem folosi într-un program de tip shader texture()
:
#version 330 uniform sampler2D texture_unit; in vec2 texture_coord; layout(location = 0) out vec4 out_color; void main() { vec4 color = texture(texture_unit, texture_coord); out_color = color; }
În interiorul memoriei stocate de un obiect de tip textură, se pot păstra informații generice.
Un prim exemplu este reprezentat de situația în care într-o grilă 2D se poate păstra informație de culoare în componentele rgb și o informație binară în componenta alpha. Această ultimă informație se poate interpreta astfel încât, pentru un pixel prelucrat de un program de tip fragment shader, pentru care pe baza coordonatelor de textură se eșantionează valoarea 1, i se asociază pixelului culoarea dată de atributul de ieșire a programului de tip fragment shader, iar unui pixel pentru care se eșantionează valoarea 0 nu i se asociază culoarea dată de atributul de ieșire. Un astfel de exemplu este în imaginea de mai jos, unde în partea stângă se prezintă doar valoarea componentei alpha.
Pentru a nu asocia pixelului culoarea de la atributul de ieșire al programului de tip fragment shader, se poate folosi directiva discard
:
vec4 color = texture2D(texture_unit, texture_coord); if(color.a < 0.5) { discard; }
Directiva discard
oprește programul de tip fragment shader la momentul apelului ei.
discard
pentru valori ale componentei alpha mai mici decât valoarea 0.5 pentru a se ține cont de posibilitatea utilizării unui proces de eșantionare liniară, ce se aplică și pentru componenta alpha.
Un alt exemplu de aplicare a utilizării obitectelor de tip textură pentru a stoca informații generice este utilizarea unei grile 2D pentru a păstra un deplasament în spațiul obiectului. Astfel, la desenarea unui plan, se poate modifica componenta y a coordonatei fiecărui vârf pe baza informației din grila 2D, proiectată pe toată suprafața planului. Un exemplu de imagine ce păstrează o astfel de informație se poate vedea mai jos:
Rezultatul deformării unui plan pe baza imaginii de mai sus este:
CreateTexture()
pentru a crea un obiect de tip textură.RenderSimpleMesh()
pentru a asocia o unitate de texturare fiecărei din cele 2 obiecte de tip textură și pentru a transmite la programul de tip shader numerele unităților de texturare asociate.LabShader.VS.glsl
calcularea valorii atributului de ieșire pentru coordonata de textură.LabShader.FS.glsl
eșantionarea informației dintr-un singur obiect de tip textură.discard
pentru a opri programele de tip fragment shader pe baza valorii componentei alpha eșantionate.Init()
, specificați coordonatele de textură ale vârfurilor unui patrulater. Aveți în vedere că directiva glTexImage2D()
asociază primele celule din memorie cu linia de jos a grilei 2D, astfel că este necesară inversarea componentei y a coordonatelor de textură.CreateStrippedTexture()
pentru a crea conținutul unei grile 2D ce conține o informație de culoare cu trei componente, rgb, în fiecare celulă. Grila 2D trebuie să conțină același triplet (r, g, b) în toate celulele ce se află pe aceeași linie. Altfel spus, toate valorile de culoare de pe o linie sunt identice. Culoarea celulelor unei linii este aleasă aleatoriu. Un exemplu de rezultat posibil este: vec3 color = mix(color1, color2, 0.5f);
Engine::GetElapsedTime()
.
Bonus: Utilizați imaginile lab08/images/snow.png
și lab08/images/water.png
, împreună cu valoarea de înălțime a fiecarui pixel, pentru a obține rezultatul de mai jos. Observați că trecerea de la zona de apă la cea de pământ este graduală. Același lucru este valabil și pentru trecerea de la zona de pământ la cea de zăpadă :) .