This is an old revision of the document!
Reamintire!!! Puteți prezenta rezolvările cerințelor de până la 2 laboratoare, în fiecare săptămână. De exemplu, puteți prezenta laboratorul curent și pe cel din săptămâna anterioară, în totalitate sau parțial, inclusiv punctajul pentru cerința bonus :) .
lab_list.h
, linia #include “lab/lab3/lab3.h”
.Acest proces va fi necesar pentru fiecare din următoarele laboratoare, cu utilizarea numărului laboratorului pe care doriți să îl specificați :) .
Î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 transformări de bază: translație, modificare de scară și rotație.
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). 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} $$
Observăm că se proiecteaza paralel coordonatele vârfurilor pe fața z=-1 a cubului. Practic, se păstreaza 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.
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 laboratorul 1 :) .
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ă utilizam în procesul de rasterizare doar triunghiurile rezultate în urma decupării. Acest mecanism poate genera de la 0 sau pana la 3 triunghiuri. Scenariile în care sunt rezultate 1, 2 și 3 triunghiuri pot fi observate în imaginea de mai jos.
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.
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.
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. 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.
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-alungul 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.
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:
Formula de calcul a proiecției perspective este:
$$ 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} $$
Această formula 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ă realizam acest proces în 2 pași:
$$ 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'} $$
Astfel, conform teoremei lui Thales, rezulta că:
$$ \frac{y'}{near} = \frac{y}{-z}\\ $$
$$ y'=\frac{near\cdot y}{-z}\\ $$
Considerăm că valoarea componentei z este negativă, deoarece o tratăm pe post de distanță și orientarea bazei trunchiului de piramidă este din constructie de-alungul axei Z, în sens negativ.
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:
$$ y'=\frac{2\cdot near\cdot y}{-z \cdot (top-bottom)}\\ $$
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:
$$ bottom= -top\\ y'=\frac{2\cdot near\cdot y}{-z \cdot 2 \cdot top}=\frac{near\cdot y}{-z \cdot top}\\ $$
Se poate observa că:
$$ \frac{top}{near}=tan(\frac{fovy}{2}) $$
Astfel, avem pentru y':
$$ y' = \frac{y}{ -z \cdot tan(\frac{fovy}{2})}\\ $$
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}=\frac{fovx}{fovy}\\ fovx=aspect \cdot fovy\\ x' = \frac{x}{ -z \cdot tan(\frac{fovy}{2}\cdot aspect)}\\ $$
Daca formula pentru (x, y) reiese din proiectia coordonatelor varfului pe planul din apropiere al trunchiului de piramida, formula pentru componenta z este artificiala si reiese exclusiv din dorinta de a creea o normalizare neliniara intre distanta planului din apropiere si distanta planului indepartat. Se doreste ca pentru obiectele din apropiere de planul apropiat, z=-near, sa fie alocat un interval mult mai mare in spatiul de decupare, decat pentru obiectele apropiate de planul z=-far. Din acest motiv, formula standard aleasa initial este:
$$ z'=\frac{c1}{-z}+c2 $$
Parametrii c1 si c2 sunt cei din formula originala. Problema acestei formule este ca nu se imparte la valoarea z, ceea ce inseamna ca pentru z' nu s-ar aplica impartirea perspectiva. Pentru a face uniform procesul de impartire perspectiva, se modifica formula si obtinem versiunea finala:
$$ z'=\frac{c2\cdot z-c1}{-z} $$
Prin transformarea de proiectie perspectiva, descrisa mai sus, putem modifica coordonatele varfurilor retelei de triunghiuri pentru a le aseza in spatiul de decupare si de acolo putem desena triunghiurile pe ecran. Cu toate acestea, coordonatele obiectelor noastre nu se regasesc intotdeauna in spatiul de vizualizare, reprezentat, cum s-a mentionat mai sus, de volumul unui trunchi de piramida cu varful in originea axelor de coordonate si cu baza orientata de-alungul axei Z, in sens negativ. In situatia in care dorim sa desenam scena dintr-un alt punct de vedere, trebuie sa definim conceptul de observator. Proprietatile care definesc un observator sunt:
Pe baza acestor informatii, putem sa cream o transformare care modifica obiectele din scena, din spatiul in care se afla, intr-un spatiu in care observatorul are pozitia in originea axelor de coordonate si “priveste” scena de-alungul axei Z, in sens negativ. Peste acest spatiu se poate suprapune volumul de vizualizare, utilizat pentru transformarea proiectiei perspective.
Transformarea de vizualizare arata in felul urmator:
$$ \begin{bmatrix} {x}'\\ {y}'\\ {z}'\\ 1 \end{bmatrix} = \begin{bmatrix} R_x & R_y & R_z & 0 \\ U_x & U_y & U_z & 0 \\ F_x & F_y & F_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 0 & 0 & 0 & -P_x \\ 0 & 0 & 0 & -P_y \\ 0 & 0 & 0 & -P_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix} $$
Se poate observa ca cea de-a doua transformare este o translatie inversa a coordonatelor pozitiei observatorului in spatiul 3D, similara cu cea discutata in laboratorul anterior pentru spatiul 2D. Prima transformare este o rotatie. Pentru constructia acestei matrici s-a folosit procedeul Gram–Schmidt.
Coordonatele varfurilor din reteaua de triunghiuri ce descrie suprafata unui obiect se declara initial intr-un spatiul convenabil crearii acelui obiect. De exemplu, din .
Pentru a modifica obiectul si a-l aseza sub forma dorita in punctul final din scena, se folosesc transformarile de translatie, de modificare a scarii si de rotatie, discutate in laboratorul anterior, adaptate pentru un spatiu 3D.
Matricile de translatie si de modificare a scarii sunt similare cu cele dintr-un spatiu 2D. Ele se modifica doar prin adaugarea unei noi linii si a unei noi coloane pentru a adapta cea de-a treia dimensiune ce apare intr-un spatiu 3D.
$$ \begin{bmatrix} {x}'\\ {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} \begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix} $$
$$ \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} $$
Mecanismul de rotatie, studiat in laboratorul trecut, roteste o coordonata a unui punct intr-un spatiu 2D cu un unghi a, in sens trigonometric fata de originea axelor de coordonate. Acest mecanism NU poate fi adaptat direct la un spatiu 3D.
Astfel, transformarea de rotatie dintr-un spatiu 2D este utilizata intr-un spatiu 3D pentru a roti o coordonata intr-un singur plan din spatiul 3D. Mai exact intr-un plan paralel cu unul din cele 3 planuri ce trec prin originea axelor de coordonate. Exista cate o matrice de transformare diferita pentru fiecare din cele 3 planuri. In situatia in care dorim sa creem o transformare de rotatie intr-un plan oarecare, trebuie sa o obtinem prin compunerea din mai multe transformari de baza.
Astfel, avem transformarea de rotatie intr-un plan paralel cu planul XOY: Putem sa consideram ca aceasta transformare este fata de axa OZ.
$$ \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} $$
Transformarea de rotatie intr-un plan paralel cu planul XOZ, sau fata 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 rotatie intr-un plan paralel cu planul YOZ, sau fata 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} $$
O optimizare suplimentara, pe care o putem realiza in spatiul de decupare, pe langa cea de decupare propriu-zisa, este sa excludem din procesul de rasterizare triunghiurile care sunt complet obturate de alta geometrie. Pentru a nu face o procesare avansata la nivel de retea, putem sa folosim urmatorul artificiu.
In situtia in care desenam un model 3D etans, care nu contine nicio gaura in suprafata lui, avem proprietatea ca din orice punct am privi geometria, nu ii vom putea vedea niciodata interiorul. Astfel, avem urmatoarea forma de definire a unui triunghi: pe baza descrierii cu indici din vertices
, utilizata in laboratoarele anterioare, sensul in care sunt afisate varfurile, pe baza ordinii din indici, ne determina noua faptul ca privim fateta fata sau fateta spate a triunghiului.
Mai exact, daca avem descrisa urmatoarea geometrie:
vector<VertexFormat> vertices { 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, }
In momentul in care geometria se afla in spatiul de decupare, dupa aplicarea tuturor transformarilor, se observa sensul dat de coordonatele varfurilor, in ordinea in care au fost definiti indicii. In situatia in care sensul pe care il fac coordonatele, conform imaginii de mai jos, este trigonometric, consideram ca ne uitam la fateta fata a triunghiului.
Prin acest proces, in situatia in care desenam suprafata unui model 3D etans, putem exclude din procesul de rasterizare toate triunghiurile pentru care afisam fatetele spate pentru ca avem garantia ca ele sunt obturate de restul geometriei modelului.
transform3D.h
cu cele transformările de modelare descrise mai sus.transform3D.h
cu transformarea de proiectie perspectiva descrisa mai sus.transform3D.h
cu transformarea de vizualizare descrisa mai sus. Dupa acest pas, cat tineti apasat butonul dreapta de la mouse, puteti modifica directia de vizualizare a observatorului prin deplasarea pozitiei mouse-ului si va puteti deplasa prin scena 3D cu tastele W, A, S, D, E si Q. Pâna în acest punct, rezultatul pe care ar trebui să îl obțineti este următorul: ComputeTriangleFace()
pentru a identifica corect care din cele doua tipuri de fateta a triunghiului, fata sau spate, este desenat. Dupa acest pas, puteti utiliza tasta F pentru a schimba intre desenarea triunghiurilor pentru care se afiseaza doar fatetele fata sau doar fatetele spate. Daca alegeti optiunea sa fie afisate doar fatetele spate, ar trebui sa puteti vedea interiorul cubului :) : Bonus: Construiti si desenati un tetraedru care sa aiba indicii fiecarui triunghiuri orientati in sens trigonometric, cand triunghiul este privit din exterior. Asociati culori diferite fiecarui varf pentru a putea identifica diferenta.