Table of Contents

Laboratorul 02

Pentru rezolvarea cerințelor din acest laborator, aveți nevoie de codul utilizat în rezolvarea cerințelor din cadrul laboratorului 1. În laboratoarele 2 și 3, vom reutiliza acest cod. În situatia în care nu ați rezolvat laboratorul 1, va trebui sa îl realizați mai întâi pe el și ulterior să reveniți la cerințele celui curent.

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 studenții care au realizat laboratorul pe o versiune a framework-ului datată înainte de 19.10.2023

Va trebui să descărcați din nou framwork-ul de laborator, deoarece a suferit câteva modificări importante și să copiați codul celor 4 metode: ComputeTriangleArea(), CheckPointInsideTriangle(), ComputePixelColor() și ComputePixelDepth(), din versiunea de framework utilizată pentru rezolvarea cerințelor din laboratorul anterior, în versiunea curentă.

Va fi necesar sa utilizați din nou utilitarul CMake. Pentru a vă reaminti procesul de realizare a setup-ului, puteți să reconsultați pagina dedicată acestui lucru.

Framework-ul de laborator are o proiectare modulară, unde fiecare clasă specifică unui laborator poate fi executată individual. Pentru a specifica ce laborator să fie executat, trebuie să modificați fișierul main.cpp, la linia 49 și să initializați clasa specifică laboratorului dorit. De exemplu, pentru inițializarea laboratorului 2, codul arată în felul următor:

World *world = new lab::Lab2();

Transformări în spații 2D

În laboratorul anterior, am văzut cum se poate desena un triunghi pe ecran, dacă avem la dispoziție informația necesară pentru fiecare vârf: coordonatele în spațiul 2D, valoarea de adâncime și o culoare. Cu toate acestea, un astfel de sistem nu este suficient de flexibil, deoarece, în cadrul unei aplicații grafice în timp real, dorim să realizăm animații cu un model 3D, reprezentat de o rețea de triunghiuri. Coordonatele inițiale ale triunghiurilor trebuie modificate pentru a realiza o animație. Realizarea aceleeași modificări, sau transformări, pentru toate vârfurile unei rețele de triunghiuri, produce, din punct de vedere vizual, efectul de aplicare a modificării pentru întreg ansamblul, respectiv pentru tot modelul 3D.

Din acest motiv, de-a lungul timpului, au fost standardizate mai multe tipuri de modificări asupra informației unui vârf, în particular asupra coordonatelor lui, ce pot fi utilizate în vederea realizării animațiilor.

Transformarea de translație (deplasare)

Transformarea cea mai de bază este cea utilizată pentru modificarea coordonatelor unui vârf. Acest proces se realizeaza prin deplasarea coordonatelor unui vârf cu un anumit vector de deplasare și poartă numele de translație sau translatare.

În imaginea de mai jos, în partea dreaptă, se poate observa rezultatul translației pătratului din partea stângă cu vectorul de translație $\vec{t}=\begin{bmatrix}
2 & 1
\end{bmatrix}$ .

Formula pentru realizarea procesului de translație este:

$$ x'=x+t_x\\ y'=y+t_y $$

Transformarea de modificare a scării

O altă transformare, ce poate fi utilizată în realizarea animațiilor, este modificarea scării unui obiect, respectiv mărirea sau micșorarea obiectului.

În imaginea de mai jos, în partea dreaptă, se poate observa rezultatul modificării scării pentru pătratul din partea stângă, cu vectorul de scară $\vec{s}=\begin{bmatrix}
2 & 2
\end{bmatrix}$ .

Formula pentru realizarea procesului de modificare a scării este:

$$ x'=s_x \cdot x\\ y'=s_y \cdot y $$

Transformarea de rotație

Ultima transformare des intalnită în grafica pe calculator este rotația unui model. Pentru realizarea acestui proces, pentru spații 2D, vom utiliza un aparat matematic care se aplică în felul următor: rotația unui triunghi se realizeaza prin rotația tuturor vârfurilor, în sens trigonometric, față de centrul axelor de coordonate.

Un exemplu pentru această transformare se poate vedea în imaginea de mai jos, unde pătratul este rotit cu 45 de grade, în sens trigonometric, față de punctul v0, care este în originea axelor de coordonate.

Formula pentru această transformare este:

$$ x'=x \cdot cos(\theta)-y \cdot sin(\theta)\\ y'=x \cdot sin(\theta)+y \cdot cos(\theta) $$

Această formula reiese din următorul fapt. Conform imaginii de mai jos, avem punctul V, pe care dorim sa îl rotim cu unghiul B, în sens trigonometric, față de originea axelor de coordonate pentru a obține punctul V'.

