This shows you the differences between two versions of the page.
spg:laboratoare:03 [2023/09/28 23:28] andrei.lambru |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laboratorul 03 ====== | ||
- | ===== Obiecte de tip framebuffer si umbre ===== | ||
- | |||
- | ==== Introducere ==== | ||
- | |||
- | In acest laborator vom introduce atat elemente noi de OpenGL cat si o abordare pentru calcularea umbrelor facute de iluminarea unei surse de lumina de tip spot. Metoda prezentata aici se numeste metoda "maparii umbrelor", intalnita in engleza sub numele de "Shadow Mapping", ce a fost prezentata in cadrul cursului de EGC din anul III. O recomandare este sa revizualizati sectiunea de texturi din pagina de [[:spg:recapitulare]]. Aceasta recomandare revine mai jos in pagina acolo unde notiunile din cursul anterior sunt explicit necesare. | ||
- | |||
- | Prima parte a laboratorului se concentreaza doar pe descrierea obiectelor de tip framebuffer. Partea a doua reia sumar Metoda "maparii umbrelor" si ofera mai multe detalii doar despre pasii metodei ce tin de utilizarea obiectelor de tip framebuffer. | ||
- | |||
- | ==== Obiecte de tip framebuffer ==== | ||
- | |||
- | Redarea scenei in fereastra de desenare se realizeaza, de fapt prin redarea scenei intr-o textura speciala, ce este afisata ulterior in fereastra. OpenGL nu permite desenarea direct intr-o textura, ci impune utilizarea unui obiect suplimentar, numit buffer de cadru sau framebuffer. Acest obiect contine: | ||
- | * Textura in care se deseneaza. | ||
- | * Pot sa fie mai multe texturi pe care se deseneaza, pana la un numar limita dat de procesorul grafic, in general 8. In laboratorul 6 o sa vedem aplicatii pentru care este necesara desearea in mai multe texturi. Putem sa ne gandim la texturi ca la o structura de date in care pastram informatie oarecare, nu doar culoare. De exemplu: putem pastra pozitia in spatiul lume a fragmentului, obtinuta prin interpolare de la vertecsi, sau vectorul normal in spatiul lume a fragmentului, obtinut prin acelasi proces de interpolare etc. | ||
- | * Textura in care se pastreaza informatia de adancime a fragmentelor desenate in texturile anterioare. Aceasta informatie este utilizata in pasul de test de adancime din procesul de rasterizare. | ||
- | |||
- | Pentru a crea un obiect de tip framebuffer putem folosi directiva OpenGL: | ||
- | |||
- | <code> | ||
- | unsigned int framebuffer_object; | ||
- | |||
- | glGenFramebuffers(1, &framebuffer_object); | ||
- | </code> | ||
- | |||
- | Fereastra de desenare detine un framebuffer implicit, ce este creat automat in framework-ul de laborator prin intermediul librariei GLFW. Astfel, orice redare a scenei se realizeaza initial in texturile acestui framebuffer. Pentru a desena in texturile obiectului de tip framebuffer creat de noi mai sus sau pentru a modifica obiectul, este necesara legarea acestuia dupa cum urmeaza: | ||
- | <code> | ||
- | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object); | ||
- | </code> | ||
- | |||
- | In momentul de fata avem un framebuffer nou, gol, ce nu contine nicio textura de culoare sau de adancime, dar care este legat la banda grafica. Evident nu ne dorim un framebuffer gol pentru ca scopul unui framebuffer este de a retine date. Putem lega texturi la framebuffer si atunci cand vom reda o scena si obiectul de tip framebuffer va fi legat la banda grafica, procesul de desenare va scrie rezultatele in texturile obiectului legat de noi. | ||
- | |||
- | Reamintim ca pentru a crea o textura cu format de culoare, folosim urmatoarele directive: | ||
- | |||
- | <code> | ||
- | unsigned int color_texture; | ||
- | |||
- | glGenTextures(1, &color_texture); | ||
- | glBindTexture(GL_TEXTURE_2D, color_texture); | ||
- | |||
- | // Pixelii din interiorul texturii au formatul RGB | ||
- | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); | ||
- | </code> | ||
- | |||
- | <note tip> | ||
- | * Pentru a revizualiza mai multe detalii despre gestionarea texturilor in API-ul grafic OpenGL, puteti consulta sectiunea despre texturi din pagina de [[:spg:recapitulare]]. | ||
- | </note> | ||
- | |||
- | Pentru a atasa textura cu format de culoare (R, RG, RGB, RGBA), creata mai sus, la framebuffer folosim: | ||
- | <code> | ||
- | glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+pct_atasare, color_texture, 0); | ||
- | </code> | ||
- | |||
- | Astfel, atasam textura ''color_texture'' la obiectul de tip framebuffer legat la banda grafica pe punctul de atasare ''pct_atasare''. Valoarea ''0'' de la final ne spune ca atasam primul nivel din mipmap (rezolutia maxima). Dupa cum se poate observa, obiectele de tip framebuffer au puncte de atasare, foarte similare ca si concept cu ideea de pipe folosita la definirea informatiei la nivel de vertecsi. In API-ul grafic OpenGL, acest tip de proiectare este foarte des folosit. Daca atasam o textura la un punct de legare pe care deja este legata o alta textura, legatura veche se va pierde si va ramane doar cea noua. | ||
- | |||
- | Este important sa observam mai sus ca punctul de atasare este de tip GL_COLOR_ATTACHMENT. Mai exista un alt tip de punct de atasare, cu un singur punct (unic!) folosit pentru textura de adancime, numit GL_DEPTH_ATTACHEMENT. Pentru a lega o textura de adancime (cu format intern GL_DEPTH_COMPONENT) putem folosi comanda: | ||
- | |||
- | <code> | ||
- | glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_texture, 0); | ||
- | </code> | ||
- | |||
- | Crearea unei texturi ce contine informatie de adancime este similara cu cea care contine informatie de culoare, descrisa mai sus, cu exceptia formatului definit in directiva glTexImage2D: | ||
- | |||
- | <code> | ||
- | glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); | ||
- | </code> | ||
- | |||
- | Pentru a putea folosi obiectul de tip framebuffer creat mai sunt inca doua etapa de efectuat: setarea texturile cu format de culoare pe care se deseneaza si verificarea statusului de creare a obiectului. | ||
- | |||
- | Pentru a seta texturile de desenare folosim: | ||
- | <code> | ||
- | std::vector<GLenum> draw_textures; | ||
- | |||
- | draw_textures.push_back(GL_COLOR_ATTACHMENT0+attachment_index_color_texture); | ||
- | |||
- | glDrawBuffers(drawbuffers.size(),&draw_textures[0]); | ||
- | </code> | ||
- | |||
- | Pratic, cu directiva glDrawBuffers, setam care sunt texturile in care se deseneaza. In exemplul de mai sus, avem o singura textura atasata pe atasamentul de culoare cu numarul 0, pe care o adaugam intr-un vector pe pozitia 0. Daca avem obiectul de tip framebuffer in cauza legat la banda grafica si in fragment shader-ul utilizat pentru desenarea in acest framebuffer avem codul de mai jos, deoarece in out_color scriem un pixel rosu, toti pixelii texturii de culoare vor fi rosii. | ||
- | |||
- | <code> | ||
- | layout(location = 0) out vec4 out_color; | ||
- | |||
- | void main() | ||
- | { | ||
- | out_color = vec4(1, 0, 0, 0); | ||
- | } | ||
- | </code> | ||
- | |||
- | Dupa cum s-a mentionat mai sus, putem avea mai multe texturi cu format de culoare atasate unui framebuffer. Acest mecanim va fi prezentat mai in detaliu in laboratorul 6. Dar, ca o previzualizare a informatiei din acel laborator, codul de mai jos scrie in 4 texturi diferite, de tipuri diferite si se poate observa ca textura din atasamentul de culoare numarul 0 este complet rosie, iar cea din atasamentul de culoare numarul 1 este complet albastra. | ||
- | |||
- | <code> | ||
- | layout(location = 0) out vec4 out_color; | ||
- | layout(location = 1) out vec3 color2; | ||
- | layout(location = 2) out int int_texture; | ||
- | layout(location = 3) out float float_texture; | ||
- | |||
- | void main() | ||
- | { | ||
- | out_color = vec4 (1, 0, 0, 0); | ||
- | color2 = vec3(0, 0, 1); | ||
- | int_texture = 1; | ||
- | float_texture = 3.14; | ||
- | } | ||
- | </code> | ||
- | |||
- | In acest exemplu, pe atasamentul de culoare numarul 0 merge ce e scris in out_color, pe atasamentul de culoare cu numarul 1 merge ce este scris in color2, pe atasamentul de culoare cu numarul 2 merge ce este scris in some_int_buffer iar pe atasamentul de culoare cu numarul 3 merge ce este scris in some_float_buffer. Dupa cum se poate observa bufferele pot avea tipuri de date diferite. | ||
- | |||
- | Ultima etapa necesara inainte de folosirea obiectului de tip framebuffer este testarea corectitudinii crearii sale: | ||
- | |||
- | <code> | ||
- | glCheckFramebufferStatus(GL_FRAMEBUFFER); | ||
- | </code> | ||
- | |||
- | In momentul in care se doreste din nou redarea scenei in texturile obiectului de tip framebuffer implicit, putem folosi: | ||
- | |||
- | <code> | ||
- | glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||
- | </code> | ||
- | |||
- | ==== Metoda maparii umbrelor ==== | ||
- | |||
- | Noi vom utiliza metoda maparii umbrelor pentru a obtine efectul de umbre ale iluminarii realizate de catre o sursa de lumina de tip spot. Pentru a revizualiza mai multe detalii, va rugam sa consultati sectiunea aferenta acestui tip de sursa de lumina din [[:spg:recapitulare]]. | ||
- | |||
- | Metoda contine 2 pasi: | ||
- | - Redarea scenei intr-un framebuffer nou. Aceasta redare se realizeaza din pozitia sursei de lumina, pe directia de iluminare a sursei, specifica tipului de sursa spot. Practic, dorim sa vedem ceea ce "vede" sursa de lumina. Pentru simplitate, in laboratorul, sursa va avea un unghi de iluminare de 90 de grade, motiv pentru care se va folosi o proiectie perspectiva cu un unghi de vizualizare atat vertical cat si orizontal de 90 de grade. Obiectul de tip framebuffer obtinut in urma desenarii **contine in texturile lui toate punctele din scena iluminate de catre sursa de lumina**. | ||
- | - Redarea scenei in texturile obiectului de tip framebuffer implicit din perspectiva observatorului. In aceasta redare, se foloseste textura cu format de adancime obtinuta la pasul anterior. In fragment shader, fiecare fragment se verifica daca este iluminat de catre sursa de lumina sau nu. Daca pozitia in spatiul lume a fragmentului, obtinuta prin interpolare de la vertecsi, "apare" in texturile de culoare ale obiectului de tip framebuffer obtinut prin desenarea scenei de la pasul anterior, inseamna ca este iluminat si pentru acel fragment trebuie sa se calculeze intensitatea iluminarii. Acest "apare" este descris putin mai in detaliu mai jos. | ||
- | |||
- | <hidden> | ||
- | ===== Reflexii si Framebuffere ===== | ||
- | |||
- | ==== Introducere ==== | ||
- | |||
- | In acest laborator vom introduce elemente noi de OpenGL cat si un algoritm pentru calcularea reflexiilor. Cum rezolvarea corecta a problemei reflexiilor este o problema extrem de complicata in banda de rasterizare, vom introduce un algoritm primitiv ce ne va oferi un rezultat orientativ (deci nu matematic corect), suficient pentru a intelege si a reprezenta efectul de reflexie. | ||
- | |||
- | ==== Framebuffere ==== | ||
- | |||
- | Pana in momentul de fata am folosit tot timpul framebuffer-ul default oferit de GLFW. Desi am avut oportunitatea de a selecta intre mai multe configuratii de buffere (culoare, adancime, stencil) nu am avut capacitatea de ne defini manual acest framebuffer. In acest laborator vom invata sa lucram cu framebuffere si sa le folosim pentru a implementa algoritmul render to texture. | ||
- | |||
- | Pentru a crea un obiect de tip framebuffer putem folosi comanda: | ||
- | |||
- | <code> | ||
- | unsigned int framebuffer_object; | ||
- | |||
- | glGenFramebuffers(1, &framebuffer_object); | ||
- | </code> | ||
- | |||
- | iar pentru a lega framebuffer-ul nou creat la banda grafica (in locul celui default), folosim: | ||
- | <code> | ||
- | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object); | ||
- | </code> | ||
- | |||
- | In framework-ul nou de laborator aceste functii sunt incluse in clasa ''Framebuffer'' pe care puteti sa o folositi pentru aceste operatii: | ||
- | <code> | ||
- | frameBuffer = new FrameBuffer(); | ||
- | |||
- | frameBuffer->Bind(); | ||
- | </code> | ||
- | In momentul de fata avem un framebuffer nou, gol (nu are niciun buffer atasat) dar care este legat la banda grafica. Evident nu ne dorim un framebuffer gol pentru ca scopul unui framebuffer e de a retine date. Putem lega texturi la framebuffer si atunci cand vom desena o scena si framebuffer-ul va fi legat la banda grafica, procesul de desenare va scrie rezultatele (pixelii) in framebuffer-ul legat de noi. Acelasi proces se intampla si pentru framebuffer-ul default dar pana acum nu ne-am lovit de acest detaliu. | ||
- | |||
- | Pentru a atasa o textura cu format de culoare (R, RG, RGB, RGBA) deja creata la un framebuffer folosim comanda: | ||
- | <code> | ||
- | glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+pct_atasare, textura,0); | ||
- | </code> | ||
- | Astfel atasam textura ''textura'' la framebuffer-ul actual legat la banda grafica pe punctul de atasare pct_atasare. 0-ul de la final ne spune ca atasam primul nivel din mipmap (rezolutia maxima). Dupa cum se poate observa framebufferele au puncte de atasare, foarte similare ca si concept cu pipe-urile folosite la trimiterea atributelor de vertecsi. In OpenGL acest tip de design este foarte des folosit. Daca atasam o textura la un punct de legare pe care deja este legata o alta textura, legatura veche se va pierde si va ramane doar cea noua. | ||
- | |||
- | Mai putem observa ca punctul de atasare este de tip GL_COLOR_ATTACHMENT. Mai exista alt tip de punct de atasare, cu un singur punct (unic!) folosit pentru bufferul de adancime, numit GL_DEPTH_ATTACHEMENT. Pentru a lega o textura de adancime (cu format intern GL_DEPTH_COMPONENT) putem folosi comanda: | ||
- | <code> | ||
- | glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textura_adancime,0); | ||
- | </code> | ||
- | Motivul pentru care am dori sa legam o textura de adancime la un framebuffer este urmatorul: daca nu am avea un buffer de adancime atunci cum s-ar putea realiza testul de adancime stiind ca el are nevoie de un spatiu de stocare pentru adancimea de pe fiecare pixel? | ||
- | |||
- | Pentru a putea folosi framebuffer-ul mai sunt inca doua etapa de efectuat: setarea bufferelor de desenare si verificarea statusului framebuffer-ului. | ||
- | |||
- | Pentru a seta bufferele de desenare folosim: | ||
- | <code> | ||
- | std::vector<GLenum> drawbuffers; | ||
- | |||
- | drawbuffers.push_back(GL_COLOR_ATTACHMENT0+attachment_index_color_texture); | ||
- | |||
- | glDrawBuffers(drawbuffers.size(),&drawbuffers[0]); | ||
- | </code> | ||
- | Pratic, cu comanda glDrawBuffers setam care sunt bufferele in care OpenGL deseneaza. In exemplul de mai sus avem o singura textura atasata pe atasamentul de culoare cu numarul 0, pe care o adaugam intr-un vector pe pozitia 0. Daca avem framebuffer-ul in cauza legat la banda grafica si in fragment shaderul executat avem: | ||
- | <code> | ||
- | layout(location = 0) out vec4 out_color; | ||
- | </code> | ||
- | atunci orice este scris in out_color va fi scris in textura. Evident, acest mecanism poate functiona cu mai multe texturi, ca de exemplu: | ||
- | <code> | ||
- | layout(location = 0) out vec4 out_color; | ||
- | |||
- | layout(location = 1) out vec3 color2; | ||
- | |||
- | layout(location = 2) out int some_int_buffer; | ||
- | |||
- | layout(location = 3) out float some_float_buffer; | ||
- | </code> | ||
- | unde pe atasamentul de culoare numarul 0 merge ce e scris in out_color, pe atasamentul de culoare cu numarul 1 merge ce este scris in color2, pe atasamentul de culoare cu numarul 2 merge ce este scris in some_int_buffer iar pe atasamentul de culoare cu numarul 3 merge ce este scris in some_float_buffer. Dupa cum se poate observa bufferele pot avea tipuri de date diferite. | ||
- | |||
- | Ultima etapa necesara inainte de folosirea framebuffer-ului este testarea corectitudinii sale cu comanda: | ||
- | <code> | ||
- | glCheckFramebufferStatus(GL_FRAMEBUFFER); | ||
- | </code> | ||
- | Pentru a lega framebuffer-ul construit trebuie sa folosim | ||
- | <code> | ||
- | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_object); | ||
- | </code> | ||
- | iar pentru a lega framebuffer-ul default putem folosi: | ||
- | <code> | ||
- | glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||
- | </code> | ||
- | |||
- | Aveti deja construite aceste structuri in clasa Framebuffer din framework | ||
- | <code> | ||
- | frameBuffer->Generate(int width, int height, int nrTextures, bool hasDepthTexture, int precision) | ||
- | |||
- | FrameBuffer::BindDefault(); | ||
- | </code> | ||
- | |||
- | Pentru inaltimea si latimea necesara pentru textura puteti folosi dimensiunile ferestrei: | ||
- | <code> | ||
- | resolution = window->GetResolution(); | ||
- | width = resolution.x; | ||
- | height = resolution.y; | ||
- | </code> | ||
- | |||
- | ==== Render to texture ==== | ||
- | |||
- | Pentru a desena intr-una sau mai multe texturi folosim urmatorul proces: | ||
- | - Construim un framebuffer in care sunt legate toate texturile in care dorim sa scriem | ||
- | - Legam acest framebuffer la banda grafica | ||
- | - Facem glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); care va reinitializa valorile din bufferele din framebuffer-ul cu care lucram (pentru ca este legat la banda grafica in acest moment). | ||
- | - Desenam scena in mod normal, doar ca in fragment shader avem grija ca outputul sa mearga exact pe punctele de atasament specificate in framebuffer (dintr-o decizie de design de cod shaderul va fi identic, dar locatia 0 va avea sensuri diferite: 0 in framebuffer-ul default si 0 in framebuffer-ul creat de noi ) | ||
- | - Dezlegam framebuffer-ul de la banda grafica | ||
- | - Facem glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); care va reinitializa valorile din bufferele din framebuffer-ul **default **(legat acum la banda grafica) | ||
- | - Legam texturile de la framebuffer-ul pe care l-am construit la banda grafica (ele raman legate si la framebuffer) | ||
- | - Acum le putem mapa texturile precedente la orice obiect cu coordonate de texturare. | ||
- | |||
- | ==== Reflexia ==== | ||
- | |||
- | Reflexia este un fenomen care nu este reprezentabil corect in banda grafica fara a utiliza metode extrem de complicate (este inca o problema **open**). Din acest motiv nu ne propunem corectitudine ci doar sa simulam acest fenomen. | ||
- | <note> | ||
- | Observatie: | ||
- | |||
- | Se presupune ca studentii au implementat fragment shader, unde se poate aproxima reflexia difuza (Gouraud) si cea speculara (Phong). Algoritmul descris mai jos este o alta metoda de a simula reflexia speculara. | ||
- | </note> | ||
- | |||
- | Pentru aceasta consideram ca oglinda functioneaza la randul ei ca o camera si consideram ca imaginea generata de aceasta camera este apropiata de reflexia pe care am observa-o daca oglinda ar functiona corect din punct de vedere fizic. | ||
- | |||
- | Algoritm (folosind metoda Render to texture): | ||
- | |||
- | - Se creeaza un framebuffer care contine o textura de culoare direct mapabila pe geometria de suport a oglinzii. | ||
- | - Se leaga framebuffer-ul creat la punctul 1 la banda grafica. | ||
- | - Se deseneaza scena (fara oglinda) din perspectiva oglinzii. Rezultatul se salveaza in framebuffer-ul creat la punctul 1 (legat la banda grafica in acest moment). | ||
- | - Se dezleaga framebuffer-ul. Acum framebuffer-ul default este cel legat la banda grafica | ||
- | - Se deseneaza scena din perspectiva camerei din scena. | ||
- | - Se deseneaza onglinda folosind textura de culoare din framebuffer-ul de la punctul 1. | ||
- | |||
- | Pentru a folosi textura de pe atasamentul 0 dintr-un framebuffer construit cu clasa framebuffer se poate folosi functia BindTexture | ||
- | <code> | ||
- | frameBuffer->BindTexture(0, GL_TEXTURE0); | ||
- | </code> | ||
- | |||
- | |||
- | ==== Cerinte laborator ==== | ||
- | |||
- | - Descarcati [[https://github.com/UPB-Graphics/gfx-framework|framework-ul de laborator]] | ||
- | - Se creaza un framebuffer si texturile atasate | ||
- | - Se deseneaza in framebuffer scena din pozitia oglinzii | ||
- | - Se aplica textura pe oglinda pentru simularea reflexiei | ||
- | |||
- | |||
- | Rezultatul vizual al laboraturlui in varianta nerezolvata: | ||
- | {{ :spg:laboratoare:l3unresolved.png?300 |}} | ||
- | Rezultatul vizual al laboratorului in varianta rezolvata: | ||
- | {{ :spg:laboratoare:l3resolved.png?300 |}} | ||
- | </hidden> | ||
- | |||
- | <hidden>Cerinte | ||
- | |||
- | * se creaza un framebuffer si texturile atasate | ||
- | * se deseneaza in framebuffer scena din pozitia oglinzii | ||
- | * se aplica textura pe oglinda pentru simularea reflexiei | ||
- | * sa afiseze in locul reflectie adancimea vazuta de oglinda (rezultatul trebuie liniarizat cumva) | ||
- | </hidden> |