This shows you the differences between two versions of the page.
ppbg:laboratoare:05 [2023/12/02 10:40] andrei.lambru |
ppbg:laboratoare:05 [2024/11/07 09:58] (current) andrei.lambru |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Laboratorul 05 ====== | ====== Laboratorul 05 ====== | ||
- | <note tip> | + | <note important> |
- | Pentru rezolvarea cerințelor din acest laborator, aveți nevoie de codul utilizat în rezolvarea cerințelor din cadrul laboratorului 3. În situatia în care nu ați rezolvat [[:ppbg:laboratoare:03|laboratorul 3]], va trebui să îl realizați mai întâi pe el și ulterior să reveniți la cerințele celui curent. | + | Pentru rezolvarea cerințelor din acest laborator, aveți nevoie de codul utilizat în rezolvarea cerințelor din cadrul laboratorului 4. În situatia în care nu ați rezolvat [[:ppbg:laboratoare:04|laboratorul 4]], va trebui să îl realizați mai întâi pe el și ulterior să reveniți la cerințele celui curent. |
- | + | ||
- | **Reamintire!!!** Puteți prezenta rezolvările cerințelor de până la 2 laboratoare, în fiecare săptămână. De exemplu, puteți prezenta laboratorul curent și pe cel din săptămâna anterioară, în totalitate sau parțial, inclusiv punctajul pentru cerința bonus :) . | + | |
</note> | </note> | ||
<note tip> | <note tip> | ||
- | Pentru rezolvarea cerințelor din cadrul acestui labroator: | + | **Reamintire!!!** Puteți prezenta rezolvările cerințelor de până la 2 laboratoare, în fiecare săptămână. De exemplu, puteți prezenta laboratorul curent și pe cel din săptămâna anterioară, în totalitate sau parțial, inclusiv punctajul pentru cerința bonus :) . |
- | - [[https://github.com/UPB-Graphics/gfx-framework-ppbg | Descărcați]] framwork-ul de laborator și copiați, din arhiva descărcată, directorul **Lab5**, în interiorul directorului //gfx-framework-ppbg\src\lab// din versiunea voastră de proiect. | + | |
- | - Adăugați în fișierul ''lab_list.h'', linia ''#include "lab/lab5/lab5.h"''. | + | |
- | - Folosiți din nou utilitarul CMake pentru a regenera proiectul. Pentru a vă reaminti procesul de realizare a setup-ului, puteți să reconsultați [[:ppbg:setup-framework | pagina]] dedicată acestui lucru. | + | |
</note> | </note> | ||
- | ===== Banda grafică programabilă ===== | + | ===== Aplicații grafice în timp real ===== |
- | În [[:ppbg:laboratoare:04#modele_3d|laboratorul anterior]], am analizat modalitatea prin care sunt stocate listele de vârfuri și de indici ce descriu un model 3D compus dintr-o rețea de triunghiuri. În API-ul grafic OpenGL, noi am utilizat 3 tipuri de obiecte diferite, un obiect ce păstrează buffer-ul de vârfuri (VBO), un obiect ce păstrează buffer-ul de indici (IBO) și un container ce le înglobează pe amândouă, VAO. Pentru desenare, informația listelor de vârfuri și indici trebuie să se regăsească în memoria RAM a procesorului grafic. | + | Aplicațiile grafice în timp real efectuează o desenare succesivă a câte unui cadru. Între 2 cadre succesive, parametrii de desenare, precum poziția și direcția de vizualizare a observatorului, transformările geometriei desenate sau alte elemente ce influențează desenarea, cum sunt informațiile unei surse de lumină, pot să difere. Utilizarea unui număr mare de cadre desenate pe secundă creează iluzia de animație continuă :) . |
- | În [[:ppbg:laboratoare:03##lantul_de_transformari_3d|laboratorul 3]], am analizat modalitatea prin care coordonatele vârfurilor unui model 3D sunt transformate din spațiul inițial în care au fost definite, numit **spațiul obiect**, la poziția și forma finală în scenă, al cărei spațiu este cunoscut sub numele de **spațiul lume**. Din acest spațiu, coordonatele vârfurilor sunt transformate în **spațiul de vizualizare** și ulterior în **spațiul de decupare**. După împărțirea perspectivă, decuparea triunghiurilor la limitele volumului de decupare, eliminarea triunghiurilor pe baza opțiunii de afișare a fețelor și transformarea în poarta de afișare, triunghiurile sunt transmise în procesul de rasterizare. Pe baza acestui proces, triunghiurile sunt desenate în grila de pixeli. Toți acești pași se regăsesc în imaginea de mai jos, unde este prezentată o privire de ansamblu a benzii grafice. | + | În framework-ul pus la dispozitie în cadrul acestui laborator, succesiunea de cadre se realizează în clasa ''world'', în metoda ''Run'', printr-o buclă care se oprește doar în momentul în care se închide fereastra. |
- | {{ :ppbg:laboratoare:shaders.png?600 |}} | + | <code cpp> |
+ | while (!window->ShouldClose()) | ||
+ | { | ||
+ | LoopUpdate(); | ||
+ | } | ||
+ | </code> | ||
- | Procesorul grafic, prin intermediul driver-ului video și a standardului API OpenGL, ne pune la dispoziție posibilitatea de a scrie programe de calculator ce sunt executate direct de către unitatea grafică de procesare. Pașii în care putem executa aceste programe sunt ficși în banda grafică. Există mai multe tipuri de astfel de programe, dar în acest laborator, vom analiza doar două. Toate tipurile de programe poartă denumirea în limba engleză de **shader**. De asemenea, pentru programarea fiecărui tip de program, se utilizează un limbaj special, particular pentru fiecare tip de program. Acest limbaj se numeste generic Limbaj de colorare OpenGL, sau în limba engleză, OpenGL Shading Language (**GLSL**). | + | ==== Analiza unui cadru ==== |
- | <note tip> | + | În interiorul acestei bucle, în metoda ''LoopUpdate'', se realizează următorul proces pentru fiecare cadru: |
- | Înainte de a merge mai departe, pentru a avea evidențiere și auto-completare pentru limbajul de programare GLSL, în mediul de dezvoltare Visual Studio, este recomandat să instalați o [[https://marketplace.visualstudio.com/items?itemName=DanielScherzer.GLSL2022| extensie specifică]] :) . Pentru versiuni mai vechi de Visual Studio 2022, puteți utiliza [[https://marketplace.visualstudio.com/items?itemName=DanielScherzer.GLSL|această extensie]]. | + | |
- | </note> | + | |
- | ==== Programe de tip shader pentru prelucrarea vârfurilor ==== | + | <code cpp> |
+ | // 1. Polls and buffers the events | ||
+ | window->PollEvents(); | ||
- | Pentru a prelucra informația fiecărui vârf în parte, se introduce un program de tip shader, denumit în limba engleză **Vertex Shader**. // Pentru fiecare vârf din lista de vârfuri, se execută **o singură instanță** de program de tip vertex shader.// Sintaxa lui este în felul următor: | + | // 2. Computes frame deltaTime in seconds |
+ | ComputeFrameDeltaTime(); | ||
- | <code glsl> | + | // 3. Calls the methods of the instance of InputController in the following order |
- | #version 330 | + | // OnWindowResize, OnMouseMove, OnMouseBtnPress, OnMouseBtnRelease, OnMouseScroll, OnKeyPress, OnMouseScroll, OnInputUpdate |
+ | // OnInputUpdate will be called each frame, the other functions are called only if an event is registered | ||
+ | window->UpdateObservers(); | ||
- | layout(location = 0) in vec3 v_position; | + | // 4. Frame processing |
- | layout(location = 1) in vec3 v_normal; | + | FrameStart(); |
- | layout(location = 2) in vec2 v_tex_coord; | + | Update(static_cast<float>(deltaTime)); |
- | layout(location = 3) in vec3 v_color; | + | FrameEnd(); |
- | // Uniform properties | + | // 5. Swap front and back buffers - image will be displayed to the screen |
- | uniform mat4 Model; | + | window->SwapBuffers(); |
- | uniform mat4 View; | + | |
- | uniform mat4 Projection; | + | |
- | + | ||
- | void main() | + | |
- | { | + | |
- | gl_Position = Projection * View * Model * vec4(v_position, 1.0); | + | |
- | } | + | |
</code> | </code> | ||
- | Directiva de mai jos specifică versiunea limbajului ce se dorește să fie utilizată. Limbajul GLSL are mai multe versiuni posibile. Directiva este obligatorie și trebuie să fie prima directivă din codul sursă al programului. | + | Pașii din codul de mai sus sunt: |
+ | - Fereastra din interfața grafică a sistemului de operare este creată prin intermediul bibliotecii GLFW. Tot prin această bibliotecă, obținem evenimente externe aplicației care se pot produce la nivelul ferestrei, precum apăsarea de către utilizator a unei taste sau a unui buton de la mouse, redimensionarea ferestrei sau închiderea ei. Vom reveni asupra acestor evenimente mai jos. | ||
+ | - Se calculează timpul de desenare a cadrului anterior, denumit ''deltaTime''. | ||
+ | - Se apelează metodele care tratează fiecare tip de eveniment extern ferestrei. Aceste metode se găsesc în fiecare clasă ''LabX'' și sunt discutate mai jos. | ||
+ | - Se apelează exact o dată o serie de metode ce se regăsesc în fiecare clasă ''LabX''. | ||
+ | - Se blochează procesul curent de pe CPU pentru a se aștepta încheierea tuturor proceselor de desenare realizate de către procesorul grafic. | ||
- | <code glsl> | + | ==== Interacțiunea cu utilizatorul ==== |
- | #version 330 | + | |
- | </code> | + | |
- | Următoarele atribute sunt date de intrare specifice unui singur vârf. După cum se poate observa, tipurile de date sunt similare bibliotecii ''glm''. Biblioteca respectă standardul limbajului de programare GLSL. | + | Interacțiunea utilizatorului cu fereastra poate fi apăsarea unei taste de la tastatură, apăsarea unui buton de la mouse, redimensionarea ferestrei sau închiderea ei. |
- | <code glsl> | + | Interacțiunea utilizatorului cu tastele, în situația în care fereastra este selectată, este de 3 feluri: |
- | layout(location = 0) in vec3 v_position; | + | - Apăsarea unei taste pentru prima dată în cadrul curent, cunoscută în limba engleză sub numele de **key press**. |
- | layout(location = 1) in vec3 v_normal; | + | - Neapăsarea unei taste la cadrul curent, în situația în care tasta a fost apăsată la cadrul anterior, cunoscută în limba engleză sub numele de **key release**. |
- | layout(location = 2) in vec2 v_tex_coord; | + | - Apăsarea unei taste la cadrul curent, indiferent când a fost apasată prima dată, cunoscută în limba engleză sub numele de **key hold**. |
- | layout(location = 3) in vec3 v_color; | + | |
- | </code> | + | |
- | În situația framework-ului utilizat în cadrul acestui laborator, aceste date sunt aceleași cu cele ce se regăsesc în structura ''VertexFormat''. Felul în care se specifică legătura dintre formatul informației unui vârf și formatul datelor de intrare de mai sus, din programul de tip vertex shader, va fi descris mai jos. | + | Interacțiunea utilizatorului cu butoanele de la mouse este de 3 feluri, similar ca în situația tastelor, descrisă mai sus. |
+ | |||
+ | În framework-ul de laborator, interacțiunea cu utilizatorul se realizează în metodele ''OnWindowResize'', ''OnMouseMove'', ''OnMouseBtnPress'', ''OnMouseBtnRelease'', ''OnMouseScroll'', ''OnKeyPress'' și ''OnMouseScroll'', ce sunt apelate pentru fiecare eveniment realizat într-un cadru. De exemplu: | ||
<code cpp> | <code cpp> | ||
- | struct VertexFormat | + | void Lab4::OnKeyPress(int key, int mods) |
- | { | + | |
- | glm::vec3 position; | + | |
- | glm::vec3 normal; | + | |
- | glm::vec2 text_coord; | + | |
- | glm::vec3 color; | + | |
- | }; | + | |
- | </code> | + | |
- | + | ||
- | Următoarele atribute sunt de asemenea date de intrare. Ele sunt //comune tuturor instanțelor de programe de tip vertex shader executate// pentru vârfurile din listă. De asemenea, aceste atribute sunt comune tuturor instanțelor executate, indiferent de tipul de shader. Acest tip de atribut poartă numele de **uniform** în limba engleză. | + | |
- | + | ||
- | <code glsl> | + | |
- | uniform mat4 Model; | + | |
- | uniform mat4 View; | + | |
- | uniform mat4 Projection; | + | |
- | </code> | + | |
- | + | ||
- | Toate programele de tip shader trebuie sa contina un ''void main()'' de unde se incepe executia codului. | + | |
- | + | ||
- | <code glsl> | + | |
- | void main() | + | |
{ | { | ||
- | gl_Position = Projection * View * Model * vec4(v_position, 1.0); | + | if (key == GLFW_KEY_R) { |
+ | printf("S-a apasat tasta R."); | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | Atributul ''gl_Position'' este //implicit în programul de tip vertex shader// și reprezintă **coordonatele vârfurilor în spațiul de decupare**. Conform lanțului de transformări, vizibil și în imaginea de mai sus, asupra coordonatelor inițiale ale vârfului ce se află în spațiul obiect, s-au aplicat pe rând transformările de modelare, de vizualizare și de proiecție. | + | Metoda ''OnKeyPress'' se apelează pentru fiecare tastă apăsată într-un cadru. Trebuie verificat în interiorul metodei, pe baza parametrilor, ce tastă a fost apasată. Pentru mai multe informații despre fiecare metodă în parte, vă rog să citiți detaliile din [[https://github.com/UPB-Graphics/gfx-framework-ppbg/blob/master/src/core/window/input_controller.h | fișierul header al clasei InputController]]. |
- | + | ||
- | ==== Programe de tip shader pentru prelucrarea pixelilor ==== | + | |
- | + | ||
- | Pentru //fiecare pixel ce a fost procesat de către rasterizator și pentru care trebuie să i se atribuie o culoare, rasterizatorul execută o instanță a programului pentru prelucrarea de pixeli//. Acest tip de program poate avea mai multe date de ieșire, dar în acest laborator ne vom concentra exclusiv pe //**culoarea pe care trebuie să o atribuie pixelului prelucrat**//. Tipul de program utilizat pentru prelucrarea pixelilor poartă numele, în limba engleză, de **fragment shader** sau **pixel shader**. | + | |
<note tip> | <note tip> | ||
- | Majoritatea API-urilor grafice adoptă termenul de **fragment shader**, deoarece aceste API-uri ne oferă posibilitatea de a executa mai multe instanțe de fragment shader per pixel, în situația în care dorim acest lucru. Această abordare poartă numele de eșantionare multiplă și mai multe detalii despre ea pot fi consultate în documentația oficială: https://www.khronos.org/opengl/wiki/Multisampling. | + | Apelul fiecărei metode se realizează în cadrul pasului 3 descris mai sus. |
</note> | </note> | ||
- | Sintaxa unui fragment shader este în felul următor: | + | Metoda ''OnInputUpdate'' are un statut special în cadrul framework-ului de laborator și este similară cu metoda ''Update'', mai exact este apelată în fiecare cadru exact o dată. Apelul ei se realizează în cadrul pasului 3 de mai sus, astfel că se apelează înainte de metoda ''Update''. Recomandarea este să utilizați această metodă când gestionați interacțiunea cu utilizatorul. |
- | <code glsl> | + | Suplimentar acestor metode specifice tratării interacțiunii cu utilizatorul, se poate folosi atributul ''window'', intern clasei, pentru a verifica existența anumitor evenimente: |
- | #version 330 | + | |
- | // Uniform properties | + | <code cpp> |
- | uniform mat4 Model; | + | if(window->KeyHold(GLFW_KEY_R) { |
- | uniform mat4 View; | + | printf("Tasta R este apasata."); |
- | uniform mat4 Projection; | + | } |
- | layout(location = 0) out vec4 out_color; | + | if (window->MouseHold(GLFW_MOUSE_BUTTON_1)) { |
- | + | printf("Butonul stanga de la mouse este apasat."); | |
- | void main() | + | |
- | { | + | |
- | out_color = vec4(1, 0, 0, 0); | + | |
} | } | ||
</code> | </code> | ||
- | Similar ca în situația programului de tip vertex shader, este obligatoriu ca prima directivă să fie cea care specifică versiunea utilizată pentru limbajul de programare GLSL. De asemenea, toate atributele de intrare de tip uniform sunt disponibile în fragment shader. În plus, și acest tip de program are un ''void main()'' din care se începe execuția codului. | + | ==== Animații independente de numărul de cadre desenate pe secundă ==== |
- | Atributul de mai jos este un atribut de ieșire pentru fragment shader. În situația acestui laborator, singurul atribut de ieșire dintr-un program de tip fragment shader este culoarea pixelului din grilă. | + | Modificarea proprietăților unui obiect între două cadre succesive trebuie realizată pe baza timpului care a trecut între cele două cadre, respectiv timpul trecut pentru desenarea cadrului anterior. Mai exact, în situația în care dorim să modificăm poziția unui obiect cu 5 unități de spațiu pe secundă, de-a lungul axei Z, în sens pozitiv, putem aplica următorul proces: |
- | <code glsl> | + | <code> |
- | layout(location = 0) out vec4 out_color; | + | object_position.z += 5 * 0.016; |
</code> | </code> | ||
- | Programul de mai jos asociază tuturor pixelilor culoarea roșie. | + | Valoarea 0.016 reprezintă timpul mediu de desenare a unui cadru la o frecvență de 60 de cadre pe secundă. Într-o secundă, poziția obiectului se va deplasa cu 5 unități de-a lungul axei Z, în sens pozitiv. Pentru a nu folosi direct valoarea aceasta, putem utiliza valoarea ''deltaTime'', ce reprezintă timpul de desenare a cadrului precedent și este primită sub formă parametru în metodele ''Update'' și ''OnInputUpdate''. |
- | <code glsl> | + | <code> |
- | void main() | + | object_position.z += 5 * deltaTime; |
- | { | + | |
- | out_color = vec4(1, 0, 0, 0); | + | |
- | } | + | |
</code> | </code> | ||
- | <note important> | ||
- | API-ul grafic OpenGL **NU** permite utilizarea în banda grafică a unui singur tip de program din cele două. Este necesar să se utilizeze ambele tipuri de programe. | ||
- | </note> | ||
- | ===== Gestionarea programelor de tip shader în API-ul grafic OpenGL ===== | + | ===== API-ul grafic OpenGL ===== |
- | În API-ul grafic OpenGL, toate programele de tip shader sunt **compilate și legate între ele** pentru a fi executate de către procesorul grafic **în timpul execuției aplicației grafice**. | + | O aplicație grafică în timp real utilizează procesorul grafic pentru a accelera desenarea unui cadru și a obține o frecvență mare de desenare a cadrelor. Deoarece procesoarele grafice prezintă diferențe mari unele față de celelalte, pentru a se evita implementarea desenării specific pentru fiecare tip de procesor grafic, au fost introduse mai multe standarde de programare a procesorului. Aceste standarde sunt implementate în interiorul driver-ului video. O privire de ansamblu a comunicării între o aplicație grafică în timp real și procesorul grafic este prezentată în imaginea de mai jos. Aceste standarde poartă numele de API-uri grafice și pentru a enumera doar cateva: OpenGL, Direct3D sau Metal. |
- | ==== Gestionarea unui obiect de tip shader ==== | + | {{ :ppbg:laboratoare:graphicsapi.png?500 |}} |
- | Pentru a gestiona un program de tip shader, în API-ul grafic OpenGL, trebuie să utilizăm un obiect de tip shader pentru fiecare din cele două tipuri de programe vertex shader și fragment shader, împreună cu un obiect suplimentar ce înglobează programele de tip shader utilizate. | + | Toate API-urile realizează în mare aceleași operații standard în cadrul implementării hardware din procesorul grafic pentru realizarea prelucrărilor în banda grafică. Diferența este dată de forma de transmitere a parametrilor pentru aceste operații. În cadrul acestui laborator, noi vom utiliza API-ul grafic OpenGL pentru programarea prelucrărilor în banda grafică. |
- | Pentru crearea unui obiect de tip shader, se utilizează: | + | ==== Curățarea informației de pe ecran ==== |
- | <code cpp> | + | Primul pas realizat la începutul unui cadru este curățarea grilei de pixeli și a grilei de valori de adâncime desenate la cadrul anterior. Acest proces se realizează în API-ul grafic OpenGL prin directiva: |
- | // GL_VERTEX_SHADER sau GL_FRAGMENT_SHADER, pe baza tipului dorit | + | |
- | unsigned int shader_id = glCreateShader(GL_VERTEX_SHADER); | + | |
- | + | ||
- | const char *shader_source = ...; | + | |
- | + | ||
- | glShaderSource(shader_id, 1, &shader_source, 0); | + | |
- | + | ||
- | glCompileShader(shader_id); | + | |
- | </code> | + | |
- | + | ||
- | Pentru legarea a două programe vertex shader și fragment shader, se utilizează: | + | |
<code cpp> | <code cpp> | ||
- | unsigned int program_id = glCreateProgram(); | + | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
- | + | ||
- | glAttachShader(program_id, vertex_shader_id); | + | |
- | glAttachShader(program_id, fragment_shader_id); | + | |
- | + | ||
- | glLinkProgram(program_id); | + | |
</code> | </code> | ||
- | Pentru desenarea cu cele două programe de tip shader create mai sus, se utilizează: | + | Informația din fiecare celulă a grilei de valori de adâncime se suprascrie cu valoarea 1. Aceasta reprezintă valoarea componentei z a feței din spate pentru volumul de decupare. Culoarea cu care se suprascrie fiecare celulă din grila de pixeli se poate stabili prin: |
<code cpp> | <code cpp> | ||
- | glUseProgram(program_id); | + | glClearColor(0, 0, 0, 1); |
- | + | ||
- | glBindVertexArray(VAO_id); | + | |
- | glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); | + | |
</code> | </code> | ||
- | ==== Specificarea formatului de date din lista de vârfuri ==== | + | ==== Poarta de afișare ==== |
- | API-ul grafic OpenGL utilizează memoria stocată în VBO. Pentru a specifica legătura dintre formatul datelor din lista de vârfuri și formatul datelor de intrare din programul de tip vertex shader, API-ul OpenGL pune la dispoziție directiva ''glVertexAttribPointer'': | + | Se poate stabili zona din ecran, poarta de afișare, în care să se deseneze obiectele prin directiva: |
<code cpp> | <code cpp> | ||
- | glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3) + sizeof(glm::vec2))); | + | glViewport(0, 0, 1280, 720); |
</code> | </code> | ||
- | Primul parametru reprezintă **locația** atributului de intrare din programul de tip vertex shader: | + | ==== Modele 3D ==== |
- | <code glsl> | + | Un model 3D, cunoscut în limba engleză sub numele de **3D mesh**, este un obiect tridimensional definit prin vârfuri și indici. În laborator aveți posibilitatea să încărcați modele 3D în aproape orice format posibil prin intermediul clasei [[https://github.com/UPB-Graphics/gfx-framework-ppbg/blob/master/src/core/gpu/mesh.h#L48|Mesh]]. |
- | layout(location = 3) in vec3 v_color; | + | |
- | </code> | + | |
- | Pentru exemplul de mai sus, specificarea formatului se realizează în felul următor: | ||
- | <code cpp> | + | === Vertex Buffer Object (VBO) === |
- | glBindVertexArray(VAO); | + | |
- | // Set vertex position attribute | + | Un vertex buffer object reprezintă un container în care stocăm date ce țin de conținutul vârfurilor precum: |
- | glEnableVertexAttribArray(0); | + | * poziție |
- | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), 0); | + | * normală |
+ | * culoare | ||
+ | * coordonate de texturare | ||
+ | * etc... | ||
- | // Set vertex normal attribute | + | Un vertex buffer object se poate crea prin comanda OpenGL **[[https://www.opengl.org/sdk/docs/man/html/glGenBuffers.xhtml|glGenBuffers]]**: |
- | glEnableVertexAttribArray(1); | + | <code cpp> |
- | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(sizeof(glm::vec3))); | + | unsigned int VBO_ID; // ID-ul (nume sau referinta) buffer-ului ce va fi cerut de la GPU |
- | + | glGenBuffers(1, &VBO_ID); // se genereaza ID-ul (numele) bufferului | |
- | // Set texture coordinate attribute | + | |
- | glEnableVertexAttribArray(2); | + | |
- | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3))); | + | |
- | + | ||
- | // Set vertex color attribute | + | |
- | glEnableVertexAttribArray(3); | + | |
- | glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3) + sizeof(glm::vec2))); | + | |
</code> | </code> | ||
<note> | <note> | ||
- | Pentru mai multe detalii despre directiva ''glVertexAttribPointer'', puteți consulta documentația oficială: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glVertexAttribPointer.xhtml. | + | Așa cum se poate vedea și din explicația API-ului, funcția [[https://www.opengl.org/sdk/docs/man/html/glGenBuffers.xhtml|glGenBuffers]] primește numărul de buffere ce trebuie generate cât și locația din memorie unde vor fi salvate referințele (ID-urile) generate.\\ |
+ | În exemplul de mai sus este generat doar 1 singur buffer iar ID-ul este salvat în variabila ''VBO_ID''. | ||
</note> | </note> | ||
- | ==== Transmiterea datelor de tip uniform la shader ==== | + | Pentru a distruge un VBO și astfel să eliberăm memoria de pe **GPU** se folosește comanda **[[https://www.opengl.org/sdk/docs/man4/html/glDeleteBuffers.xhtml|glDeleteBuffers]]**: |
+ | <code cpp> | ||
+ | glDeleteBuffers(1, &VBO_ID); | ||
+ | </code> | ||
- | Pentru a transmite un atribut de tip uniform la un program shader, API-ul grafic OpenGL ne pune la dispoziție mai multe directive, specifice fiecărui tip de date pe care îl permite limbajul de programare GLSL. Doar pentru a enumera câteva, directivele sunt: | + | Pentru a putea pune date într-un buffer trebuie întâi să legăm acest buffer la un „target”. Pentru un vertex buffer acest „binding point” se numește **GL_ARRAY_BUFFER** și se poate specifica prin comanda **[[https://www.khronos.org/opengles/sdk/1.1/docs/man/glBindBuffer.xml|glBindBuffer]]**: |
<code cpp> | <code cpp> | ||
- | glUseProgram(program_id); | + | glBindBuffer(GL_ARRAY_BUFFER, VBO_ID); |
- | + | ||
- | //void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) | + | |
- | glm::mat4 matrix(1.0f); | + | |
- | glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(matrix)); | + | |
- | + | ||
- | // void glUniform1f(GLint location, GLfloat v0) | + | |
- | glUniform1f(location, 1.5f); | + | |
- | + | ||
- | // void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) | + | |
- | glUniform4f(location, 1, 0.5f, 0.3f, 0); | + | |
- | + | ||
- | //void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2) | + | |
- | glUniform3i(location, 1, 2, 3); | + | |
- | + | ||
- | //void glUniform3fv(GLint location, GLsizei count, const GLfloat *value) | + | |
- | glm::vec3 color = glm::vec3(1.0f, 0.5f, 0.8f); | + | |
- | glUniform3fv(location, 1, glm::value_ptr(color)); | + | |
</code> | </code> | ||
- | API-ul grafic OpenGL nu impune obligativitatea utilizării unei locații explicite pentru atributele de tip uniform, dar această locație există implicit. Pentru a obține locația implicită a unui atribut, indiferent de tipul lui, utilizăm: | + | În acest moment putem să facem upload de date din memoria **CPU** către **GPU** prin intermediul comenzii **[[https://www.opengl.org/sdk/docs/man4/html/glBufferData.xhtml|glBufferData]]**: |
<code cpp> | <code cpp> | ||
- | int location = glGetUniformLocation(program_id, "uniform_variable_name_in_shader"); | + | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), &vertices[0], GL_STATIC_DRAW); |
</code> | </code> | ||
- | <note tip> | + | * Comanda citește de la adresa specificată, în exemplul de sus fiind adresa primului vârf ''&vertices[0]'', și copiază în memoria video dimensiunea specificată prin parametrul al 2-lea. |
- | Pentru mai multe detalii despre directivele de transmitere a atributelor de tip uniform, pe baza tipului, puteți consulta documentația oficială: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glUniform.xhtml . | + | * **GL_STATIC_DRAW** reprezintă un hint pentru driver-ul video în ceea ce privește metoda de utilizare a bufferului. Acest simbol poate avea mai multe valori dar în cadrul laboratorului este de ajuns specificarea prezentată. Mai multe informații găsiți pe pagina de manual a funcției [[https://www.opengl.org/sdk/docs/man4/html/glBufferData.xhtml|glBufferData]] |
- | </note> | + | |
- | ==== Comunicarea între vertex shader și fragment shader ==== | ||
- | |||
- | Pe lângă atributul implicit ''gl_Position'', un program de tip vertex shader poate avea atribute de ieșire explicite, ce devin atribute de intrare pentru următorul tip de program din banda grafică, respectiv pentru laboratorul curent, pentru programul de tip fragment shader. Există două posibilități de declarare a acestor atribute: | ||
- | * Utilizarea aceluiași nume pentru atribut în codul sursă al ambelor programe de tip shader, cum se poate vedea în continuare: | ||
- | * Vertex shader: <code glsl> | ||
- | out vec3 attribute_name; | ||
- | </code> | ||
- | * Fragment shader: <code glsl> | ||
- | in vec3 attribute_name; | ||
- | </code> | ||
- | * Utilizarea aceleiași locații explicite pentru atribut, după cum urmează: | ||
- | * Vertex shader: <code glsl> | ||
- | layout(location = 3) out vec3 some_attribute; | ||
- | </code> | ||
- | * Fragment shader: <code glsl> | ||
- | layout(location = 3) in vec3 the_same_attribute; | ||
- | </code> | ||
<note tip> | <note tip> | ||
- | Valoarea unui **atribut de intrare dintr-un program de tip fragment shader**, se obține prin **interpolare între valorile acelorași atribute de ieșire de la programele de tip vertex shader** ce au prelucrat cele 3 vârfuri care au format triunghiul rasterizat. | + | Pentru a înțelege mai bine API-ul OpenGL vă recomandăm să citiți documentația indicată pentru fiecare comandă prezentată. Atunci când se prezintă o nouă comandă, dacă apăsați click pe numele acesteia veți fi redirecționați către pagina de manual a comenzii respective.\\ |
+ | De asemenea, documentația oficială și completă a API-ului OpenGL poate fi gasită pe pagina **[[https://www.opengl.org/sdk/docs/man/|OpenGL 4 Reference Pages]]** | ||
</note> | </note> | ||
- | ===== Laborator ===== | + | === Index Buffer Object (IBO) === |
- | ==== Codul sursă al programelor de tip shader ==== | + | Un index buffer object (numit și element buffer object) reprezintă un container în care stocăm indicii vertecșilor. Cum **VBO** si **IBO** sunt buffere, ele sunt extrem de similare în construcție, încărcare de date și ștergere. |
- | În cadrul laboratorului, codul sursă al unui program de tip shader este într-un fișier dedicat special pentru fiecare tip de program în directorul cu numele ''shaders''. Acest director se regăsește în interiorul directorului specific fiecărui laborator. Pentru laboratorul curent, fișierele codului sursă al programelor de tip shader se regăsește în ''lab5/shaders''. Ierarhia filtrelor din mediul de dezvoltare Visual Studio pentru laboratorul curent poate fi vizualizată în imaginea de mai jos: | + | <code cpp> |
+ | glGenBuffers(1, &IBO_ID); | ||
+ | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO_ID); | ||
+ | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), &indices[0], GL_STATIC_DRAW); | ||
+ | </code> | ||
- | {{ :ppbg:laboratoare:shaders-lab.png?300 |}} | + | La fel ca la VBO, creăm un IBO și apoi îl legăm la un punct de legatură, doar că de data aceasta punctul de legatură este **GL_ELEMENT_ARRAY_BUFFER**. Datele sunt trimise către bufferul mapat la acest punct de legatură. În cazul indicilor toți vor fi de dimensiunea unui singur întreg. |
- | ==== Erori de compilare sau de legare a programelor de tip shader ==== | + | === Vertex Array Object (VAO) === |
- | Posibilele erori de compilare a codului sursă pentru programele de tip shader se regăsesc în consolă. De exemplu, pentru codul sursă al unui program de tip vertex shader, ce conține o eroare de compilare după cum urmează: | + | Într-un vertex array object putem stoca toată informația legată de starea geometriei desenate. Putem folosi un număr mare de buffere pentru a stoca fiecare din diferitele atribute („separate buffers”). Putem stoca mai multe (sau toate) atribute într-un singur buffer („interleaved” buffers). În mod normal înainte de fiecare comandă de desenare trebuie specificate toate comenzile de „binding” pentru buffere sau atribute ce descriu datele ce doresc a fi randate. Pentru a simplifica această operație se folosește un vertex array object care ține minte toate aceste legături. |
- | <code glsl> | + | Un vertex array object este creat folosind comanda **[[https://www.opengl.org/sdk/docs/man4/html/glGenVertexArrays.xhtml|glGenVertexArrays]]**: |
- | #version 330 | + | |
- | uniform mat4 Model; | + | <code cpp> |
+ | unsigned int VAO; | ||
+ | glGenVertexArrays(1, &VAO); | ||
+ | </code> | ||
- | void main() | + | Este legat cu **[[https://www.opengl.org/sdk/docs/man4/html/glBindVertexArray.xhtml|glBindVertexArray]]**: |
- | { | + | |
- | vec4 pos = Model * ; | + | |
- | } | + | |
- | </code> | + | <code cpp>glBindVertexArray(VAO);</code> |
+ | <hidden> | ||
+ | Și este distrus cu **[[https://www.opengl.org/sdk/docs/man4/html/glDeleteVertexArrays.xhtml|glDeleteVertexArrays]]**: | ||
+ | <code cpp>glDeleteVertexArrays(1, &VAO);</code> | ||
+ | </hidden> | ||
+ | <note tip> | ||
+ | Înainte de a crea VBO-urile și IBO-ul necesar pentru un obiect se va lega VAO-ul obiectului și acesta va ține minte automat toate legăturile specificate ulterior. | ||
- | Eroarea de compilare este marcată în consolă în felul următor: | + | După ce toate legăturile au fost specificate este recomandat să se dea comanda ''glBindVertexArray(0)'' pentru a dezactiva legătura către VAO-ul curent, deoarece altfel riscăm ca alte comenzi OpenGL ulterioare să fie legate la același VAO și astfel să introducem foarte ușor erori în program. |
+ | </note> | ||
- | {{ :ppbg:laboratoare:shaders-compilation-error.png?600 |}} | + | Înainte de comanda de desenare este suficient să legăm doar VAO-ul ca OpenGL să știe toate legatările create la construcția obiectului. |
- | Textul erorii menționează la început, prin marcajul ''0(7)'', că eroarea se găsește la linia 7 din codul sursă. | + | ==== Opțiunea de optimizare Face Culling==== |
- | Textul posibilelor erori de legare dintre două programe de tip shader apar tot în consolă. | + | API-ul OpenGL oferă posibilitatea de a testa orientarea aparentă pe ecran a fiecărui triunghi înainte ca acesta să fie redat și să îl ignore în funcție de starea de discard setată: **GL_FRONT** sau **GL_BACK**. Acestă funcționalitate poartă numele de **[[https://www.opengl.org/wiki/Face_Culling|Face Culling]]** și este foarte importantă deoarece reduce costul de procesare total. |
+ | Modul cum este considerată o față ca fiind **GL_FRONT** sau **GL_BACK** poate fi schimbat folosind comanda [[https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glFrontFace.xhtml|glFrontFace]] (valoarea inițială pentru o față **GL_FRONT** este considerată ca având ordinea specificării vârfurilor în sens trigonometric / counter clockwise): | ||
+ | <code cpp> | ||
+ | // mode can be GL_CW (clockwise) or GL_CCW (counterclockwise) | ||
+ | // the initial value is GL_CCW | ||
+ | void glFrontFace(GLenum mode); | ||
+ | </code> | ||
- | ==== Încărcarea modelelor 3D din fișiere ==== | + | <note> |
+ | Exemplu: pentru un **cub** maxim 3 fețe pot fi vizibile la un moment dat din cele 6 existente. În acest caz maxim 6 triunghiuri vor fi procesate pentru afișarea pe ecran în loc de 12. | ||
+ | </note> | ||
- | Framework-ul de laborator permite încărcarea și desenarea modelelor 3D din fișiere ce au diferite formate. De exemplu, pentru a încărca modelul unei sfere, se poate folosi: | + | În mod normal face-culling este dezactivat. Acesta poate fi activat folosind comanda [[https://www.opengl.org/sdk/docs/man4/html/glEnable.xhtml|glEnable]]: |
+ | <code cpp> | ||
+ | glEnable(GL_CULL_FACE); | ||
+ | </code> | ||
+ | Pentru a dezactiva face-culling se folosește comanda [[https://www.opengl.org/sdk/docs/man4/html/glEnable.xhtml|glDisable]]: | ||
<code cpp> | <code cpp> | ||
- | Mesh* mesh = new Mesh("sphere"); | + | glDisable(GL_CULL_FACE); |
- | mesh->LoadMesh(PATH_JOIN(window->props.selfDir, RESOURCE_PATH::MODELS, "primitives"), "sphere.obj"); | + | |
- | meshes[mesh->GetMeshID()] = mesh; | + | |
</code> | </code> | ||
- | Modelul se regăsește deja în interiorul proiectului, în directorul ''assets/models/primitives''. Puteți încerca să încarcați alte modele puse la dispoziție în cadrul framework-ului de laborator :) . | + | Pentru a specifica ce orientare a fețelor să fie ignorată se folosește comanda **[[https://www.opengl.org/wiki/GLAPI/glCullFace|glCullFace]]** |
+ | <code cpp> | ||
+ | // GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK are accepted. | ||
+ | // The initial value is GL_BACK. | ||
+ | glCullFace(GL_BACK); | ||
+ | </code> | ||
+ | |||
+ | ===== Cerințe laborator ===== | ||
<note important> | <note important> | ||
- | Majoritatea modelelor 3D încărcate din fișiere **NU** au asociată informația de culoare în vârfuri. Din acest motiv, aceste modele, inclusiv modelul de sferă ce este utilizat în laboratorul curent și este vizibil în imaginea de mai jos, au valoarea ''vec3(0, 0, 0)'' asociată pentru atributul de intrare specific informației vârfului de la locația 3, v_color. | + | Pentru toate cerințele în care se precizează că animațiile trebuie să fie continue, utilizați valoarea ''deltaTime''. |
</note> | </note> | ||
- | ===== Cerințe laborator ===== | + | - 0.05p - Completați metoda ''CreateMesh()'' astfel încât să încărcați geometria în memoria RAM a procesorului grafic. |
- | <note important> | + | * Creați un VAO; |
- | Inițial, execuția laboratorului 5, fără rezolvarea niciunei cerințe, va aduce după sine o eroare de execuție. Este normal, deoarece utilizăm un program de tip shader ce nu a fost compilat și legat cu succes :) . După rezolvarea cerinței 1, de mai jos, eroarea de execuție va dispărea. | + | * Creați un VBO și adăugați date în el; |
- | </note> | + | * Creați un IBO și adăugați date în el; |
- | - 0.05p - Completați metoda ''CreateShader()'' pentru a crea un program de tip shader. | + | * După completarea corectă a metodei, rezultatul vizual ar trebui să fie următorul: {{ :ppbg:laboratoare:result1.png?600 |}} |
- | - 0.05p - Completați metoda ''RenderMesh()'' pentru a transmite atributele de tip uniform la cele două programe de tip shader. Atributele transmise sunt matricile utilizate pentru transformarea de modelare, vizualizare și proiecție. | + | - 0.05p - La apăsarea unei taste, alegeți o culoare aleatorie pentru curățarea grilei de pixeli. |
- | - 0.05p - Completați codul sursă al programelor de tip shader: | + | * Utilizați în metoda ''Update()'', directiva de specificare a culorii de curățare a grilei de pixeli. |
- | - Completați codul sursă al programului de tip vertex shader, din fișierul ''LabShader.VS.glsl'', ce se regăsește în interiorul directorului ''shaders'': | + | * La apăsarea tastei R, alegeți o culoare aleatorie. |
- | * Se declară atributele de intrare pentru programul de tip vertex shader folosind layout location <code glsl> | + | - 0.05p - La apăsarea unei taste, schimbați între desenarea triunghiurilor a căror fațetă față sau spate se afișează. |
- | layout(location = 0) in vec3 v_position; | + | * Utilizați în metoda ''DrawObjects()'', directivele de activare și dezactivare a optimizării Face culling. |
- | // same for the rest of the attributes ( check Lab5.cpp CreateMesh() ); | + | * Utilizați în metoda ''DrawObjects()'' directiva de specificare a tipului de față pentru care triunghiurile se elimina din procesul de rasterizare. |
- | </code> | + | * La apăsarea tastei F, schimbați între eliminarea triunghiurilor pentru care se afișează fațeta față sau spate. |
- | * Se declară atributele de ieșire către programul de tip fragment shader <code glsl> | + | - 0.05p - Pentru cele 3 cuburi din scenă, aplicați urmatoarele animații: |
- | out vec3 color; | + | * Unul dintre cuburi să se deplaseze continuu sus-jos între limitele 0 și 3 de-a lungul axei Y. |
- | // same for other attributes | + | * Un alt cub să se rotească continuu față de una dintre cele 3 axe principale. |
- | </code> | + | * Un alt cub sa pulseze continuu între scările 0.5 și 2. |
- | * Se salvează valorile pentru atributele de ieșire, în ''main()'' <code glsl> | + | - 0.05p - Desenați un alt cub pe care să îl deplasați prin spațiu la apăsarea tastelor W, A, S, D, E, Q (pozitiv și negativ pe toate cele 3 axe). Nu permiteți deplasarea cubului în situația în care poziția observatorului se modifică. |
- | color = v_color; | + | - 0.05p - Desenați obiectele de 4 ori în 4 porți de afișare diferite, conform imaginii de mai jos. Păstrați proporțiile precizate în imagine. |
- | // same for other attributes | + | * Utilizați în metoda ''DrawObjects()'', directiva de specificare a poziției și dimensiunii porții de afișare. |
- | </code> | + | * În metoda ''Update()'', desenați de 4 ori obiectele. Pentru fiecare desenare a obiectelor, utilizați una dintre cele 4 camere setate cu poziții și direcții de vizualizare predefinite. |
- | * Se calculează poziția în spațiul de decupare a vârfului primit folosind matricile Model, View, Projection <code glsl> | + | {{ :ppbg:laboratoare:result2.png?700 |}} |
- | gl_Position = Projection * View * Model * vec4(v_position, 1.0); | + | |
- | </code> | + | |
- | - Completați codul sursă al programului de tip fragment shader, din fișierul ''LabShader.FS.glsl'' ce se regăsește în interiorul directorului ''shaders'': | + | |
- | * Se primesc valorile atributelor trimise de la programul de tip vertex shader <code glsl> | + | |
- | in vec3 color; | + | |
- | </code> | + | |
- | * Se calculează atributul de ieșire al programului de tip fragment shader, ce reprezentă culoarea pixelului <code glsl> | + | |
- | out_color = vec4(color, 1); | + | |
- | </code> | + | |
- | * Până în acest punct, rezultatul pe care ar trebui să îl obțineti este următorul: {{ :ppbg:laboratoare:shaders-result.png?600 |}} | + | |
- | * Sfera din colorată în culoarea neagră deoarece geometria ei a fost încărcată dintr-un fișier text ce nu conține informație de culoare asociată în vârfuri | + | |
- | - 0.05p - Trimiteți timpul aplicației (opținut cu metoda ''Engine::GetElapsedTime()''), și creați o animație de pulsație a obiectelor prin modificarea scării lor după o funcție de timp (trigonometrică etc.) | + | |
- | - 0.05p - Pe baza timpului aplicației, trimis anterior, modificați culoarea (unul sau mai multe canale de culoare) după o funcție de timp (trigonometrică etc.) | + | |
- | - 0.05p - Pe baza timpului aplicației, creați o animație de oscilație de-a lungul axei OY **globală**, după o funcție de timp (trigonometrică etc.) pentru cubul din partea dreaptă | + | |
- | * Pentru desenarea cubului din dreapta, utilizați programul de tip shader denumit ''"LastTask"''. | + | |
- | * Completați codul sursă al programelor de tip shader din fișierele ''LastTask.VS.glsl'' și ''LastTask.FS.glsl'', conform cerinței 3. | + | |
- | * Pe baza timpului aplicației, modificați coordonatele vârfurilor de-a lungul axei OY globală. | + | |
- | Bonus: Creați o animație de deformare la nivel de vârfuri, diferită pentru fiecare vârf, similar cu animația vizibilă mai jos :) . Creați fișierele pentru un al treilea program de tip shader și utilizați-l pentru desenarea geometriei sferei din stânga. Realizați animatia pe geometria sferei. Pentru a asocia culori vârfurilor, puteți utiliza informația de poziție în spațiul obiect pentru culoarea pixelilor. | ||
- | {{ :ppbg:laboratoare:shaders-bonus.gif?600 |}} | ||