Componentele x și y ale coordonatei lui V se pot scrie și sub forma:

$$ x=r \cdot cos(\alpha)\\ y=r \cdot sin(\alpha) $$

De asemenea, componentele x' și y' ale coordonatei lui V' se pot scrie sub forma:

$$ x'=r \cdot cos(\alpha + \beta)\\ y'=r \cdot sin(\alpha + \beta)\\ $$

Continuăm prin înlocuirea sumei din funcțiile trigonometrice cu:

$$ x'=r \cdot cos(\alpha) \cdot cos(\beta)-r \cdot sin(\alpha) \cdot sin(\beta)\\ y'=r \cdot cos(\alpha) \cdot sin(\beta)+ r\cdot sin(\alpha) \cdot cos(\beta)\\ $$

În final, înlocuim cu x și y în rezultatul anterior, conform primei formule din această notă și obținem:

$$ x'=x \cdot cos(\beta)-y \cdot sin(\beta)\\ y'=x \cdot sin(\beta)+y \cdot cos(\beta) $$

Compunerea transformărilor

Transformarile de bază, prezentate mai sus, nu sunt suficient de flexibile pentru a obține orice tip de animații prin utilizarea individuală a unui anume tip. Un exemplu este rotația față de propriul centru al unui pătrat de latură 1 ce a fost definit cu colțul stânga-jos în originea axelor de coordonate. O aplicare directă a transformării de rotație rezultă în rotația pătratului față de colțul stânga-jos. Din acest motiv, este necesar sa obținem rezultatul dorit prin compunerea mai multor transformări. Lanțul de transformări este vizibil în imaginea de mai jos.

Transformarea compusă este creată după cum urmează:

  1. Primul pas este translația pătratului, astfel încat centrul lui să se afle în originea axelor de coordonate
  2. În acest moment, putem roti vârfurile pătratului față de centrul lui
  3. După rotație, pătratul rămâne cu centrul în originea axelor de coordonate, astfel că aplicăm transformarea inversă de la punctul 1 pentru a readuce pătratul cu centrul în poziția lui originală

Același proces se poate aplica și pentru transformarea de modificare a scării, pentru a face modificarea de scară din centrul pătratului :) .

Forma matriceală a transformărilor

Formula finală obținută în urma compunerii unui lanț de transformări poate fi complexă. De exemplu, formula finală pentru compunerea de mai sus, în care rotim un pătrat față de propriul centru, este:

$$ x'=(x-0.5) \cdot cos(45)-(y-0.5) \cdot sin(45) + 0.5\\ y'=(x-0.5) \cdot sin(45)+(y-0.5) \cdot cos(45) + 0.5 $$

Pentru a simplifica și optimiza procesul de calcul, se folosește forma matriceală a transformărilor. Pentru acest lucru, vectoriul pentru care se aplică transformarea se scrie, de asemenea, în formă matriceală. În această situație, avem 2 posibilități:

Ambele reprezentări sunt practice, dar atrag după sine forme matriceale diferite pentru transformări. În continuare, vom lua pe rând ambele forme pentru a vedea reprezentarea lor.

Vector linie

Pentru fiecare din cele 3 transformări de bază de mai sus, în situația în care utilizăm un vector linie, forma matriceală este:

$$ \begin{bmatrix} {x+t_x} & {y+t_y} & 1 \end{bmatrix} = \begin{bmatrix} x & y & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ t_x & t_y & 1 \end{bmatrix} $$

$$ \begin{bmatrix} {s_x \cdot x} & {s_y \cdot y} & 1 \end{bmatrix} = \begin{bmatrix} x & y & 1 \end{bmatrix} \begin{bmatrix} s_x & 0 & 0\\ 0 & s_y & 0\\ 0 & 0 & 1 \end{bmatrix} $$

$$ \begin{bmatrix} {x \cdot cos(\theta)-y \cdot sin(\theta)} & {x \cdot sin(\theta)+y \cdot cos(\theta)} & 1 \end{bmatrix} = \begin{bmatrix} x & y & 1 \end{bmatrix} \begin{bmatrix} cos(\theta) & sin(\theta) & 0\\ -sin(\theta) & cos(\theta) & 0\\ 0 & 0 & 1 \end{bmatrix} $$

