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/lab5/lab5.h”
.
În laboratorul anterior, am analizat modalitatea în 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 ne-am folosit de 3 tipuri de obiecte distincte, 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.
În laboratorul 3 am analizat modalitatea în 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ă, numit spatiu 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.
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).
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:
#version 330 layout(location = 0) in vec3 v_position; layout(location = 1) in vec3 v_normal; layout(location = 2) in vec2 v_tex_coord; layout(location = 3) in vec3 v_color; // Uniform properties uniform mat4 Model; uniform mat4 View; uniform mat4 Projection; void main() { gl_Position = Projection * View * Model * vec4(v_position, 1.0); }
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.
#version 330
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.
layout(location = 0) in vec3 v_position; layout(location = 1) in vec3 v_normal; layout(location = 2) in vec2 v_tex_coord; layout(location = 3) in vec3 v_color;
Î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.
struct VertexFormat { glm::vec3 position; glm::vec3 normal; glm::vec2 text_coord; glm::vec3 color; };
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ă.
uniform mat4 Model; uniform mat4 View; uniform mat4 Projection;
Toate programele de tip shader trebuie sa contina un void main()
de unde se incepe executia codului.
void main() { gl_Position = Projection * View * Model * vec4(v_position, 1.0); }
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.
Pentru fiecare pixel ce a fost procesat de catre rasterizator si pentru care trebuie sa i se atribuie o culoare, rasterizatorul executa o instanta de program pentru prelucrarea de pixeli. Acest tip de program poate avea mai multe date de iesire, dar in acest laborator ne vom concentra exclusiv pe culoarea pe care trebuie sa o atribuie pixelului prelucrat. Tipul de program utilizat pentru prelucrarea pixelilor poarta numele in limba engleza de fragment shader sau pixel shader.
Sintaxa unui fragment shader arata in felul urmator:
#version 330 // Uniform properties uniform mat4 Model; uniform mat4 View; uniform mat4 Projection; layout(location = 0) out vec4 out_color; void main() { out_color = vec4(1, 0, 0, 0); }
Similar ca in situatia programului de tip vertex shader, prima directiva necesara este cea care specifica versiunea utilizata pentru limbajul de programare GLSL. De asemenea, toate atributele de intrare de tip uniform sunt disponibile in fragment shader. In plus, si acest tip de program are un void main()
din care se incepe executia codului.
Acest atribut reprezinta datele de intrare pentru fragment shader. In situatia acestui laborator, este culoarea pixelului din grila.
layout(location = 0) out vec4 out_color;
Programul de mai jos asociaza tuturor pixelilor culoarea rosie.
void main() { out_color = vec4(1, 0, 0, 0); }
In API-ul grafic OpenGL, toate programele de tip shader sunt compilate si legate intre ele pentru a fi executate de catre procesul grafic in timpul executiei aplicatiei grafice.
Pentru a gestiona un program de tip shader in API-ul grafic OpenGL, trebuie sa utilizam un obiect de tip shader pentru fiecare din cele doua tipuri de programe vertex shader si fragment shader, impreuna cu un obiect suplimentar ce inglobeaza programele de tip shader utilizate.
Pentru crearea unui obiect de tip shader, se utilizeaza:
// 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);
Pentru legarea a doua programe vertex shader si fragment shader, se utilizeaza:
unsigned int program_id = glCreateProgram(); glAttachShader(program_id, vertex_shader_id); glAttachShader(program_id, fragment_shader_id); glLinkProgram(program_id);
Pentru desenarea cu cele doua programe de tip shader create mai sus, se utilizeaza:
glUseProgram(program_id); glBindVertexArray(VAO_id); glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
API-ul utilizeaza memoria stocata in VBO. Pentru a specifica legatura dintre formatul datelor din lista de varfuri si formatul datelor de intrare din programul de tip vertex shader, API-ul OpenGL pune la dispozitie directiva glVertexAttribPointer
:
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3) + sizeof(glm::vec2)));
Primul parametru reprezinta locatia atributului de intrare din programul de tip vertex shader:
layout(location = 3) in vec3 v_color;
Pentru exemplul de mai sus, specificarea formatului se realizeaza in felul urmator:
glBindVertexArray(VAO); // Set vertex position attribute glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), 0); // Set vertex normal attribute glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(sizeof(glm::vec3))); // 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)));
glVertexAttribPointer
puteti consulta documentatia oficiala: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glVertexAttribPointer.xhtml.
Pentru a transmite un atribut de tip uniform la un program shader, API-ul grafic OpenGL ne pune la dispozitie mai multe directive, specifice fiecarui tip de date pe care il permite limbajul de programare GLSL. Doar pentru a enumera cateva, directivele sunt:
glUseProgram(program_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 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));
API-ul grafic OpenGL nu impune obligativitatea utilizarii unei locatii explicite pentru atributele de tip uniform, dar aceasta locatie exista implicit. Pentru a obtine locatia implicita a unui atribut, indiferent de tipul lui, utilizam:
int location = glGetUniformLocation(program_id, "uniform_variable_name_in_shader");
In cadrul laboratorului, codul sursa al programelor de tip shader se vor gasi in fisere dedicate special in directorul cu numele shaders
din interiorul directorului specific fiecarui laborator, de exemplu pentru laboratorul curent, directorul lab5
. Pentru acest laborator, ierarhia filtrelor din mediul de dezvoltare Visual Studio poate fi vizualizata in imaginea de mai jos:
Posibilele erori de compilare a codului sursa pentru programele de tip shader se regasesc in consola. De exemplu, pentru codul sursa al unui program de tip vertex shader, ce contina o eroare de compilare dupa cum urmeaza:
#version 330 uniform mat4 Model; void main() { vec4 world_pos = Model * ; }
Eroarea de compilare este marcata in consola in felul urmator:
Textul erorii mentioneaza la inceput, prin marcajul 0(7)
ca eroarea se gaseste la linia 7 din codul sursa.
Framework-ul de laborator permite incarcarea si desenarea modelelor 3D din fisiere ce au diferite formate. De exemplu, pentru a incarca modelul unei sfere, se poate folosi:
Mesh* mesh = new Mesh("sphere"); mesh->LoadMesh(PATH_JOIN(window->props.selfDir, RESOURCE_PATH::MODELS, "primitives"), "sphere.obj"); meshes[mesh->GetMeshID()] = mesh;
Modelul se regaseste deja in interiorul proiectului, in directorul assets/models/primitives
. Puteti incerca sa incarcati alte modele puse la dispozitie din cadrul framework-ului de laborator :) .
vec3(0, 0, 0)
asociata pentru atributul de intrare v_color, specific informatiei din varf.
CreateShader()
pentru a crea un program de tip shader.RenderMesh()
pentru a transmite atributele de tip uniform la cele doua programe de tip shader. Atributele transmise sunt matricile pentru transformarea de modelare, vizualizare si proiectie.FirstTask.VS.glsl
din directorul shaders
:layout(location = 0) in vec3 v_position; // same for the rest of the attributes ( check Lab5.cpp CreateMesh() );
out vec3 color; // same for other attributes
main()
color = v_color; // same for other attributes
gl_Position = Projection * View * Model * vec4(v_position, 1.0);
FirstTask.FS.glsl
din directorul shaders
:in vec3 color;
out_color = vec4(color, 1);
Engine::GetElapsedTime()
), si creati animatia de pulsatie a obiectelor prin modificarea scarii lor dupa o functie de timp (trigonometrica etc.)“LastTask”
.LastTask.VS.glsl
si LastTask.FS.glsl
, conform cerintei 3.Bonus: Creati o animatie de deformare la nivel de varfuri, diferita pentru fiecare varf, similara cu cea din imaginea de mai jos :) . Creati un al treilea program de tip shader si aplicati animatia pe geometria sferei din stanga. Pentru a asocia culori varfurilor, puteti utiliza informatia de pozitie pe post de culoare.