This shows you the differences between two versions of the page.
ppbg:laboratoare:06 [2023/11/16 10:07] andrei.lambru |
ppbg:laboratoare:06 [2024/11/14 09:59] (current) andrei.lambru |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Laboratorul 06 ====== | ====== Laboratorul 06 ====== | ||
- | <note tip> | + | <note important> |
- | **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 :) . | + | 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. |
</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 **Lab6**, î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/lab6/lab6.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> | ||
- | ===== Modele de iluminare ===== | + | ===== Banda grafică programabilă ===== |
- | + | ||
- | În laboratoarele anterioare, am analizat modalitatea prin care se poate desena pe ecran geometria unui model 3D. Până în acest moment, culorile atribuite pixelilor în care a fost discretizată suprafața geometriei au fost alese artificial prin diferite abordări, precum interpolarea între culorile vârfurilor. Deoarece ochiul uman "vede" doar lumină :) , culoarea unui pixel în care a fost discretizată o suprafață trebuie să se calculeze pe baza influenței unei surse de lumină asupra zonei din suprafață, reprezentată de pixel. Pentru acest proces, se introduce în procesul de desenare conceptul de //**sursă de lumină**//. Există mai multe tipuri de astfel de surse: | + | |
- | * Sursă de lumina **punctiformă**, ce are o poziție în spațiu și împrăștie lumină uniform în toate direcțiile în jurul poziției. | + | |
- | * Sursă de lumină de **tip spot**, ce are o poziție în spațiu și împrăștie lumină de la această poziție de-alungul unei singure direcții. Acest tip de sursă este similar cu o lanternă. | + | |
- | + | ||
- | Calcularea influenței iluminării ce provine de la o sursă de lumină, de orice tip, asupra unui punct de pe o suprafață, se realizează prin simularea procesului de transport al luminii de la poziția sursei, prin reflexie pe suprafață, în poziția punctului de pe suprafață, la poziția observatorului. O abordare care simulează acest proces de transport al luminii poartă numele de **model de iluminare** sau **model de reflexie**. | + | |
- | + | ||
- | În general, în majoritatea modelelor de iluminare, influența luminii asupra unui punct este împărțită în 4 componente: | + | |
- | * Componenta emisivă, reprezentată de lumina care este emisă direct de către suprafață. Este vorba aici de suprafețe ce emit lumină, precum suprafața unui bec sau a ecranului unui televizor. | + | |
- | * Componenta de reflexie indirectă a luminii, ce se referă la procesul de transport al luminii, de la o sursă, prin reflexia pe mai multe suprafețe, secvențial, la poziția observatorului. | + | |
- | * Componenta de reflexie difuză, ce se referă la transportul luminii de la o sursă, prin reflexia pe //exact o singură suprafață//, într-un punct, la poziția observatorului. | + | |
- | * Componenta de reflexie oglindă, ce se referă, similar cu cea de reflexie difuză, la transportul luminii de la o sursă, prin reflexia pe //o singură suprafață//, într-un punct, la poziția observatorului. | + | |
- | + | ||
- | Diferenta dintre ultimele două componente este dată de comportamentul de reflexie, respectiv o reflexie difuză și o reflexie oglindă. | + | |
- | Deoarece lumina este aditivă, cu cât mai mulți fotoni ajung în ochii nostri, cu atât lumina este mai "puternică" :), rezultatul final al influenței iluminării într-un punct este dat de suma celor 4 componente: | + | Î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. |
- | $$ | + | Î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. |
- | iluminare = componentaEmisiva + componentaIndirecta + componentaDifuza + compoentaOglinda | + | |
- | $$ | + | |
- | ==== Componenta emisivă ==== | + | {{ :ppbg:laboratoare:shaders.png?600 |}} |
- | În situația în care ne referim strict la transportul luminii de la suprafața sursei la poziția observatorului, această componenta se poate aproxima printr-o distribuire uniformă a împrăștierii luminii de la suprafață în toate direcțiile, astfel că poate fi utilizată o singură valoare uniformă pentru toată suprafața. | + | 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**). |
<note tip> | <note tip> | ||
- | În situația în care dorim să simulăm procesul de transport al luminii de la o suprafață ce emite lumină, prin reflexie pe o altă suprafață, la poziția observatorului, procesul devine complex. Există abordări pentru simularea lui, dar în general, în aplicații grafice în timp real se evită astfel de efecte vizuale. | + | Î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> | </note> | ||
- | ==== Componenta indirectă a iluminării ==== | + | ==== Programe de tip shader pentru prelucrarea vârfurilor ==== |
- | În general, în aplicațiile grafice în timp real este precalculată și se evită utilizarea pentru un numar mare de obiecte dinamice. Odată cu apariția procesoarelor grafice ce au arhitecturi de ray-tracing, se folosesc astfel de tehnici pentru simularea transportului luminii de la o sursă, ce ajunge prin reflexii multiple pe mai multe suprafețe, secvențial, la poziția observatorului. | + | 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: |
- | ==== Componentele de reflexie directă ==== | + | <code glsl> |
+ | #version 330 | ||
- | Acestea se referă la transportul luminii de la poziția unei surse, prin reflexie pe o suprafață, la poziția observatorului. Pentru comoditate, se împarte acest tip de reflexie în două componente distincte: | + | 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; | ||
- | === Componenta de reflexie difuză === | + | // Uniform properties |
+ | uniform mat4 Model; | ||
+ | uniform mat4 View; | ||
+ | uniform mat4 Projection; | ||
- | Această componentă se refera la cantitatea de lumină ce este impraștiată uniform prin reflexie pe o suprafață, în toate direcțiile din jurul punctului de pe suprafață, deasupra ei. | + | void main() |
+ | { | ||
+ | gl_Position = Projection * View * Model * vec4(v_position, 1.0); | ||
+ | } | ||
+ | </code> | ||
- | === Componenta de reflexie oglindă === | + | 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. |
- | Această componentă se referă la cantitatea de lumină ce este reflectată **de-alungul direcției de reflexie simetrică față de direcția de incidență a luminii cu direcția vectorului normal**. Această componenta este cunoscuta sub numele de ** componenta speculara**. Termenul //speculum// în limba latină se traduce în limba română cu termenul de //oglindă// :) . | + | <code glsl> |
+ | #version 330 | ||
+ | </code> | ||
- | ==== Atenuarea intensității iluminării pe bază de distanță ==== | + | 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. |
- | În momentul în care o sursă de lumină se depărteaza de un obiect, prin procesul de difuzie, cantitatea de lumină emisă de la sursă rămâne constantă, dar obiectul primește o cantitate mai mică de lumină. Simularea acestui fenomen se realizează prin scăderea intenstității iluminării pe baza distanței dintre poziția sursei de lumină și poziția pentru care se calculează iluminarea. | + | <code 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; | ||
+ | </code> | ||
- | ===== Laborator ===== | + | Î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. |
- | În practică, inclusiv în cadrul acestui laborator, se utilizează metode ce simulează fenomenele optice menționate mai sus. | + | <code cpp> |
- | + | struct VertexFormat | |
- | ==== Componenta emisivă ==== | + | { |
- | + | glm::vec3 position; | |
- | <code glsl> | + | glm::vec3 normal; |
- | vec3 emissive_component = Ke; # GLSL | + | glm::vec2 text_coord; |
+ | glm::vec3 color; | ||
+ | }; | ||
</code> | </code> | ||
- | <note tip> | ||
- | * Ke – culoarea emisivă a obiectului | ||
- | </note> | ||
- | ==== Componenta indirectă a iluminării ==== | + | 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ă. |
- | Este cunoscută în domeniul graficii pe calculator sub numele de **componentă ambientală**. Pentru a nu calcula tot transportul luminii cu toate reflexiile de pe suprafețe, se consideră că influența indirectă a iluminării este **aceeași în toate punctele din scenă**. Această aproximare obține rezultate satisfăcătoare pentru o parte din scenarii. | + | <code glsl> |
+ | uniform mat4 Model; | ||
+ | uniform mat4 View; | ||
+ | uniform mat4 Projection; | ||
+ | </code> | ||
- | Avem astfel: | + | Toate programele de tip shader trebuie sa conțină un ''void main()'' de unde se începe execuția codului. |
<code glsl> | <code glsl> | ||
- | vec3 ambient_component = Ka * global_ambient_color; # GLSL | + | void main() |
+ | { | ||
+ | gl_Position = Projection * View * Model * vec4(v_position, 1.0); | ||
+ | } | ||
</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. | ||
+ | |||
+ | ==== 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> | ||
- | * Ka – culoarea de reflexie ambientală a obiectului | + | 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. |
- | * global_ambient_color – culoarea ambientală a tuturor luminilor | + | |
</note> | </note> | ||
- | ==== Componenta difuză ==== | + | Sintaxa unui fragment shader este în felul următor: |
- | În imaginea de mai jos se poate observa că un fascicul de lumină ce are o lățime de dimensiune $A$, este proiectat pe o suprafață, de-alungul unei zone de dimensiune $B$. Se poate observa că în situația în care fasciculul este proiectat vertical pe suprafață, $B = A$. | + | <code glsl> |
+ | #version 330 | ||
- | {{ :ppbg:laboratoare:lambert-cosine-law.png?500 |}} | + | // Uniform properties |
+ | uniform mat4 Model; | ||
+ | uniform mat4 View; | ||
+ | uniform mat4 Projection; | ||
- | Notăm cu $\alpha$ unghiul dintre vectorul $\vec{L}$, ce reprezintă direcția de la poziția punctului spre poziția sursei de lumină și vectorul normal, $\vec{N}$. Deoarece unghiul făcut cu vectorul normal este 90 de grade, unghiul față de vectorul $\vec{L}$, realizat cu suprafața, este de $90-\alpha$. Pentru că suma unghiurilor unui triunghi este de 180 de grade și triunghiul din imagine este dreptunghic, rezultă că cel de-al treilea unghi din triunghi are dimensiunea $\alpha$. Astfel, rezultă că: | + | layout(location = 0) out vec4 out_color; |
+ | void main() | ||
+ | { | ||
+ | out_color = vec4(1, 0, 0, 0); | ||
+ | } | ||
+ | </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. |
- | cos(\alpha)=\frac{A}{B} \\ | + | |
- | B=\frac{A}{cos(\alpha)} | + | |
- | $$ | + | |
+ | 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ă. | ||
- | Din acest motiv, se poate deduce că intensitatea de iluminare pentru orice punct de pe suprafață este $cos(\alpha)$. În situația în care $\alpha$ este de 0 grade, intensitatea de iluminare este $cos(0)=1$. Această abordare poartă numele de **legea cosinusului a lui Lambert**, propusă de Johann Heinrich Lambert în anul 1760. | + | <code glsl> |
+ | layout(location = 0) out vec4 out_color; | ||
+ | </code> | ||
- | În domeniul graficii pe calculator, această lege a cosinusului este utilizată pentru calculul componentei difuze a iluminării, ce se referă la lumina împrăștiată uniform în toate directiile. Mai exact, se consideră că lumina se împrăștie uniform în toate direcțiile cu intensitatea de iluminare dată de legea cosinusului a lui Lambert. | + | Programul de mai jos asociază tuturor pixelilor culoarea roșie. |
- | În practică, în loc de cosinus, se utilizează produsul scalar dintre $\vec{L}$ și $\vec{N}$, cu ambii vectori de lungime 1. Interpretarea geometrică a produsului scalar este: | + | <code glsl> |
+ | void main() | ||
+ | { | ||
+ | out_color = vec4(1, 0, 0, 0); | ||
+ | } | ||
+ | </code> | ||
- | $$ | + | <note important> |
- | \vec{V_1}\cdot\vec{V_2}=\frac{cos(\angle(\vec{V_1},\vec{V_2}))}{\lVert\vec{V_1}\rVert\lVert\vec{V_2}\lVert} | + | 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> |
- | În situația în care vectorii $\vec{V_1}$ și $\vec{V_2}$ sunt amândoi de lungime 1, expresia de mai sus este echivalentă cu: | + | ===== Gestionarea programelor de tip shader în 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**. | ||
+ | ==== Gestionarea unui obiect de tip shader ==== | ||
- | $$ | + | 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. |
- | \vec{V_1}\cdot\vec{V_2}=cos(\angle(\vec{V_1},\vec{V_2})) | + | |
- | $$ | + | |
- | <note tip> | + | Pentru crearea unui obiect de tip shader, se utilizează: |
- | Pentru a obține un vector $\vec{V_u}$, de lungime 1, cunoscut sub numele de **vector unitate**, pe direcția și în sensul dat de un vector $\vec{V}$, putem aplica în limbajul GLSL: | + | |
- | <code glsl> | + | |
- | vec3 Vu = normalize(V); | + | |
- | </code> | + | |
- | </note> | + | |
- | Astfel, calculul componentei difuze a iluminării este: | + | <code cpp> |
+ | // GL_VERTEX_SHADER sau GL_FRAGMENT_SHADER, pe baza tipului dorit | ||
+ | unsigned int shader_id = glCreateShader(GL_VERTEX_SHADER); | ||
- | $$ | + | const char *shader_source = ...; |
- | componentaDifuza = K_d \cdot culoareLumina \cdot max(\vec{N}\cdot \vec{L}, 0) | + | |
- | $$ | + | |
- | În limbajul GLSL, expresia de mai sus se transcrie sub forma: | + | glShaderSource(shader_id, 1, &shader_source, 0); |
- | <code glsl> | + | glCompileShader(shader_id); |
- | vec3 diffuse_component = Kd * culoareLumina * max (dot(N,L), 0); # GLSL | + | |
</code> | </code> | ||
- | <note tip> | + | Pentru legarea a două programe vertex shader și fragment shader, se utilizează: |
- | * Kd - culoarea de reflexie difuză a obiectului | + | |
- | * culoareLumina – culoarea luminii | + | |
- | * $\vec{N}$ – vector normal (normalizat) | + | |
- | * $\vec{L}$ – vectorul direcției luminii incidente (normalizat) | + | |
- | * $max(\vec{N}\cdot \vec{L}, 0)$ – produsul scalar $\vec{N}\cdot \vec{L}$ reprezintă măsura unghiului dintre acești 2 vectori; astfel, dacă $\alpha$ este mai mare decât $\pi/2$, valoarea produsului scalar va fi mai mică decât 0, acest lucru însemnând că suprafața nu primește lumină ( sursa de lumină se află în spatele suprafeței ) și de aici și formula care asigură că în acest caz suprafața nu primește lumină difuză | + | |
- | </note> | + | |
- | {{ :egc:laboratoare:lab07:difuză.jpg?300 |}} | + | <code cpp> |
+ | unsigned int program_id = glCreateProgram(); | ||
- | ==== Componenta speculară ==== | + | glAttachShader(program_id, vertex_shader_id); |
+ | glAttachShader(program_id, fragment_shader_id); | ||
- | Pentru calcularea componentei speculare, vom folosi modelul propus de Bui Tuong Phong in 1973: | + | glLinkProgram(program_id); |
+ | </code> | ||
+ | Pentru desenarea cu cele două programe de tip shader create mai sus, se utilizează: | ||
- | $$ | + | <code cpp> |
- | componentaSpeculara = K_s \cdot culoareLumina \cdot primesteLumina \cdot (max(\vec{V}\cdot \vec{R}, 0))^n | + | glUseProgram(program_id); |
- | $$ | + | |
- | + | ||
- | În limbajul GLSL, expresia de mai sus se transcrie sub forma: | + | |
- | <code glsl> | + | glBindVertexArray(VAO_id); |
- | vec3 specular_component = Ks * culoareLumina * primesteLumina * pow(max(dot(V, R), 0), n) # GLSL | + | glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); |
</code> | </code> | ||
- | <note tip> | + | ==== Specificarea formatului de date din lista de vârfuri ==== |
- | * Ks - culoarea speculară de reflexie a obiectului | + | |
- | * $\vec{V}$ – vectorul direcției de vizualizare (normalizat) | + | |
- | * $\vec{R}$ – vectorul direcției luminii reflectate (normalizat) | + | |
- | * n – coeficientul de strălucire (shininess) al materialului | + | |
- | * primesteLumina – 1 dacă $\vec{N}\cdot \vec{L}$ este mai mare decât 0; sau 0 în caz contrar | + | |
- | </note> | + | |
- | {{ :egc:laboratoare:lab07:specular.jpg?300 |}} | + | 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'': |
- | Componenta speculară reprezintă lumina reflectată de suprafața obiectului numai în jurul acestei direcții, $\vec{R}$. Acest vector se obține prin: | + | <code cpp> |
+ | glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3) + sizeof(glm::vec2))); | ||
+ | </code> | ||
+ | |||
+ | Primul parametru reprezintă **locația** atributului de intrare din programul de tip vertex shader: | ||
<code glsl> | <code glsl> | ||
- | vec3 R = reflect (-L, N) # GLSL | + | layout(location = 3) in vec3 v_color; |
</code> | </code> | ||
- | <note tip> | + | Pentru exemplul de mai sus, specificarea formatului se realizează în felul următor: |
- | * Este necesar să se utilizeze -L deoarece ''reflect()'' are primul parametru vectorul incident care intră în suprafață, nu cel care iese din ea așa cum este reprezentat în figură | + | |
- | </note> | + | |
- | În modelul Phong, se aproximează scăderea rapidă a intensității luminii reflectate atunci când $\alpha$ crește prin $(cos \alpha)^n$, unde $n$ este exponentul de reflexie speculară al materialului (shininess). | + | <code cpp> |
+ | glBindVertexArray(VAO); | ||
- | După cum se observă, față de celelalte 3 componente, componenta speculară depinde și de poziția observatorului. Dacă observatorul nu se află într-o poziție unde poate vedea razele reflectate, atunci nu va vedea reflexie speculară pentru zona respectivă. De asemenea, nu va vedea reflexie speculară dacă lumina se află în spatele suprafeței. | + | // Set vertex position attribute |
+ | glEnableVertexAttribArray(0); | ||
+ | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), 0); | ||
- | ==== Atenuarea intensității iluminarii ==== | + | // Set vertex normal attribute |
+ | glEnableVertexAttribArray(1); | ||
+ | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(sizeof(glm::vec3))); | ||
- | Factorul de atenuare a intensității iluminării pe bază de distanță se aplică doar componentei difuze și speculare: | + | // Set texture coordinate attribute |
+ | glEnableVertexAttribArray(2); | ||
+ | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3))); | ||
- | <code glsl> | + | // Set vertex color attribute |
- | vec3 illumination = emissive_component + ambient_component | + | glEnableVertexAttribArray(3); |
- | + attenuation_factor * ( diffuse_component + specular_component); # GLSL | + | glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(2 * sizeof(glm::vec3) + sizeof(glm::vec2))); |
</code> | </code> | ||
- | <note tip> | ||
- | * attenuation_factor = $1/(d^2+1)$ este o funcție de atenuare | ||
- | * $d$ este distanța de la pozitia sursei de lumina la pozitia punctului de pe suprafață pentru care se calculeaza iluminarea | ||
- | </note> | ||
- | |||
- | <hidden> | ||
<note> | <note> | ||
- | Modele de colorare | + | Pentru mai multe detalii despre directiva ''glVertexAttribPointer'', puteți consulta documentația oficială: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glVertexAttribPointer.xhtml. |
+ | </note> | ||
- | Există mai multe modele pentru a specifica abordarea de implementare a modelului de calcul al reflexiei luminii. Mai exact, se specifică unde se evaluează modelul de reflexie. O astfel de abordare poarta numele de model de colorare si se intalneste in limba engleza sub numele de **shading model**. Dacă vrem să calculăm iluminarea pentru o suprafață poligonală: | + | ==== Transmiterea datelor de tip uniform la shader ==== |
- | * Se poate calcula o singură culoare pentru un triunghi al suprafeței. Acesta poarta numele de model de colorare Lambert. | + | |
- | * Se poate calcula câte o culoare pentru fiecare vârf al unui poligon si prin interpolare intre varfuri se calculeaza culorile pixelilor rasterizati pe baza triunghiului. Acesta poarta numele de model de colorare Gouraud. | + | |
- | * Se poate calcula câte o normală pentru fiecare vârf al unui triunghi si pentru fiecare pixel se determină o normală prin interpolare între normalele din vârfuri. Astfel, se calculează o culoare pentru fiecare pixel rasterizat pe baza triunghiului în fragment shader. Aceasta abordare poarta numele de model de colorare Phong. | + | |
- | Rezultatele vizuale ale celor 3 abordari se pot vedea mai jos. | + | 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: |
- | {{ :egc:laboratoare:lab07:iluminareglsl.png?500 |}} | + | <code cpp> |
+ | glUseProgram(program_id); | ||
- | În acest laborator se va discuta modelul de shading Phong. | + | //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)); | ||
- | </note> | + | // void glUniform1f(GLint location, GLfloat v0) |
- | </hidden> | + | glUniform1f(location, 1.5f); |
- | ==== Surse de lumină de tip spot ==== | + | // void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) |
+ | glUniform4f(location, 1, 0.5f, 0.3f, 0); | ||
- | Pentru a simula surse de lumina precum lanternele, ce imprastie lumina intr-o singura directie de iluminare, se poate utiliza orice model de reflexie. Singurele diferente fata de o sursa de lumina punctiforma este ca se introduce un factor de atenuare suplimentar si faptul ca sursa de lumina contine informatii suplimentare fata de o sursa de lumina punctiforma: | + | //void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2) |
+ | glUniform3i(location, 1, 2, 3); | ||
- | Nu toate sursele de lumina sunt punctiforme. Daca dorim sa implementam iluminarea folosind o sursa de lumina de tip spot trebuie sa tinem cont de o serie de constrangeri | + | //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> | ||
- | {{ :ppbg:laboratoare:spot-light.png?600 |}} | + | 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: |
- | Așa cum se poate vedea și în imaginea de mai sus, pentru a implementa o sursă de lumină de tip spot, avem nevoie de următorii parametri suplimentari: | + | <code cpp> |
- | * Direcția de iluminare a sursei de lumină | + | int location = glGetUniformLocation(program_id, "uniform_variable_name_in_shader"); |
- | * Unghiul de tăiere a iluminării, ce controlează deschiderea conului de lumină | + | </code> |
- | * Un model de atenuare pe bază de unghi a intensității iluminării, ce ține cont de valoarea de unghiului de tăiere | + | |
- | Astfel, un punct se află în conul de lumină al unei sursa de tip spot dacă condiția următoare este indepilită: | + | <note tip> |
- | <code glsl> | + | 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 . |
- | float cos_theta_angle = dot(-L, light_direction); | + | </note> |
- | float cos_phi_angle = cos(phi_angle); | + | |
- | if (cos_theta_angle > cos_phi_angle ) | + | ==== Comunicarea între vertex shader și fragment shader ==== |
- | { | + | |
- | // fragmentul este iluminat, astfel ca se calculeaza valoarea luminii conform modelului Phong | + | 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: |
- | // se calculeaza atenuarea luminii | + | * 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> | </code> | ||
- | Pentru a simula corect iluminarea de tip spot, este nevoie să tratăm și atenuarea iluminării corespunzătoare apropierii unghiului de taiere. Putem astfel sa utilizăm un model de atenuare patratică ce ofera un rezultat convingator. | + | <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. | ||
+ | </note> | ||
- | <code glsl> | + | ===== Laborator ===== |
- | float cos_theta_angle = dot(-L, light_direction); | + | |
- | float cos_phi_angle = cos(phi_angle); | + | |
- | // Quadratic attenuation | + | ==== Codul sursă al programelor de tip shader ==== |
- | float spot_linear_att_factor = (cos_theta_angle - cos_phi_angle) / (1.0f - cos_phi_angle); | + | |
- | float quadratic_spot_light_att_factor = pow(spot_linear_att_factor, 2); | + | |
- | </code> | + | |
- | ==== Biblioteca GLM ==== | + | Î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: |
- | === Matrici de transformare === | + | {{ :ppbg:laboratoare:shaders-lab.png?250 |}} |
- | Biblioteca GLM ne pune la dispoziție metode de construcție a matricilor celor 3 tipuri de transformări analizate: translatie, modificare de scară și rotație. Următoarele 2 lanțuri de transformări sunt identice: | + | ==== Erori de compilare sau de legare a programelor de tip shader ==== |
- | <code cpp> | + | 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ă: |
- | glm::mat4 model = glm::mat4(1); | + | |
- | model = glm::translate(model, glm::vec3(-5, 1.5f, 1)); | + | |
- | model = glm::rotate(model, glm::radians(60.0f), glm::vec3(0, 1, 0)); | + | |
- | model = glm::scale(model, glm::vec3(0.1f)); | + | |
- | </code> | + | |
+ | <code glsl> | ||
+ | #version 330 | ||
- | <code cpp> | + | uniform mat4 Model; |
- | glm::mat4 model = glm::mat4(1); | + | |
- | model *= transform3D::Translate(glm::vec3(-5, 1.5f, 1)); | + | |
- | model *= transform3D::RotateOY(glm::radians(60.0f)); | + | |
- | model *= trasnform3D::Scale(glm::vec3(0.1f)); | + | |
- | </code> | + | |
- | === Aplicarea matricilor de transformare === | + | void main() |
+ | { | ||
+ | vec4 pos = Model * ; | ||
+ | } | ||
- | Dacă dorim să rotim un vector cu 30 de grade față de axa OY, putem utiliza matricile de mai sus după cum urmează: | + | </code> |
- | <code cpp> | + | Eroarea de compilare este marcată în consolă în felul următor: |
- | glm::vec3 v = glm::vec3(1, 0, 0); | + | |
- | glm::mat4 rotation = glm::rotate(glm::mat4(1), glm::radians(30.0f), glm::vec3(0, 1, 0)); | + | {{ :ppbg:laboratoare:shaders-compilation-error.png?600 |}} |
- | // First option | + | Textul erorii menționează la început, prin marcajul ''0(7)'', că eroarea se găsește la linia 7 din codul sursă. |
- | v = glm::mat3(rotation) * v; | + | |
- | // Second option | + | Textul posibilelor erori de legare dintre două programe de tip shader apar tot în consolă. |
- | v = glm::vec3(rotation * glm::vec4(v, 1.0f)); | + | |
- | </code> | + | |
- | === Transmiterea de vectori în atribute de tip uniform === | ||
- | Pentru a transmite un vector de obiecte ce au tipuri de date din biblioteca GLM într-un atribut de tip uniform, la un shader, se poate utiliza: | + | ==== Încărcarea modelelor 3D din fișiere ==== |
- | <code cpp> | + | 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: |
- | glm::vec3 light_positions[10]; | + | |
- | glUniform3fv(location, 10, glm::value_ptr(light_positions[0])); | + | <code cpp> |
+ | Mesh* mesh = new Mesh("sphere"); | ||
+ | mesh->LoadMesh(PATH_JOIN(window->props.selfDir, RESOURCE_PATH::MODELS, "primitives"), "sphere.obj"); | ||
+ | meshes[mesh->GetMeshID()] = mesh; | ||
</code> | </code> | ||
- | ==== Detalii de implementare ==== | + | 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 :) . |
- | * Calculele de iluminare se vor face în spatiul lumii, deci înainte de a fi folosite, poziția și normala vor trebui aduse din spatiul obiectului în spatiul lumii. Aceste calcule se realizeaza in vertex shader si noua pozitie si vector normal se transmit spre fragment shader. Calculul se poate face astfel: | + | <note important> |
- | * pentru poziție: <code glsl>vec3 world_position = (model_matrix * vec4(v_position,1)).xyz;</code> | + | 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 normală: <code glsl>vec3 world_normal = normalize( mat3(model_matrix) * v_normal );</code> | + | |
- | * Vectorul normal N trebuie renormalizat in fragment shader, deoarece dupa procesul de interpolare, lungimea lui nu se pastreaza: <code glsl>vec3 N = normalize( world_normal );</code> | + | |
- | * Vectorul direcției luminii L: <code glsl>vec3 L = normalize( light_position - world_position );</code> | + | |
- | * Vectorul direcției din care priveste observatorul V: <code glsl>vec3 V = normalize( eye_position - world_position );</code> | + | |
- | + | ||
- | <note tip> | + | |
- | Funcții GLSL utile care pot fi folosite pentru implementarea modelului de iluminare | + | |
- | * normalize(V) – normalizează vectorul V | + | |
- | * normalize(V1+V2) – normalizează vectorul obținut prin V1+V2 | + | |
- | * normalize(P1-P2) - returnează un vector de direcție normalizat între punctele P1 și P2 | + | |
- | * dot(V1,V2) – calculează produsul scalar dintre V1 și V2 | + | |
- | * pow(a, shininess) – calculează a la puterea shininess | + | |
- | * max(a,b) – returnează maximul dintre a și b | + | |
- | * distance(P1,P2) – returnează distanța euclidiană dintre punctele P1 și P2 | + | |
- | * reflect(V,N) - calculează vectorul de reflexie pornind de la incidenta V și normala N | + | |
</note> | </note> | ||
===== Cerințe laborator ===== | ===== Cerințe laborator ===== | ||
- | + | <note important> | |
- | <note tip> | + | 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. |
- | Prin apasarea tastelor **W**, **A**, **S**, **D**, **E** si **Q** puteti controla pozitia unei surse de lumina. Prin apasarea tastei **F** puteti interschimba intre controlul a doua surse de lumina, una punctiforma si cealalta de tip spot. | + | |
</note> | </note> | ||
+ | - 0.05p - Completați metoda ''CreateShader()'' pentru a crea un program de tip shader. | ||
+ | - 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 - Completați codul sursă al programelor de tip shader: | ||
+ | - 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'': | ||
+ | * Se declară atributele de intrare pentru programul de tip vertex shader folosind layout location <code glsl> | ||
+ | layout(location = 0) in vec3 v_position; | ||
+ | // same for the rest of the attributes ( check Lab5.cpp CreateMesh() ); | ||
+ | </code> | ||
+ | * Se declară atributele de ieșire către programul de tip fragment shader <code glsl> | ||
+ | out vec3 color; | ||
+ | // same for other attributes | ||
+ | </code> | ||
+ | * Se salvează valorile pentru atributele de ieșire, în ''main()'' <code glsl> | ||
+ | color = v_color; | ||
+ | // same for other attributes | ||
+ | </code> | ||
+ | * Se calculează poziția în spațiul de decupare a vârfului primit folosind matricile Model, View, Projection <code glsl> | ||
+ | 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 |}} | ||
- | - 0.05p - Trimiteti toate informatiile necesare calcularii iluminarii in atribute de tip uniform. | ||
- | - 0.15p - Implementati calculul de iluminare in fisierele sursa ale programelor de tip shader: | ||
- | * Completati in fisierul ''VertexShader.glsl'' calculul pozitiei varfului si a vectorului normal in spatiul lumii si transmiteti-le spre fragment shader. | ||
- | * Completati in fisierul ''FragmentShader.glsl'': | ||
- | * Calculul componentelor difuze si speculare ale iluminarii unei surse de lumina | ||
- | * Calculul factorului de atenuare a iluminarii pe baza de distanta | ||
- | * Calculul final de obtinere a iluminarii prin combinarea componentelor difuza si speculara, a factorului de atenuare si a culorii iluminarii | ||
- | * Calculul componentei ambientale a iluminarii globale | ||
- | * Până în acest punct, rezultatul pe care ar trebui să îl obțineti este următorul. Culoarea fiecarei lumini este aleasa aleator la inceputul fiecarei executii a aplicatiei grafice {{ :ppbg:laboratoare:illumination.png?600 |}} | ||
- | - 0.05p - Implementati calculul de iluminare pentru sursele de lumina de tip spot: | ||
- | * Completati in fisierul ''FragmentShader.glsl'': | ||
- | * Calculul factorului de atenuare specific unei surse de lumina de tip spot | ||
- | * Calculul final de obtinere a iluminarii prin combinarea componentelor difuza si speculara, a factorului de atenuare si a culorii iluminarii | ||
- | - 0.05p - Pentru sursa de lumina de tip spot ce poate fi controlata de la tastatura, prin apasarea unor taste, modificati directia de iluminare si unghiul. Directia de vizualizare trebuie sa se poata roti fata de axa OX si OZ, in ambele sensuri, iar unghiul trebuie sa se poata mari si micsora. |