Maparea mediului inconjurator reprezinta o metoda prin care putem interoga informatia din jurului unui obiect. Fie ca facem acest lucru pentru reflexii, refractii sau alte efecte mai complicate ideea este aceeasi: de a putea reprezenta intr-un mod eficient mediul inconjurator.
In imaginea de mai sus se poate observa cum o raza ce pleaca din camera se poate descompune in doua traiectorii distincte: una reflectata si una refractata. In realitate procesul este mai complicat dar pentru o iluzie vizuala acest model este suficient. Problema interesanta ce este usor de observat in acest desen este problema intersectiilor. Rasterizarea este un proces de intersectie coerent, bazat pe faptul ca intersectia intre primitiva si raster are loc intr-un mod cu localitate in mare masura garantata a fi buna. Problema cu un model bazat pe raze este ca nu mai avem mecanismul de rasterizare pentru a calcula foarte eficient intersectiile. Alternativele sunt raze, ca la ray-tracing, ce duc la un proces foarte costisitor si incoerent din punct de vedere al localitatii spatiale sau aproximari. Una din aceste aproximari se numeste cube mapping.
Un cubemap este un tip special de textura care este formata din 6 imagini: pozitiv x, negativ x, pozitiv y, negativ y, pozitiv z, negativ z. Aceste 6 imagini reprezinta o impartire a unui cub in 6 planuri, asa cum poate fi observat in imaginea urmatoare. Un cubemap reprezinta de obicei culoarea de la mediul inconjurator, pe toate directiile. Deci, un cubemap poate fi perceput ca un set de elemente de tip (directie, culoare). Un cubemap poate fi citit doar cu coordonate de texturare tridimensionale, pentru ca interogam culoarea din harta pentru directia (coordonata de texturare) introdusa. Mai mult cum coordonatele de texturare sunt de fapt directii, ele pot fi si negative.
Formatul in care veti gasi de obicei acest tip de textura este ca 6 imagini diferite sau ca o imagine cu urmatoarea forma.
Un cubemap este creat la fel ca orice alta textura, cu comanda:
glGenTextures(1, &gl_texture_object);
Cubemap-ul este legat la banda grafica la un nou punct de legare per unitatea de texturare curent legata, numit GL_TEXTURE_CUBE_MAP:
glBindTexture(GL_TEXTURE_CUBE_MAP, gl_texture_object);
Din punct de vedere al filtrarii procesul este identic cu cel al unei texturi bidimensionale, doar ca se poate folosi si:
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
care imbunatateste efectele vizuale la filtrarea de la marginea dintre fete. Pentru a introduce date in aceasta textura, se introduc pe rand toate cele 6 imagini, fiecare pe o pozitie specifica:
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data_posx); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data_posy); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data_posz); glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data_negx); glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data_negy); glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data_negz);
Pentru a fi utilizat in shader, un cubemap trebuie legat la banda grafica, proces identic cu cel pentru o textura 2D normala. In schimb, in shader se foloseste un sampler diferit, numit samplerCube care functioneaza doar cu coordonate de texturare tridimensionale.
Pentru a implementa reflexia si refractia se pot folosi functiile de glsl:
reflect( RazaIncidenta, Normala) refract( RazaIncidenta, Normala, indexMediuCurent/indexMediuNou)
ce ne ofera directia razei reflectate sau refractate. Dar cum cubemap-ul reprezinta de fapt o harta de culoare interogata prin directie, putem folosi razele reflectate/refractate din fragment ca si coordonate de texturare pentru a afla culoarea mediului din directia razelor.
Rezultatul final pentru o reflexie poate fi calculat cu:
vec3 culoarefragment = texture(cubemap, reflect(RazaIncidenta, Normala));
Crearea si utilizarea unui framebuffer a mai fost detaliata si in laboratorul 3. Astfel, initializarea si legarea framebuffer-ului se va face similar.
Particularitatea laboratorului curent este ca acest framebuffer va trebui sa contina o textura de culoare de tip cubemap.
Acest lucru se face prin legarea unei texturi utilizand tipul GL_TEXTURE_CUBE_MAP
.
glGenTextures(1, &color_texture); glBindTexture(GL_TEXTURE_CUBE_MAP, color_texture);
Lucrand cu o textura de tip cubemap, aceasta trebuie initializata pe toate cele 6 fete. Pentru acest lucru avem de asemenea la dispozitie tipuri dedicate pentru acestea:
Pentru a asocia texturi cu format de culoare pentru cele 6 fete, putem utiliza:
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); ...
Valoarea lor este incrementala, astfel ca se poate folosi si iterativ prin GL_TEXTURE_CUBE_MAP_POSITIVE_X+i
.
Pentru a asocia texturi cu format de adancime, putem utiliza:
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); ...
Pentru a crea un cubemap dinamic, la fiecare frame trebuie sa pozitionam camera pe obiectul pe care vrem sa aplicam cubemap-ul si sa facem capturi pentru toate cele 6 directii ale cubemap-ului.
Acest lucru se poate face prin mai multe modalitati:
In acest laborator vom aborda tehnica single pass. Astfel, in Geometry Shader, geometria trebuie redata de 6 ori, cate o data pentru fiecare directie a camerei.
Matricile de vizualizare se pot trimite separat, intr-o structura sau intr-un array. Exemplu:
glm::mat4 cubeView[6] = { glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f,-1.0f, 0.0f)), // +X glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f,-1.0f, 0.0f)), // -X glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), // +Y glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,-1.0f, 0.0f), glm::vec3(0.0f, 0.0f,-1.0f)), // -Y glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f,-1.0f, 0.0f)), // +Z glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f,-1.0f), glm::vec3(0.0f,-1.0f, 0.0f)), // -Z }; glUniformMatrix4fv(glGetUniformLocation(shader->GetProgramID(), "viewMatrices"), 6, GL_FALSE, glm::value_ptr(cubeView[0]));
Redarea stratificată este procesul prin care GS trimite primitive specifice la diferite straturi ale unui framebuffer. Acest lucru poate fi util pentru realizarea umbrelor bazate pe un cubmap sau pentru maparea mediului inconjurator folosind un cubemap, fără a fi nevoie să redați întreaga scenă de mai multe ori.
Redarea stratificată în GS funcționează prin două variabile speciale de ieșire:
out int gl_Layer; out int gl_ViewportIndex; // Necesită GL 4.1 sau ARB_viewport_array.
Ieșirea gl_Layer definește la ce strat din imaginea stratificată merge geometria. Fiecare vârf din primitivă trebuie să obțină același indice de strat.
gl_ViewportIndex, care necesită GL 4.1 sau ARB_viewport_array, specifică ce index de vizualizare să fie utilizat cu această primitivă, foarte util de exemplu pentru redarea in VR pentru cele 2 ecrane.
Astfel, putem folosi variabila gl_Layer pentru a specifica practic fata cubului dintr-un cubemap pe care se va reda, putand itera prin fiecare fata pentru redare stratificată. Cele 6 fețe au ordinea deja mentionata:
Layer number | Cubemap face |
0 | GL_TEXTURE_CUBE_MAP_POSITIVE_X |
1 | GL_TEXTURE_CUBE_MAP_NEGATIVE_X |
2 | GL_TEXTURE_CUBE_MAP_POSITIVE_Y |
3 | GL_TEXTURE_CUBE_MAP_NEGATIVE_Y |
4 | GL_TEXTURE_CUBE_MAP_POSITIVE_Z |
5 | GL_TEXTURE_CUBE_MAP_NEGATIVE_Z |
for (layer = 0; layer < 6; layer++) { gl_Layer = layer; //redarea primitivei din directia camerei specifica layer-ului curent, //folosind setul de matrici de vizualizare primite in variabila viewMatrices ... }
CubeMap.FS.glsl
CubeMap.FS.glsl
(se pot folosi tastele 1 si 2 pentru switch intre reflexie si refractie)Framebuffer.GS.glsl