Se poate observa că doar transformarea de translație necesită o matrice de dimensiune 3×3, iar pentru celelalte 2 transformări sunt necesare matrici de dimensiune 2×2. Sunt 2 motive principale pentru care se utilizează matrici de 3×3 pentru toate transformările:

  • Pentru a realiza transformări compuse, operația de înmulțire între matrici trebuie să fie posibilă, astfel că se utilizează aceeași dimensiune pentru toate tipurile de matrici de transformare. Mai exact, se alege dimensiunea maximă necesară dintre dimensiunile matricelor celor 3 transformări de bază, respectiv 3×3, necesară pentru translație. Astfel, se completează celelalte 2 matrici cu încă o linie și o coloană în a căror celule se scrie 0, cu excepția celulei (3,3), în care se scrie 1. În plus, la reprezentarea matriceală a vectorului se mai adaugă o celulă cu valoarea 1.
  • În procesul de transformare perspectivă, ce va fi utilizat în laboratorul următor, se utilizează coordonate într-un spatiu omogen, ceea ce necesită o informație scalară suplimentară. Vom discuta mai multe detalii în legatură cu acest aspect în laboratorul următor :) .

Compunerea transformărilor se poate realiza prin înmulțirea matricilor corespunzătoare fiecărei transformări. Astfel, pentru exemplul de mai sus, în care se rotește un pătrat în jurul propriului centru, lanțul de transformări este următorul:

$$ \begin{bmatrix} x' & y' & 1 \end{bmatrix} = \begin{bmatrix} x & y & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ -0.5 & -0.5 & 1 \end{bmatrix} \begin{bmatrix} cos(45) & sin(45) & 0\\ -sin(45) & cos(45) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0.5 & 0.5 & 1 \end{bmatrix} $$

Vector coloană

Pentru a obține matricile de transformare, în situația în care utilizăm un vector coloană, putem pleca de la forma anterioară, în care era utilizat un vector linie. Astfel, dacă vrem sa obținem un rezultat transpus celui anterior pentru a aduce forma linie a vectorului la forma coloană, avem:

$$ \begin{bmatrix} x+t_x & y+t_y & 1 \end{bmatrix} ^t =( \begin{bmatrix} x& y& 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ t_x & t_y & 1 \end{bmatrix})^t $$

Deoarece, conform următoarei proprietăți a matricilor:

$$ (M_{0} \cdot M_{1} \cdot ... \cdot M_{n-1} \cdot M_{n})^{t} = M_{n}^{t} \cdot M_{n-1}^{t} \cdot ... \cdot M_{1}^{t}\cdot M_{0}^{t} $$

Rezultă că forma matricei pentru transformarea de translație este:

$$ \begin{bmatrix} x+t_x \\ y + t_y \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ t_x & t_y & 1 \end{bmatrix}^t \begin{bmatrix} x& y& 1 \end{bmatrix} ^t= \begin{bmatrix} 1 & 0 & t_x\\ 0 & 1 & t_y\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$

Analog pentru matricile celorlalte 2 transformări:

$$ \begin{bmatrix} s_x \cdot x \\ s_y \cdot y \\ 1 \end{bmatrix} = \begin{bmatrix} s_x & 0 & 0\\ 0 & s_y & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$

$$ \begin{bmatrix} x \cdot cos(\theta)-y \cdot sin(\theta) \\ x \cdot sin(\theta)+y \cdot cos(\theta) \\ 1 \end{bmatrix} = \begin{bmatrix} cos(\theta) & -sin(\theta) & 0\\ sin(\theta) & cos(\theta) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix} $$

Pentru transformarea compusă dată exemplu mai devreme, vom avea:

$$ \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0.5\\ 0 & 1 & 0.5\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} cos(45) & -sin(45) & 0\\ sin(45) & cos(45) & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & -0.5\\ 0 & 1 & -0.5\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

Observăm că, in situația în care utilizăm un vector coloană, ordinea de aplicare a transformărilor este de la ultima matrice la prima. În plus, vectorul pentru care se aplică lanțul de transformări este întotdeauna la final.

În cadrul laboratoarelor de la această materie, până la finalul semestrului, vom utiliza reprezentarea unui vector în formă coloană. Ambele reprezentări ale vectorilor sunt practice și compatibile cu procesorul grafic.

Biblioteca glm

În cadrul laboratoarelor de la această materie, vom utiliza biblioteca glm pentru gestionarea matricilor și a operațiilor cu matrici.

La bază, biblioteca glm folosește matrici column-major, mai precis, matrici care au informația transpusă în memorie. Astfel, în constructorul tipului de date glm::mat3, trebuie transmisă forma transpusă a matricei pe care dorim sa o reținem.

De exemplu, matricea pentru transformarea de translație, utilizată pentru inmulțirea cu un vector coloană, trebuie inițializată astfel:

