This is an old revision of the document!
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 :) .
lab_list.h
, linia #include “lab/lab4/lab4.h”
.
Aplicatiile grafice in timp real realizeaza o desenare succesiva a cate unui cadru. Intre 2 cadre succesive, parametrii de desenare, precum pozitia si directia de vizualizare a observatorului, transformarile geometriei desenate sau alte elemente ce influenteaza desenarea, cum sunt informatiile unei surse de lumina, pot sa difere. Utilizarea unui numar mare de cadre desenate pe secunda, creeaza iluzia de animatie continua :) .
In framework-ul pus la dispozitie in cadrul acestui laborator, succesiunea de cadre se realizeaza in clasa world
in metoda Run
printr-o bucla care se opreste doar in momentul in care se inchide fereastra.
while (!window->ShouldClose()) { LoopUpdate(); }
In interiorul acestei bucle, in interiorul metodei LoopUpdate
se realizeaza urmatorul proces pentru fiecare cadru:
// 1. Polls and buffers the events window->PollEvents(); // 2. Computes frame deltaTime in seconds ComputeFrameDeltaTime(); // 3. Calls the methods of the instance of InputController in the following order // 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(); // 4. Frame processing FrameStart(); Update(static_cast<float>(deltaTime)); FrameEnd(); // 5. Swap front and back buffers - image will be displayed to the screen window->SwapBuffers();
Pasii din codul de mai sus sunt:
deltaTime
.LabX
si sunt discutate mai jos.LabX
.Interactiunea utilizatorului cu fereastra poate fi: apasarea unei taste de tastatura, apasarea unui buton de la mouse, redimensionarea ferestrei si inchiderea ei.
Interactiunea utilizatorului cu tastele de la tastatura, in situatia in care fereastra este selectata, este de 3 feluri:
Interactiunea utilizatorului cu butoanele de la mouse este de 3 feluri, similar ca in situatia tastelor, descrisa mai sus.
In framework-ul de laborator, interactiunea cu utilizatorul se realizeaza in metodele OnWindowResize
, OnMouseMove
, OnMouseBtnPress
, OnMouseBtnRelease
, OnMouseScroll
, OnKeyPress
, OnMouseScroll
, ce sunt apelate pentru fiecare eveniment realizat intr-un cadru. De exemplu:
void Lab4::OnKeyPress(int key, int mods) { if (key == GLFW_KEY_R) { printf("S-a apasat tasta R."); } }
Metoda OnKeyPress
se apeleaza pentru fiecare tasta apasata intr-un cadru. Trebuie verificat in interiorul metodei, pe baza parametrilor ce tasta a fost apasata. Pentru mai multe informatii despre fiecare metoda in parte, va rog sa cititi detaliile din fisierul header al clasei InputController.
Metoda OnInputUpdate
are un statut special in cadrul framework-ului de laborator si este similara cu metoda Update
, mai exact este apelata in fiecare cadru exact o data. Apelul ei se realizat in cadrul pasului 3 de mai sus, astfel ca se apeleaza inainte de metoda Update
. Recomandarea este sa utilizati aceasta metoda cand gestionati interactiunea cu utilizatorul.
Suplimentar acestor metode specifice tratarii interactiunii cu utilizatorul, se poate folosi atributul window
, intern clasei, pentru a verifica existenta anumitor evenimente:
if(window->KeyHold(GLFW_KEY_R) { printf("Tasta R este apasata."); } if (window->MouseHold(GLFW_MOUSE_BUTTON_1)) { printf("Butonul stanga de la mouse este apasat."); }
Modificarea proprietatilor unui obiect intre 2 cadre succesive trebuie realizata pe baza timpului care a trecut intre cele 2 cadre, respectiv timpul trecut pentru desenarea cadrului anterior. Mai exact, in situatia in care dorim sa modificam pozitia unui obiect cu 5 unitati de spatiu pe secunda, de-alungul axei Z, in sens pozitiv, putem aplica urmatorul proces:
object_position.z += 5 * 0.016;
Valoarea 0.016 reprezinta timpul mediu de desenare a unui cadru la o frecventa de 60 de cadre pe secunda. Intr-o secunda, pozitia obiectului se va deplasa cu 5 unitati de-alungul axei Z, in sens pozitiv. Pentru a nu folosi direct valoarea aceasta, putem utiliza valoarea deltaTime
, ce reprezinta timpul de desenare a cadrului precedent si este primita ca parametru in metodele Update
si OnInputUpdate
.
object_position.z += 5 * deltaTime;
O aplicatie grafica in timp real utilizeaza procesorul grafic pentru a accelera desenarea unui cadru si a mari frecventa de desenare a cadrelor. Deoarece fiecare tip de procesor grafic este diferit fata de celelalte, pentru a se evita implementarea desenarii specific pentru fiecare tip de procesor grafic, au fost introduse mai multe standarde de programare a procesorului, ce sunt implementate in interiorul driver-ului video. O privire de ansamblu a comunicarii intre o aplicatie grafica in timp real si procesorul grafic cum este prezentata in imaginea de mai jos. Aceste standarde poarta numele de API-uri grafice si pentru a enumera doar cateva: OpenGL, Direct3D sau Metal.
Toate API-urile realizeaza in mare aceleasi operatii standard in cadrul implementarii hardware din procesorul grafic pentru realizarea prelucrarilor in banda grafica. Diferenta este data de forma de transmitere a parametrilor pentru aceste operatii. In cadrul acestui laborator, noi vom utiliza API-ul grafic OpenGL pentru realizarea prelucrarilor in banda grafica.
Primul pas realizat la inceputul unui cadru este curatarea grilei de pixeli si a grilei de valori de adancime desenate la cadrul anterior. Acest proces se realizeaza in API-ul grafic OpenGL prin directiva:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Informatia din fiecare celula a grilei de valori de adancime se suprascrie cu valoarea 1. Aceasta reprezinta valoarea componentei z a fetei din spate pentru volumul de decupare. Culoarea cu care se suprascriu valorile in grila de pixeli se poate stabili prin:
glClearColor(0, 0, 0, 1);
Se poate stabili zona din ecran, poarta de afisare, in care sa se deseneze obiectele prin directiva:
glViewport(0, 0, 1280, 720);
Un model 3D, cunoscut in limba engleza 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 Mesh.
Un vertex buffer object reprezintă un container în care stocăm date ce țin de conținutul vârfurilor precum:
Un vertex buffer object se poate crea prin comanda OpenGL glGenBuffers:
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
VBO_ID
.
Pentru a distruge un VBO și astfel să eliberăm memoria de pe GPU se folosește comanda glDeleteBuffers:
glDeleteBuffers(1, &VBO_ID);
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 glBindBuffer:
glBindBuffer(GL_ARRAY_BUFFER, VBO_ID);
În acest moment putem să facem upload de date din memoria CPU către GPU prin intermediul comenzii glBufferData:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), &vertices[0], GL_STATIC_DRAW);
&vertices[0]
, și copiază în memoria video dimensiunea specificată prin parametrul al 2-lea.
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.
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);
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.
Î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.
Un vertex array object este creat folosind comanda glGenVertexArrays:
unsigned int VAO; glGenVertexArrays(1, &VAO);
Este legat cu glBindVertexArray:
glBindVertexArray(VAO);
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.
Î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.
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 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 glFrontFace (valoarea inițială pentru o față GL_FRONT este considerată ca având ordinea specificării vârfurilor în sens trigonometric / counter clockwise):
// mode can be GL_CW (clockwise) or GL_CCW (counterclockwise) // the initial value is GL_CCW void glFrontFace(GLenum mode);
În mod normal face-culling este dezactivat. Acesta poate fi activat folosind comanda glEnable:
glEnable(GL_CULL_FACE);
Pentru a dezactiva face-culling se folosește comanda glDisable:
glDisable(GL_CULL_FACE);
Pentru a specifica ce orientare a fețelor să fie ignorată se folosește comanda glCullFace
// GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK are accepted. // The initial value is GL_BACK. glCullFace(GL_BACK);
deltaTime
.
CreateMesh()
astfel încât să încărcați geometria in memoria RAM a procesorului grafic.Update()
directiva de specificare a culorii curatare a informatiei de pe ecran.DrawObjects()
directivere de activare si dezactivare a optimizarii Face cullingDrawObjects()
directiva de specificare a tipului de fata pentru care triunghiurile se elimina din procesul de rasterizareDrawObjects()
directiva de specificare a pozitiei si dimensiunii portii de afisare.Update()
desenati de 4 ori obiectele. Trimiteti la desenare cele 4 camere setate cu pozitii si directii de vizualizare predefinite.