This shows you the differences between two versions of the page.
ppbg:laboratoare:04 [2023/11/01 22:31] andrei.lambru [API-ul grafic OpenGL] |
ppbg:laboratoare:04 [2024/11/01 00:22] (current) andrei.lambru |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Laboratorul 04 ====== | ====== Laboratorul 04 ====== | ||
- | <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 acest [[:ppbg:laboratoare:03|laboratorul 3]], va trebui să le 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 2 și al laboratorului 3. În situatia în care nu ați rezolvat [[:ppbg:laboratoare:02|laboratorul 2]] sau [[:ppbg:laboratoare:03|laboratorul 3]], va trebui să le realizați mai întâi pe ele și ulterior să reveniți la cerințele celui curent. |
+ | </note> | ||
+ | <note tip> | ||
**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 :) . | **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> | ||
+ | |||
+ | ===== Lanțul de transformări 3D ===== | ||
+ | |||
+ | În laboratorul anterior, am văzut cum putem desena obiecte pe ecran, independent de spațiul în care am definit coordonatele vârfurilor unui obiect. Mai precis, am folosit un spațiu logic, pe care l-am denumit fereastră peste spațiul 2D și în care am definit inițial coordonatele vârfurilor. Prin utilizarea transformării fereastră-poartă, am putut desena obiectele într-o poartă de afișare de pe ecran, la orice rezoluție de pixeli a acestei porți. De asemenea, am văzut cum se pot prelucra obiectele prin diferite transformări. În laboratorul anterior, am analizat doar 3 tipuri de transformări de bază: translație, modificare de scară și rotație. | ||
+ | |||
+ | ===== Spațiul de decupare ===== | ||
+ | |||
+ | Lucrul cu un spațiu 3D nu este cu mult diferit față de lucrul cu un spațiu 2D. Pentru desenarea pe ecran a unor obiecte dintr-un spațiu 3D, este necesar să ne stabilim un spațiu logic, din care să transformăm coordonatele vârfurilor în coordonatele grilei de pixeli. În grafica pe calculator, spațiul standard ales pentru acest proces este volumul unui cub, cu latura de dimensiune 2 și centrat în originea axelor de coordonate. Astfel, colțul stânga-jos-față al cubului are coordonatele $(-1, -1, -1)$ și colțul dreapta-sus-spate are coordonatele $(1, 1, 1)$. O reprezentare vizuală a acestui volum se poate găsi în imaginea următoare: | ||
+ | |||
+ | {{ :ppbg:laboratoare:clip_space.png?300 |}} | ||
+ | |||
+ | Coordonatele vârfurilor ce se regăsesc în acest volum pot fi aduse în grila de pixeli printr-o transformare fereastră-poartă, similar ca săptămâna trecută: | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | \begin{bmatrix} | ||
+ | x' \\ y' \\ z' \\ 1 | ||
+ | \end{bmatrix} = | ||
+ | \begin{bmatrix} | ||
+ | 1 & 0 & 0 & V_{x}\\ | ||
+ | 0 & 1 & 0 & V_{y}\\ | ||
+ | 0 & 0 & 1 & 0\\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | \begin{bmatrix} | ||
+ | V_{w}/2 & 0 & 0\\ | ||
+ | 0 & V_{h}/2 & 0\\ | ||
+ | 0 & 0 & 1/2 & 0\\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | \begin{bmatrix} | ||
+ | 1 & 0 & 0 & -1\\ | ||
+ | 0 & 1 & 0 & -1\\ | ||
+ | 0 & 0 & 1 & -1\\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x \\ y \\ z \\ 1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | $$ | ||
<note tip> | <note tip> | ||
- | Pentru rezolvarea cerințelor din cadrul acestui labroator: | + | În cuvinte, se normalizează coordonatele vârfurilor din spațiul $([-1, 1], [-1, 1], [-1, 1])$ în spațiul $([0, viewportWidth], [0, viewportHeight], [0,1])$. |
- | - [[https://github.com/UPB-Graphics/gfx-framework-ppbg | Descărcați]] framwork-ul de laborator și copiați, din arhiva descărcată, directorul **Lab4**, î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/lab4/lab4.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> | ||
+ | Observăm că se proiectează paralel coordonatele vârfurilor pe fața $z=-1$ a cubului. Practic, se păstrează componentele $(x, y)$ ale coordonatelor din vârfuri și putem să consideram că pentru componentele $(x, y)$ se utilizează o transformare fereastră-poartă dintr-o fereastră peste spatiul 2D. | ||
- | ===== Aplicatii grafice in timp real ===== | + | Pentru componenta $z$, din moment ce nu se poate transforma direct în coordonatele grilei de pixeli, se normalizează între 0 și 1 și se utilizează cu scopul de valore de adâncime în procesul de rasterizare studiat în [[:ppbg:laboratoare:01#testul_de_adancime | laboratorul 1]] :) . |
- | 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 :) . | + | ==== Procesul de decupare ==== |
- | 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. | + | Pe ecran ajung să fie desenate doar suprafețele dintr-un triunghi ce se regăsesc în volumul cubului de latură 2, centrat în originea axelor de coordonate. Cu toate acestea, este posibil ca nu toată suprafața unui triunghi să se afle în volumul cubului, astfel că în procesul de rasterizare trebuie să se realizeze procesări suplimentare pentru a nu se desena în afara grilei de pixeli. Pentru a evita realizarea acestor calcule, putem decupa triunghiurile la limitele volumului și să utilizăm în procesul de rasterizare doar triunghiurile rezultate în urma decupării. Scenariile în care acest mecanism produce 1, 2 și 3 triunghiuri pot fi observate în imaginea de mai jos. |
- | <code cpp> | + | {{ :ppbg:laboratoare:clip.png?400 |}} |
- | while (!window->ShouldClose()) | + | |
- | { | + | |
- | LoopUpdate(); | + | |
- | } | + | |
- | </code> | + | |
- | ==== Analiza unui cadru ==== | + | Cele 1, 2 sau 3 triunghiuri, rezultate după procesul de decupare, sunt utilizate în procesul de rasterizare. Exemplificarea din imagine este pentru o decupare 2D, dar ea se poate extinde la volumul unui cub, în care un triunghi se decupează la limitele volumului, în spațiul 3D. |
- | In interiorul acestei bucle, in interiorul metodei ''LoopUpdate'' se realizeaza urmatorul proces pentru fiecare cadru: | + | Mai există un scenariu suplimentar, ce nu este acoperit în imagine, anume situația în care nicio parte din suprafața triunghiului nu se regăsește în interiorul volumului. În acest scenariu, nu este transmisă nicio primitivă în procesul de rasterizare. |
- | <code cpp> | + | <note> |
- | // 1. Polls and buffers the events | + | Datorită faptului că acest proces de decupare are loc în spațiul finit descris mai sus, determinat de un cub de latură 2, centrat în originea axelor de coordonate, acest spațiu poartă numele de **spațiu de decupare**. El este cunoscut în limba engleză sub numele de **clip space**. |
- | window->PollEvents(); | + | </note> |
- | // 2. Computes frame deltaTime in seconds | + | ===== Transformarea de proiecție perspectivă ===== |
- | ComputeFrameDeltaTime(); | + | |
- | // 3. Calls the methods of the instance of InputController in the following order | + | Definirea unui spațiu finit pe care să îl normalizăm ulterior în grila de pixeli ne oferă o flexibilitate foarte mare. Dezavantajul aplicării directe a acestei abordări este că geometria obiectelor nu se regăsește întotdeauna în interiorul volumului de decupare. De fapt, de cele mai multe ori, obiectele nu se află în acest volum. Astfel, trebuie să aducem noi geometria în acesta printr-o transformare suplimentară. Această transformare poate să fie o transformare de proiecție perspectivă pentru a simula procesul de transport al luminii de pe suprafețele scenei la observator. |
- | // 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 | + | Tot ce „vede” ochiul uman este lumina, astfel că procesul de desenare a suprafețelor unui obiect pe ecran simulează procesul de achiziție a luminii care ajunge de pe suprafețele obiectului la ochiul uman :) . Pentru simplitate, vom considera un singur punct de convergență pentru simularea razelor de lumină. Astfel, putem să folosim o transformare perspectivă a coordonatelor vârfurilor pentru a aduce geometria în spațiul de decupare și ulterior pentru a o desena pe ecran. |
- | FrameStart(); | + | |
- | Update(static_cast<float>(deltaTime)); | + | |
- | FrameEnd(); | + | |
- | // 5. Swap front and back buffers - image will be displayed to the screen | + | Volumul proiecției perspectivă este un trunchi de piramidă, frustum. Standard, acest volum este descris cu vârful în originea axelor de coordonate și cu baza orientată de-a lungul axei $Z$, în sens negativ. Acest volum se normalizează în volumul spațiului de decupare. O reprezentare vizuală a lui se găsește în imaginea de mai jos. |
- | window->SwapBuffers(); | + | |
- | </code> | + | |
- | Pasii din codul de mai sus sunt: | + | {{ :ppbg:laboratoare:perspective.png?500 |}} |
- | - Fereastra din interfata grafica a sistemului de operare este creata prin intermediul bibliotecii GLFW. Tot prin aceasta biblioteca obtinem evenimente externe aplicatiei care se pot produce la nivelul ferestrei, precum apasarea de catre utilizator a unei taste sau a unui buton de la mouse, redimensionarea ferestrei sau inchiderea ei. Vom reveni asupra acestor evenimente mai jos. | + | |
- | - Se calculeaza timpul de desenare a cadrului anterior, denumit ''deltaTime''. | + | |
- | - Se apeleaza metodele care trateaza fiecare tip de eveniment extern ferestrei. Aceste metode se gasesc in fiecare clasa ''LabX'' si sunt discutate mai jos. | + | |
- | - Se apeleaza exact o data o serie de metode ce se regasesc in fiecare clasa ''LabX''. | + | |
- | - Se blocheaza procesul curent de pe CPU pentru a se astepta incheierea tuturor proceselor de desenare realizate de catre procesorul grafic. | + | |
- | ==== Interactiunea cu utilizatorul ==== | + | Avem mai multe posibilități de a descrie forma acestui volum, dar în continuare va fi prezentată cea care apare uzual în grafica pe calculator. Pentru descrierea frustumului folosim: |
+ | * Distanța până la un plan apropiat, pe care se va normaliza fața $z=-1$ a spațiului de decupare (**near**) | ||
+ | * Distanța până la un plan îndepărtat, pe care se va normaliza fața $z=1$ a spațiului de decupare (**far**) | ||
+ | * Un unghi pentru deschiderea verticală a piramidei (**fovy**) | ||
+ | * Un raport de aspect al porții de afișare, lățime / înălțime, cu ajutorul căruia se poate obține unghiul de deschidere orizontală a piramidei (**aspect**) | ||
- | Interactiunea utilizatorului cu fereastra poate fi: apasarea unei taste de tastatura, apasarea unui buton de la mouse, redimensionarea ferestrei si inchiderea ei. | + | Formula de calcul a proiecției perspective este: |
- | Interactiunea utilizatorului cu tastele de la tastatura, in situatia in care fereastra este selectata, este de 3 feluri: | ||
- | - Apasarea unei taste pentru prima data in cadrul curent, cunoscuta in limba engleza sub numele de **key press** | ||
- | - Neapasarea unei taste la cadrul curent, in situatia in care tasta a fost apasata la cadrul anterior, cunoscuta in limba engleza sub numele de **key release** | ||
- | - Apasarea unei taste la cadrul curent, indiferent cand a fost apasata prima data, cunoscuta in limba engleza sub numele de **key hold** | ||
- | 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: | ||
- | <code cpp> | + | $$ |
+ | x' = \frac{x}{ -z \cdot tan(\frac{fovy}{2})\cdot aspect}\\ | ||
+ | y' = \frac{y}{ -z \cdot tan(\frac{fovy}{2})}\\ | ||
+ | z' = \frac{c2\cdot z-c1}{-z}\\ c1=\frac{(-2) \cdot far \cdot near}{far- near}\\ c2=\frac{(-1)\cdot(far+near)}{far - near} | ||
+ | $$ | ||
- | void Lab4::OnKeyPress(int key, int mods) | ||
- | { | ||
- | if (key == GLFW_KEY_R) { | ||
- | printf("S-a apasat tasta R."); | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 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 [[https://github.com/UPB-Graphics/gfx-framework-ppbg/blob/master/src/core/window/input_controller.h | fisierul header al clasei InputController]]. | + | Această formulă nu poate fi scrisă sub formă matriceală, deoarece este un sistem de ecuații neliniare. După cum vedem, în calcularea componentelor $(x', y', z')$ ale coordonatei obținute în urma transformării de proiecție perspectivă, se utilizează valoarea componentei $z$ a coordonatei pentru care se aplică transformarea. Din acest motiv, este necesar să realizăm acest proces în 2 pași: |
+ | - Se creează o matrice pentru tranformarea de proiecție perspectivă prin care se aplică formula de mai sus, fără împărțirea componentelor coordonatei rezultate în urma transformării la valoarea componentei $z$ a coordonatei pentru care s-a realizat transformarea. Se utilizează această matrice pentru transformarea de proiecție perspectivă $$ | ||
+ | \begin{bmatrix} | ||
+ | {x}'\\ | ||
+ | {y}'\\ | ||
+ | {z}'\\ | ||
+ | {w}' | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | \frac{1}{tan(\frac{fovy}{2})\cdot aspect} & 0 & 0 & 0 \\ | ||
+ | 0 & \frac{1}{tan(\frac{fovy}{2})} & 0 & 0 \\ | ||
+ | 0 & 0 & \frac{(-1)\cdot (far+near)}{far - near} & \frac{(-2) \cdot far \cdot near}{far - near} \\ | ||
+ | 0 & 0 & -1 & 0 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
+ | - Împărțirea componentelor $(x', y', z')$ cu valoarea componentei $z$ a coordonatei vârfului pentru care s-a aplicat transformarea | ||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | x'' = \frac{x'}{-z}\\ | ||
+ | y'' = \frac{y'}{-z}\\ | ||
+ | z'' = \frac{z'}{-z} | ||
+ | $$ | ||
+ | |||
+ | Pentru a nu fi nevoie să se rețină apriori valoarea componentei $z$, care poate fi obținută în urma aplicării unui lanț de transformări, astfel că transformarea de proiecție perspectivă se poate afla în interiorul unei matrici compuse dintr-un lanț de transformări, se utilizează un artificiu. Se poate observa că matricea de mai sus este construită astfel încât să păstreze în componenta $w'$ , valoarea componentei $z$, negativă: | ||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | w'=0\cdot x + 0\cdot y + (-1) \cdot z + 0 \cdot 0 | ||
+ | $$ | ||
+ | |||
+ | Din acest motiv, cel de-al doilea pas de mai sus devine: | ||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | x'' = \frac{x'}{w'}\\ | ||
+ | y'' = \frac{y'}{w'}\\ | ||
+ | z'' = \frac{z'}{w'} | ||
+ | $$ | ||
+ | |||
+ | <note> | ||
+ | Acest al doilea pas este cunoscut sub numele de **împărțire perspectivă** și este implementat nativ în banda grafică a procesorului. Observăm că această transformare este ultima care se aplică înainte de transformarea coordonatelor din vârfuri în spațiul de decupare. Din acest motiv, înainte de decuparea propriu-zisă, procesorul grafic realizează împărțirea perspectivă automat, hardware sau software în driver-ul companion al procesorului grafic. | ||
+ | </note> | ||
<note tip> | <note tip> | ||
- | Apelul fiecarei metode se realizeaza in cadrul pasului 3 descris mai sus. | + | Spațiul finit determinat de volumul trunchiului de piramidă ce are vârful în originea axelor de coordonate și baza orientata de-a lungul axei $Z$, în sens negativ, poartă numele de **spațiu de vizualizare**. În limba engleză, acest spațiu se regăsește sub numele de **view space**. |
</note> | </note> | ||
- | 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. | + | <note> |
+ | Formula de calculare a proiecției perspective pentru coordonata unui vârf, pe planul din apropiere a trunchiului de piramidă, se obține din următoarea observație: conform imaginii de mai jos, se poate vedea că triunghiul determinat de vectorii marcați cu $near$ și $y'$ este asemenea cu cel determinat de vectorii marcați cu $-z$ și $y$. | ||
- | Suplimentar acestor metode specifice tratarii interactiunii cu utilizatorul, se poate folosi atributul ''window'', intern clasei, pentru a verifica existenta anumitor evenimente: | + | {{ :ppbg:laboratoare:perspective2.png?600 |}} |
- | <code cpp> | + | Astfel, conform teoremei lui Thales, rezultă că: |
- | 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."); | + | \frac{y'}{near} = \frac{y}{-z}\\ |
- | } | + | |
- | </code> | + | |
- | ==== Animatii independente de numarul de cadre desenate pe secunda ==== | + | $$ |
- | 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: | ||
- | <code> | ||
- | object_position.z += 5 * 0.016; | ||
- | </code> | ||
- | 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''. | + | $$ |
- | <code> | + | y'=\frac{near\cdot y}{-z}\\ |
- | object_position.z += 5 * deltaTime; | + | $$ |
- | </code> | + | |
- | ===== API-ul grafic OpenGL ===== | ||
- | 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. | + | Considerăm că valoarea componentei $z$ este negativă, deoarece o tratăm pe post de distanță și orientarea bazei trunchiului de piramidă este din construcție de-a lungul axei $Z$, în sens negativ. |
- | {{ :ppbg:laboratoare:graphicsapi.png?500 |}} | + | Suplimentar, presupunem că avem distanța până la limita de jos, denumită **bottom** și distanța până la limita de sus, denumtiă **top**, a feței din apropiere a frustumului, după cum se poate vedea în imaginea de mai sus. Dorim să normalizăm componenta $y$ din intervalul $[bottom, top]$ în $[-1, 1]$. Aceasta este o transformare fereastră-poartă. Împreună cu formula anterioara pentru $y'$, obținem că $y'$ este: |
- | 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. | ||
- | ==== Curatarea informatiei de pe ecran ==== | + | $$ |
+ | y'=\frac{2\cdot near\cdot y}{-z \cdot (top-bottom)}\\ | ||
+ | $$ | ||
- | 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: | + | Din construcție, avem un singur unghi de deschidere verticală a trunchiului de piramidă, astfel că avem aceeași valoare pentru limitele bottom și top: |
- | <code cpp> | ||
- | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | ||
- | </code> | ||
- | 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: | ||
- | <code cpp> | + | $$ |
- | glClearColor(0, 0, 0, 1); | + | bottom= -top\\ |
- | </code> | + | y'=\frac{2\cdot near\cdot y}{-z \cdot 2 \cdot top}=\frac{near\cdot y}{-z \cdot top}\\ |
+ | $$ | ||
- | ==== Poarta de afisare ==== | + | Se poate observa că: |
- | Se poate stabili zona din ecran, poarta de afisare, in care sa se deseneze obiectele prin directiva: | ||
- | <code cpp> | + | $$ |
- | glViewport(0, 0, 1280, 720); | + | \frac{top}{near}=tan(\frac{fovy}{2}) |
- | </code> | + | $$ |
- | ==== Modele 3D ==== | + | Astfel, avem pentru $y'$: |
- | 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 [[https://github.com/UPB-Graphics/gfx-framework-ppbg/blob/master/src/core/gpu/mesh.h#L48|Mesh]]. | + | $$ |
+ | y' = \frac{y}{ -z \cdot tan(\frac{fovy}{2})}\\ | ||
+ | $$ | ||
- | === Vertex Buffer Object (VBO) === | + | Pentru simplitate și un control ridicat, în loc să se descrie volumul frustumului prin 2 valori, una care descrie unghiul de deschidere verticală și una pentru unghiul de deschidere orizontală, se utilizează raportul de aspect al ecranului: |
+ | |||
+ | |||
+ | $$ | ||
+ | |||
+ | aspect = \frac{V_{w}}{V_h}\\ | ||
+ | x' = \frac{x}{ -z \cdot tan(\frac{fovy}{2})\cdot aspect}\\ | ||
+ | $$ | ||
+ | |||
+ | Daca formula pentru $(x, y)$ reiese din proiecția coordonatelor vârfului pe planul din apropiere al trunchiului de piramidă, formula pentru componenta $z$ este artificială și reiese exclusiv din dorința de a creea o normalizare **neliniară** între distanța planului din apropiere și distanța planului îndepărtat. Se dorește ca pentru obiectele din apropiere de planul apropiat, $z=-near$, să fie alocat un interval mult mai mare în spațiul de decupare, decât pentru obiectele apropiate de planul $z=-far$. Din acest motiv, formula standard aleasă inițial este: | ||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | |||
+ | z'=\frac{c1}{-z}+c2 | ||
+ | $$ | ||
+ | |||
+ | Parametrii $c1$ și $c2$ sunt cei din formula originală. Problema acestei formule este că nu se imparte la valoarea $z$, ceea ce înseamnă că pentru $z'$ nu s-ar aplica împărțirea perspectivă. Pentru a face uniform procesul de împărțire perspectivă, se modifică formula și obținem versiunea finală: | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | |||
+ | z'=\frac{c2\cdot z-c1}{-z} | ||
+ | $$ | ||
- | Un vertex buffer object reprezintă un container în care stocăm date ce țin de conținutul vârfurilor precum: | ||
- | * poziție | ||
- | * normală | ||
- | * culoare | ||
- | * coordonate de texturare | ||
- | * etc... | ||
- | Un vertex buffer object se poate crea prin comanda OpenGL **[[https://www.opengl.org/sdk/docs/man/html/glGenBuffers.xhtml|glGenBuffers]]**: | ||
- | <code cpp> | ||
- | 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 | ||
- | </code> | ||
- | <note> | ||
- | 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> | ||
- | 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]]**: | + | ===== Transformarea de vizualizare ===== |
- | <code cpp> | + | |
- | glDeleteBuffers(1, &VBO_ID); | + | |
- | </code> | + | |
- | 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]]**: | + | Prin transformarea de proiecție perspectivă, descrisă mai sus, putem modifica coordonatele vârfurilor rețelei de triunghiuri pentru a le așeza în spațiul de decupare și de acolo putem desena triunghiurile pe ecran. Cu toate acestea, coordonatele obiectelor noastre nu se regăsesc întotdeauna în spațiul de vizualizare, reprezentat, cum s-a menționat mai sus, de volumul unui trunchi de piramidă cu vârful în originea axelor de coordonate și cu baza orientată de-a lungul axei $Z$, în sens negativ. În situația în care dorim să desenăm scena dintr-un alt punct de vedere, trebuie să definim conceptul de observator. Proprietățile care definesc un observator sunt: |
+ | * O poziție $P$ în spatiu, de unde „privește” obiectele din scenă; | ||
+ | * O direcție $\vec{F}$ în care „privește” obiectele. Companion acestei direcții, mai este necesar un vector $\vec{R}$ ce descrie direcția dreaptă a observatorului, daca iși rotește direcția în care priveste scena cu 90 de grade față de axa OY și un vector $\vec{U}$ ce descrie direcția sus a observatorului. Cei 3 vectori sunt ortogonali, mai exact, unghiul dintre oricare 2 vectori din cei 3 este de 90 de grade. | ||
- | <code cpp> | + | Pe baza acestor informații, putem să creăm o transformare care modifică obiectele din scenă, din spațiul în care se află, într-un spațiu în care observatorul are poziția în originea axelor de coordonate și „privește” scena de-a lungul axei $Z$, în sens negativ. Peste acest spațiu se poate suprapune volumul de vizualizare, utilizat pentru transformarea proiecției perspective. |
- | glBindBuffer(GL_ARRAY_BUFFER, VBO_ID); | + | |
- | </code> | + | |
- | Î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]]**: | + | Transformarea de vizualizare arată în felul următor: |
- | <code cpp> | ||
- | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), &vertices[0], GL_STATIC_DRAW); | ||
- | </code> | ||
- | * 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. | + | $$ |
- | * **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]] | + | \begin{bmatrix} |
+ | {x}'\\ | ||
+ | {y}'\\ | ||
+ | {z}'\\ | ||
+ | 1 | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | \vec{R}_x & \vec{R}_y & \vec{R}_z & 0 \\ | ||
+ | \vec{U}_x & \vec{U}_y & \vec{U}_z & 0 \\ | ||
+ | \vec{F}_x & \vec{F}_y & \vec{F}_z & 0 \\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | \begin{bmatrix} | ||
+ | 1 & 0 & 0 & -P_x \\ | ||
+ | 0 & 1 & 0 & -P_y \\ | ||
+ | 0 & 0 & 1 & -P_z \\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
+ | Se poate observa că prima transformare aplicată, ultima în lanțul de înmulțiri de matrici, este o translație inversă a coordonatelor poziției observatorului în spațiul 3D, similară cu cea discutată în laboratorul anterior pentru spațiul 2D. A doua transformare aplicată este o rotație. Pentru construcția acestei matrici s-a folosit [[https://ro.wikipedia.org/wiki/Procedeul_Gram%E2%80%93Schmidt | procedeul Gram–Schmidt]]. | ||
<note tip> | <note tip> | ||
- | 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.\\ | + | Spațiul în care se regăsesc: poziția observatorului, vectorii ce descriu direcția de „privire”, direcția dreapta și sus, împreună cu coordonatele tuturor obiectelor „privite” de către observator, poartă numele de **spațiu al lumii**. În limba engleză, se întâlnește denumirea de **world space**. |
- | 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> | ||
- | === Index Buffer Object (IBO) === | + | ===== Transformarea de modelare ===== |
- | 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. | + | Coordonatele vârfurilor din rețeaua de triunghiuri ce descrie suprafața unui obiect se declară inițial într-un spațiu convenabil creării acelui obiect. |
- | <code cpp> | + | Pentru a modifica obiectul și a-l așeza sub forma dorită în punctul final din scenă, se folosesc transformările de translație, de modificare a scării și de rotație, discutate în laboratorul anterior, adaptate pentru un spațiu 3D. |
- | 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> | + | |
- | 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. | + | ==== Transformarea de translație ==== |
- | === Vertex Array Object (VAO) === | + | Forma matriceală a transformărilor de translație și de modificare a scării este similară cu cea a transformărilor utilizate într-un spațiu 2D. Fiecare din cele două matrici se modifică doar prin adăugarea unei noi linii și a unei noi coloane pentru a adapta cea de-a treia dimensiune ce apare într-un spațiu 3D. |
- | Î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 **[[https://www.opengl.org/sdk/docs/man4/html/glGenVertexArrays.xhtml|glGenVertexArrays]]**: | ||
- | <code cpp> | + | $$ |
- | unsigned int VAO; | + | \begin{bmatrix} |
- | glGenVertexArrays(1, &VAO); | + | {x}'\\ |
- | </code> | + | {y}'\\ |
+ | {z}'\\ | ||
+ | 1 | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | 1 & 0 & 0 & t_x\\ | ||
+ | 0 & 1 & 0 & t_y\\ | ||
+ | 0 & 0 & 1 & t_z\\ | ||
+ | 0 & 0 & 0 &1 | ||
+ | \end{bmatrix} | ||
- | Este legat cu **[[https://www.opengl.org/sdk/docs/man4/html/glBindVertexArray.xhtml|glBindVertexArray]]**: | + | \begin{bmatrix} |
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
- | <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. | ||
- | 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. | + | ==== Transformarea de modificare a scării ==== |
+ | |||
+ | |||
+ | $$ | ||
+ | \begin{bmatrix} | ||
+ | {x}'\\ | ||
+ | {y}'\\ | ||
+ | {z}'\\ | ||
+ | 1 | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | s_x & 0 & 0 &0 \\ | ||
+ | 0 & s_y & 0 &0 \\ | ||
+ | 0 & 0 & s_z &0 \\ | ||
+ | 0 & 0 & 0 &1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
+ | |||
+ | |||
+ | ==== Transformarea de rotație ==== | ||
+ | |||
+ | Mecanismul de rotație, studiat în laboratorul trecut, rotește o coordonată a unui punct într-un spațiu 2D cu un unghi a, //în sens trigonometric față de originea axelor de coordonate//. Acest mecanism NU poate fi adaptat direct la un spațiu 3D. | ||
+ | |||
+ | Astfel, transformarea de rotație dintr-un spațiu 2D este utilizată într-un spațiu 3D pentru a roti o coordonată într-un singur plan din spațiul 3D. Mai exact într-un plan paralel cu unul din cele 3 planuri ce trec prin originea axelor de coordonate. Există câte o matrice de transformare diferită pentru fiecare din cele 3 planuri. În situația în care dorim sa creăm o transformare de rotație într-un plan oarecare, trebuie să o obținem prin compunerea din mai multe transformări de bază. | ||
+ | |||
+ | Astfel, avem transformarea de rotație într-un plan paralel cu planul XOY: | ||
+ | |||
+ | |||
+ | $$ | ||
+ | \begin{bmatrix} | ||
+ | {x}'\\ | ||
+ | {y}'\\ | ||
+ | {z}'\\ | ||
+ | 1 | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | cos(u) & -sin(u) & 0 & 0\\ | ||
+ | sin(u) & cos(u) & 0 & 0\\ | ||
+ | 0 & 0 & 1 & 0 \\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
+ | |||
+ | |||
+ | Putem să considerăm că această transformare este față de axa OZ. | ||
+ | |||
+ | |||
+ | Transformarea de rotație într-un plan paralel cu planul XOZ, sau față de axa OY este: | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | $$ | ||
+ | \begin{bmatrix} | ||
+ | {x}'\\ | ||
+ | {y}'\\ | ||
+ | {z}'\\ | ||
+ | 1 | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | cos(u) & 0 & sin(u) & 0\\ | ||
+ | 0 & 1 & 0 & 0 \\ | ||
+ | -sin(u) & 0 & cos(u) & 0\\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | Transformarea de rotație într-un plan paralel cu planul YOZ, sau față de axa OX este: | ||
+ | |||
+ | $$ | ||
+ | \begin{bmatrix} | ||
+ | {x}'\\ | ||
+ | {y}'\\ | ||
+ | {z}'\\ | ||
+ | 1 | ||
+ | \end{bmatrix} = \begin{bmatrix} | ||
+ | 1 & 0 & 0 & 0 \\ | ||
+ | 0 & cos(u) & -sin(u) & 0 \\ | ||
+ | 0 & sin(u) & cos(u) & 0 \\ | ||
+ | 0 & 0 & 0 & 1 | ||
+ | \end{bmatrix} | ||
+ | |||
+ | \begin{bmatrix} | ||
+ | x\\ | ||
+ | y\\ | ||
+ | z\\ | ||
+ | 1 | ||
+ | \end{bmatrix} | ||
+ | $$ | ||
+ | |||
+ | |||
+ | <note tip> | ||
+ | Spațiul în care sunt definite inițial coordonatele vârfurilor poartă numele de **spațiu al obiectului**, sau în limba engleză, poate fi întâlnit cu denumirea de **object space**. | ||
</note> | </note> | ||
- | Î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. | + | ===== Eliminarea din procesul de rasterizare a triunghiurilor obturate ===== |
- | ==== Optiunea de optimizare Face Culling==== | + | O optimizare suplimentară, pe care o putem realiza în spațiul de decupare, pe lângă cea de decupare propriu-zisă, este să excludem din procesul de rasterizare triunghiurile care sunt complet obturate de altă geometrie. Pentru a nu face o procesare avansată la nivel de rețea de triunghiuri, putem să folosim următorul artificiu: |
- | 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. | + | În situția în care desenăm un model 3D etanș, care nu conține nicio gaură în suprafața lui, avem proprietatea că din orice punct am privi geometria, nu îi vom putea vedea niciodată interiorul. Astfel, avem următoarea formă de definire a unui triunghi: pe baza descrierii cu indici din ''vertices'', utilizată în laboratoarele anterioare, sensul în care sunt afișate vârfurile, pe baza ordinii din indici, ne determină nouă faptul că privim fațeta față sau fațeta spate a triunghiului. |
+ | |||
+ | Mai exact, dacă avem descrisă urmatoarea geometrie: | ||
- | 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> | <code cpp> | ||
- | // mode can be GL_CW (clockwise) or GL_CCW (counterclockwise) | + | vector<VertexFormat> vertices |
- | // the initial value is GL_CCW | + | { |
- | void glFrontFace(GLenum mode); | + | VertexFormat(glm::vec3(0, 2, 0)), |
+ | VertexFormat(glm::vec3(-1, 0, 0)), | ||
+ | VertexFormat(glm::vec3(1, 0, 0)), | ||
+ | } | ||
+ | |||
+ | vector<unsigned int> indices1 | ||
+ | { | ||
+ | 0, 1, 2, | ||
+ | } | ||
+ | |||
+ | vector<unsigned int> indices2 | ||
+ | { | ||
+ | 0, 2, 1, | ||
+ | } | ||
</code> | </code> | ||
- | <note> | + | În momentul în care geometria se află în spațiul de decupare, după aplicarea tuturor transformărilor, se observă sensul dat de coordonatele vârfurilor, în ordinea în care au fost definiți indicii. În situația în care sensul pe care îl fac coordonatele, conform imaginii de mai jos, este trigonometric, considerăm că ne uităm la fațeta față a triunghiului. |
- | 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. | + | |
+ | {{ :ppbg:laboratoare:cull.png?400 |}} | ||
+ | |||
+ | Prin acest proces, în situația în care desenăm suprafața unui model 3D etanș, putem exclude din procesul de rasterizare toate triunghiurile pentru care afișăm fațetele spate, deoarece avem garanția că ele sunt obturate de restul geometriei modelului. | ||
+ | |||
+ | <note tip> | ||
+ | Acest proces se întâlnește în limba engleză sub numele de **face culling**, adică tăierea de la desenare a anumitor triunghiuri, pe baza fațetei pe care o vedem. Avem opțiunea de a tăia de la desenare triunghiurile pentru care afișăm fațetele față sau pe cele pentru care afișăm fațetele spate. | ||
</note> | </note> | ||
- | În mod normal face-culling este dezactivat. Acesta poate fi activat folosind comanda [[https://www.opengl.org/sdk/docs/man4/html/glEnable.xhtml|glEnable]]: | + | ===== Privire de ansamblu ===== |
- | <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]]: | + | O privire de ansamblu a întrgului proces se poate regăsi în imaginea următoare: |
- | <code cpp> | + | |
- | glDisable(GL_CULL_FACE); | + | |
- | </code> | + | |
- | Pentru a specifica ce orientare a fețelor să fie ignorată se folosește comanda **[[https://www.opengl.org/wiki/GLAPI/glCullFace|glCullFace]]** | + | {{ :ppbg:laboratoare:overview.png?600 |}} |
- | <code cpp> | + | |
- | // GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK are accepted. | + | |
- | // The initial value is GL_BACK. | + | |
- | glCullFace(GL_BACK); | + | |
- | </code> | + | |
- | ===== Cerinte laborator ===== | + | - Din //spațiul obiectului//, prin **transformarea de modelare**, ce conține un lanț de transformări de translație, de modificare a scării și de rotație, obiectele ajung în //spațiul lumii//; |
+ | - Din //spațiul lumii//, prin **transformarea de vizualizare**, conform proprietăților observatorului, obiectele ajung în //spațiul de vizualizare//; | ||
+ | - Peste //spațiul de vizualizare// se aplică volumul de proiecție perspectivă, reprezentat de un trunchi de piramidă, centrat în originea axelor de coordonate, cu baza orientată de-a lungul axei Z, în sens negativ. Din acest spațiu, prin **transformarea de proiecție** perspectivă, obiectele ajung în //spațiul de decupare//, reprezentat de volumul unui cub, cu latura de dimensiune 2, centrat în originea axelor de coordonate. | ||
+ | - După procesul de decupare și de eliminare a triunghiurilor obturate, în situația în care dorim acest al doilea proces, prin transformarea coordonatelor din volumul de decupare în poarta de afișare, triunghiurile se transmit la procesul de rasterizare, prin care se desenează suprafețele triunghiurilor în grila de pixeli :) . | ||
- | <note important> | + | <note tip> |
- | Pentru toate cerintele in care se precizeaza ca animatiile trebuie sa fie continue, utilizati valoarea ''deltaTime''. | + | Felicitări :) ! Daca ați înțeles acest proces, ați înțeles standardul care stă la baza graficii pe calculator în timp real. Toate procesoarele grafice implementează acest standard, hardware sau software în driver-ul companion procesorului. Acest fapt este valabil și pentru arhitecturile ce implementează hardware procesul de ray-tracing. Aplicațiile grafice ce utilizează această arhitectură folosesc o abordare hibridă ce combină banda grafică de rasterizare cu banda grafică de ray-tracing. |
</note> | </note> | ||
- | - 0.05p - Completați metoda ''CreateMesh()'' astfel încât să încărcați geometria in memoria RAM a procesorului grafic. | + | ===== Cerințe laborator ===== |
- | * Creați un VAO | + | |
- | * Creați un VBO și adăugați date în el | + | |
- | * Creați un IBO și adăugați date în el | + | |
- | * Dupa completarea corecta a metodei, rezultatul vizual ar trebui sa fie urmatorul: {{ :ppbg:laboratoare:result1.png?600 |}} | + | |
- | - 0.05p - La apasarea unei taste, alegeti o culoarea aleatoare pentru curatarea grilei de pixeli | + | |
- | * Utilizati in metoda ''Update()'' directiva de specificare a culorii curatare a informatiei de pe ecran. | + | |
- | * La apasarea tastei R, alegeti o culoare aleatoare | + | |
- | - 0.05p - La apasarea unei taste, | + | |
- | * Utilizati in metoda ''DrawObjects()'' directivere de activare si dezactivare a optimizarii Face culling | + | |
- | * Utilizati in metoda ''DrawObjects()'' directiva de specificare a tipului de fata pentru care triunghiurile se elimina din procesul de rasterizare | + | |
- | * La apasarea tastei F, schimbati intre eliminarea triunghiurilor pentru care se afiseaza fateta fata sau spate | + | |
- | - 0.05p - Pentru cele 3 cuburi din scena aplicati urmatoarele animatii: | + | |
- | * Unul dintre cuburi sa se deplaseze continuu sus-jos intre limitele pe axa y: 0 si 3 | + | |
- | * Un alt cub sa se roteasca continuu fata de una dintre cele 3 axe principale | + | |
- | * Un alt cub sa pulseze continuu intre scarile 0.5 si 2 | + | |
- | - 0.05p - Desenati un alt cub pe care sa il deplasati prin spatiu la apăsarea tastelor W, A, S, D, E, Q (pozitiv și negativ pe toate cele 3 axe). Nu permiteti deplasarea cubului in situatia in care pozitia observatorului se deplaseaza. | + | |
- | - 0.05p - Desenati obiectele de 4 ori in 4 ferestre de afisare diferite, conform imaginii de mai jos. Pastrati proportiile precizate in imagine. | + | |
- | * Utilizati in metoda ''DrawObjects()'' directiva de specificare a pozitiei si dimensiunii portii de afisare. | + | |
- | * In metoda ''Update()'' desenati de 4 ori obiectele. Trimiteti la desenare cele 4 camere setate cu pozitii si directii de vizualizare predefinite. | + | |
- | {{ :ppbg:laboratoare:result2.png?700 |}} | + | |
+ | - 0.1p - Completați fișierul ''transform3D.h'' cu transformările de modelare descrise mai sus. | ||
+ | - 0.05p - Completați fișierul ''transform3D.h'' cu transformarea de proiecție perspectivă descrisă mai sus. | ||
+ | - 0.05p - Aplicați procesul de împărțire perspectivă a coordonatei obținute în urma aplicării tuturor transformărilor din lanț. | ||
+ | - 0.05p - Completați fișierul ''transform3D.h'' cu transformarea de vizualizare descrisă mai sus. | ||
+ | * După acest pas, cât țineți apăsat butonul dreapta de la mouse, puteți modifica direcția de vizualizare a observatorului prin deplasarea poziției mouse-ului și vă puteți deplasa prin scena 3D cu tastele **W**, **A**, **S**, **D**, **E** și **Q**. Până în acest punct, rezultatul pe care ar trebui să îl obțineti este următorul: {{ :ppbg:laboratoare:cube1.png?600 |}} | ||
+ | - 0.05p - Completați metoda ''DetermineTriangleFace()'' pentru a identifica corect care din cele două fațete ale triunghiului, față sau spate, este afișat. | ||
+ | * După acest pas, puteți utiliza tasta **F** pentru a schimba între desenarea triunghiurilor pentru care se afișeaza fațetele față sau fațetele spate. Dacă alegeți opțiunea să fie afișate doar fațetele spate, ar trebui să puteți vedea interiorul cubului :) : {{ :ppbg:laboratoare:cube2.png?600 |}} | ||
+ | Bonus: Construiți și desenați un tetraedru care să aibă indicii fiecărui triunghiuri orientați în sens trigonometric, când triunghiul este privit din exterior. Asociați culori diferite fiecărui vârf pentru a vedea mai ușor detaliile tetraedrului. | ||