glm::mat3 Translate(float tx, float ty)
{
	return glm::mat3( 
        	 1,  0, 0,     // coloana 1 in memorie 
		 0,  1, 0,     // coloana 2 in memorie 
		tx, ty, 1);    // coloana 3 in memorie 
 
}

De asemenea, avem opțiunea să inițializăm matricea în formă netranspusă și să aplicăm glm::transpose ulterior:

glm::mat3 Translate(float tx, float ty)
{
	return glm::transpose(
		glm::mat3( 1, 0, tx, 
			   0, 1, ty, 
			   0, 0, 1)
	); 
}

Transformarea fereastră-poartă

În situatia în care dorim ca aplicația grafică proiectată de noi să nu depindă de rezoluția ferestrei și astfel să poată funcționa la rezoluții diferite, trebuie să nu mai definim coordonatele vârfurilor în grila de pixeli a ecranului. Pentru acest proces, vom defini un spațiu special, finit, denumit spațiu logic, în care vom declara coordonatele vârfurilor. Acest spațiu este o fereastră peste spațiul 2D și din acest motiv, transformarea are numele de transformare fereastră-poartă, respectiv transformare din fereastra peste spațiul 2D în poarta de vizualizare, reprezentată de ecran.

În laborator, a fost definit spațiul logic, după cum urmează:

struct LogicSpace
{
    float x;
    float y;
    float width;
    float height;
};

Se poate observa ca acest spațiu este definit de coordonatele calțului stânga-jos pentru fereastra și de o lățime și o înălțime a acesteia.

Analog, realizăm o structură de date pentru poarta de vizualizare, cunoscută în limba engleza sub numele de viewport:

struct ViewportSpace
{
    int x;
    int y;
    int width;
    int height;
};

De exemplu, pentru a transforma obiectele definite în spatiul logic ce are colțul stânga-jos la coordonatele (1, 1) și rezoluția de 3×2, astfel încât să fie puse pe întreg ecranul, de rezoluție 1280×720, se pot defini spațiile în felul următor.

ViewportSpace viewport_space = { 0, 0, 1280, 720 };
LogicSpace logic_space = { 1, 1, 3, 2 };

Transformarea se poate urmări vizual în imaginea de mai jos.

Lanțul de transformări necesar acestui proces este:

$$ \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & V_{x}\\ 0 & 1 & V_{y}\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} V_{w}/F_{w} & 0 & 0\\ 0 & V_{h}/F_{h} & 0\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & -F_x\\ 0 & 1 & -F_y\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

Componentele Fx, Fy, Fw și Fh reprezintă coordonatele (x, y) pentru colțul stânga-jos al ferestrei spațiului 2D, în care sunt definite coordonatele vâfurilor din aplicația grafică, respectiv lățimea și înălțimea ferestrei. Componentele Vx, Vy, Vw și Vh reprezintă coordonatele (x, y) pentru colțul stânga-jos al porții de vizualizare, respectiv lățimea și înălțimea ei.

Cerințe laborator

  1. 0.1p - Completați fișierul transform2D.h cu cele 4 transformări descrise mai sus.
  2. 0.05p - Aplicați transformările necesare pentru a modifica pătratele de pe linia din partea de sus, conform rezultatului din imaginea de mai jos. Consultați comentariile din cod pentru a primi mai multe detalii despre fiecare transformare.
  3. 0.1p - Aplicați transformările necesare pentru a modifica pătratele de pe linia din partea de jos, conform rezultatului din imaginea de mai jos. Consultați comentariile din cod pentru a primi mai multe detalii despre fiecare transformare.
    Pâna în acest punct, rezultatul pe care ar trebui să îl obțineti este următorul:
  4. 0.05p - Împărțiți ecranul în 4 cadrane și desenați formele geometrice de mai sus de 4 ori, câte o dată în fiecare cadran. Modificați spațiul porții de vizualizare, coordonatele colțului din stânga jos, respectiv, lățimea și înălțimea.

Rezultatul final ar trebui să fie urmatorul:

Utilizați tastele sus, jos, stânga și dreapta pentru a controla interactiv pozitia colțului din stânga-jos a spațiului logic.

Bonus: Realizați razele unei stele prin desenarea multiplă a unui triunghi rotit la unghiuri diferite. Este necesar să construiți o nouă geometrie pentru triunghiul ce reprezintă o rază. Un exemplu vizual ar putea fi:

  • Aveți în vedere că, pentru simplitate, triunghiul ce reprezintă raza, poate fi creat direct cu proprietatea de a fi isoscel :) .
  • Razele se pot suprapune între ele.