Video Laborator 2: https://youtu.be/RtXuIQO8l0U.
Autor: Alex Gradinaru
Dacă am încerca să reducem întregul API de OpenGL la mari concepte acestea ar fi:
Shaderele vor fi introduse pe parcursul cursului.
Stările reprezintă un concept mai larg, OpenGL fiind de fapt un mare automat finit cu o mulțime de stări și posibilități de a seta aceste stări. De-a lungul laboratoarelor o parte din aceste stări vor fi folosite pentru a obține efectele dorite.
Datele conțin informațiile ce definesc scena, precum:
Primitiva de bază în OpenGL este triunghiul. Astfel, așa cum se poate observa și în imaginea de sus, pentru a desena un obiect acesta trebuie specificat prin triunghiuri.
Cubul descris mai sus este specificat prin lista celor 8 coordonate de vârfuri și o listă de 12 triunghiuri care descrie modul în care trebuie unite vârfurile specificate în lista precedentă pentru a forma fețele cubului. Folosind vârfuri și indici putem descrie în mod discret orice obiect tridimensional.
Mai jos regăsiți principalele primitive acceptate de standardul OpenGL 3.3+.
După cum se poate observa, există mai multe metode prin care geometria poate fi specificată:
primitive
poate fi oricare dintre primitivele menționate în imaginea de mai sus.
O observație importantă legată de topologie este ordinea vârfurilor într-o primitivă solidă (nu linie, nu punct) cu mai mult de 2 vârfuri. Această ordine poate fi în sensul acelor de ceas sau în sens invers.
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);
Un „mesh” este un obiect tridimensional definit prin vârfuri și indici. În laborator aveți posibilitatea să încărcați meshe î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:
GLuint 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.
În cadrul laboratorului vom învăța să folosim VAO, VBO, IBO și astfel să generăm și încărcăm geometrie simplă.
Laboratorul pune la dispoziție structura VertexFormat ce va fi utilizată ca bază pentru a crea geometria.
struct VertexFormat { // position of the vertex glm::vec3 position; // vertex normal glm::vec3 normal; // vertex texture coordinate glm::uvec2 text_coord; // vertex color glm::vec3 color; };
Clasa Mesh
pune la dispoziție posibilitatea de a încărca geometrie simplă folosind diverse metode:
// Initializes the mesh object using a VAO GPU buffer that contains the specified number of indices bool InitFromBuffer(unsigned int VAO, unsigned int nrIndices); // Initializes the mesh object and upload data to GPU using the provided data buffers bool InitFromData(const std::vector<VertexFormat>& vertices, const std::vector<unsigned int>& indices); // Initializes the mesh object and upload data to GPU using the provided data buffers bool InitFromData(const std::vector<glm::vec3>& positions, const std::vector<glm::vec3>& normals, const std::vector<unsigned int>& indices);
F3 - afișează/ascunde gridul din scenă
Space - desenează primitivele doar prin puncte sau linii (wireframe) sau geometrie opacă
Lab2::CreateMesh
dar puteți folosi metodele Mesh::InitFromData()
pentru a verifica validitatea geometriei.
VertexFormat
este o structură pentru vertex cu 2 parametrii (poziție, culoare).Lab2::CreateMesh
astfel încât să încărcați geometria pe GPUglEnable()
/ glDisable()