Laboratorul 01

Bun venit la prima activitate practică a acestei materii. În fiecare săptămână, până la finalul semestrului, vom folosi un framework adaptat special pentru aceste activități practice. Framwork-ul este scris în limbajul C++ și folosește API-ul grafic OpenGL. Primul pas al acestui laborator este să realizați setup-ul framework-ului .

Rasterizatorul

Banda grafică, implementată hardware în procesorul grafic și software în driver-ul companion tipului de procesor, poate fi utilizată în desenarea pe ecran a unor suprafețe complexe, în situația în care acestea sunt trimise la intrare în bandă sub forma unei rețele de triunghiuri. Un astfel de exemplu, poate fi văzut în imaginea de mai jos, unde trăsăturile feței sunt aproximate din unirea mai multor triunghiuri.

Noi vom studia, în primele 3 laboratoare de la această materie, procesul de desenare pe ecran a unei topologii descrise sub forma unei rețele de triunghiuri. Acest proces cuprinde mai mulți pași. Secvența de pași utilizați în desenarea pe ecran a unei rețele de triunghiuri poartă numele de bandă grafică sau, cum este cunoscută în limba engleză, rendering pipeline.

În acest laborator, ne vom concentra doar pe un singur pas, de bază, din tot procesul de desenare, mai exact, pasul de desenare în grila de pixeli a ecranului a unor triunghiuri în spațiul 2D. Procesul acesta, în banda grafică, poartă numele de rasterizare.

În imaginea de mai jos se poate observa o grilă de pixeli peste care este suprapus un triunghi, marcat in culoarea negru. În culoare albastră a fost marcat centrul fiecărui pixel. Stabilirea pixelilor ce se regasesc în interiorul triunghiului, marcați în culoarea verde, se realizează după regula: dacă centrul unui pixel se află în interiorul triunghiului sau pe latura lui, se consideră ca tot pixelul se află în interiorul triunghiului.

Interpolarea informației

Culoarea unui pixel ce se stabilește că se află în interiorul unui triunghi este dată de culoarea triunghiului. În banda grafică, pentru a oferi control utilizatorului, culoarea se precizează la nivel de vârf. Astfel, fiecare din cele 3 vârfuri ale unui triunghi conține informație de culoare.

În laboratorul 7 vom studia o abordare cu care se poate preciza informația la nivel de pixel.

În situația în care toate cele 3 vârfuri ale unui triunghi au aceeași culoare, toți pixelii ce se regăsesc în interiorul triunghiului au aceeași culoare. În situatia în care varfurile au culori diferite, culoarea unui pixel se calculează prin interpolare, pe baza distanței față de fiecare vârf. Un exemplu vizual se poate găsi în imaginea de mai jos, unde vârful de sus are culoarea albastră, vârful din stânga jos, culoarea roșie și vârful din dreapta jos culoarea verde. Se poate vedea că, un pixel cu cât este mai apropiat de un anumit vârf, cu atât are o nuanță de culoare mai apropiată de cea a vârfului respectiv.

Pe lângă informația de culoare, fiecare vârf poate avea asociate și alte date. Această abordare poate fi folosită pentru orice tip de informație definită la nivel de vârfuri. Puțin mai jos vom vedea o abordare pentru a obține și noi acest rezultat.

Testul de adâncime

Vom propune, suplimentar, ca pe lângă coordonate 2D (x, y), fiecare vârf al unui triunghi să aibă și o coordonată de adâncime (z). Această coordonată nu este folosită pentru a stabili dacă un pixel se regăsește în interiorul unui triunghi, dar este folosită pentru rezolvarea problemei în care suprafețele a două triunghiuri se suprapun între ele.

Pentru a rezolva această situație, trebuie să avem o ordine de afișare. Presupunem că o valoare de adâncime (z) mai mică determină afișarea peste o valoare de adâncime mai mică. Banda grafică hotărăște dacă 2 triunghiuri se pot rasteriza concomitent. Pentru simplitate vom considera că se desenează secvențial exact 2 triunghiuri ce se suprapun.

  1. Primul triunghi este rasterizat în totalitate. Pentru fiecare pixel ce se află în interiorul lui, se calculează coordonata de adâncime, prin interpolare între, similar cu abordarea de mai sus, utilizată pentru calcularea culorii. Această coordonată se pastrează într-o grilă separată față de cea utilizata pentru a păstra pixelii.
  2. În momentul în care se rasterizează triunghiul 2, și se stabilește un pixel ce se află în interiorul acestui triunghi, se calculează coordonata de adâncime pentru pixel și se compară cu valoarea ce se află la aceeași locație în grila de valori de adâncime.
    1. În situația în care valoarea de adâncime a pixelului desenat acum este mai mică decât cea din grilă, se salvează în grila de pixeli și de adâncime, valorile pixelului curent.
    2. În situația în care valoarea de adâncime a pixelului desenat acum este mai mare decât cea din grilă, se consideră că pixelul ce trebuie să fie vizibil este deja în grilă și pixelul ce se procesează trebuie să fie în spatele lui, astfel că se renunță la pixelul curent.

Laborator

Descrierea rețelei de triunghiuri

În framework-ul de laborator, rețeaua de triunghiuri se va descrie prin două structuri de date. Prima este mulțimea ordonată de vârfuri:

vector<VertexFormat> vertices
{
    VertexFormat(glm::vec3(0, 50,  0.2f), glm::vec3(1, 0, 0)),
    VertexFormat(glm::vec3(70, 99,  0.2f), glm::vec3(0, 1, 0)),
    VertexFormat(glm::vec3(99, 0,  0.2f), glm::vec3(0, 0, 1)),
    VertexFormat(glm::vec3(10, 10,  0.2f), glm::vec3(0, 1, 1))
}

Structura de date VertexFormat conține mai multe informații pentru fiecare vârf. În constructorul ei, primii 2 parametri transmiși sunt poziția (x, y, z), cu (x, y) în grila de pixeli și coordonata (z) cu valori între 0 si 1, respectiv al doilea parametru este culoarea în modelul de culoare rgb, unde fiecare canal are valorile între 0 și 1. Astfel, culoarea roșie este definită ca (1, 0, 0), culoarea verde (0, 1, 0), culoarea cyan (0, 1, 1).

Structura de date glm::vec3 reprezintă un vector de 3 dimensiuni (x, y, z). Aceasta face parte din biblioteca glm , inclusă în framework-ul de laborator. Componentele individuale ale vectorului, x, y și z, pot fi accesate în felul următor:

glm::vec3 position = glm::vec3 (10.0f, 10.0f, 0.1f);
 
float x = position.x;
float y = position.y;
float z = position.z;

A doua structura de date utilizată pentru descrierea rețelei de triunghiuri este o mulțime ordonată de indici ai vârfurilor din prima structură de date:

vector<unsigned int> indices
{
    0, 1, 2,
    0, 2, 3
}

Fiecare triplet de indici consecutivi din această mulțime descrie vârfurile care formează un triunghi. De exemplu, tripletul 0, 1, 2 descrie triunghiul format din primele 3 vârfuri din vertices, iar tripletul 0, 2, 3 descrie triunghiul format de primul vârf și ultimele două.

Procesul de rasterizare

Procesul de rasterizare este implementat de cele mai multe ori hardware în procesorul grafic, dar pentru anumite procesoare, este implementat software în driver-ul companion al tipului de procesor grafic. Aceste procesoare au arhitecturi closed-source, astfel că metodele utilizate nu sunt publice. Din acest motiv, în acest laborator, veți implementa o versiune didactică a rasterizatorului, ce obține rezultate similare cu cel utilizat de procesorul grafic :) .

Abordarea propusă pentru implementare, în acest laborator, este următoarea:

  • Se parcurg pe rând toți pixelii din dreptunghiul incadrator al triunghiului
    • În situația în care centrul pixelului se află în interiorul triunghiului
      • Se calculează informația de adâncime a pixelului prin interpolare între vârfuri
      • În situația în care testul de adâncime confirmă că acest pixel trebuie să fie vizibil
        • Se calculează culoarea pixelului prin interpolare între vârfuri

Centrul unui pixel p, ce se află pe linia r și coloana c în grilă, se consideră că are coordonatele la poziția (c + 0.5, r + 0.5). Numeroatarea liniilor și coloanelor începe de la 0. De exemplu, pentru pixelul aflat pe linia 0 și coloana 0 în grila de pixeli, coordonatele centrului sunt (0.5, 0.5), iar centrul pixelului de pe linia 100 și coloana 150 este la coordonatele (150.5, 100.5).

Din acest motiv, o grilă de pixeli de rezoluție 1280×720, are ultima coloană egală cu 1279 și ultima linie egală cu 719. Observăm că rezoluția unei grile este dată mai întâi de lățime, ce reprezintă numărul de coloane și apoi de înălțime, ce reprezintă numărul de linii.

Punct în interiorul unui triunghi

Pentru a verifica dacă un punct se află în interiorul unui triunghi, se pot folosi ariile triunghiului și cele ale triunghiurilor formate de punct cu perechi de vârfuri din triunghi. În imaginea de mai jos se pot vedea aceste suprafețe.

Astfel, în situația în care suma ariilor triunghiurilor interioare este egală cu aria triunghiului, punctul se află în interior.

$$ A_{\Delta V_1 V_2 V_3} = A_{\Delta P V_1 V_3} + A_{\Delta P V_1 V_2} + A_{\Delta P V_2 V_3} $$

Deoarece ariile sunt calculate cu tipuri de date în virgulă mobilă, nu se poate folosi direct comparația de egalitate și trebuie să ne asumăm un anumit nivel de eroare în calcule. Astfel, în cod, comparația va fi în felul următor:

const float EPSILON = 5.0f;
 
bool inside_triangle = abs(area_v1v2v3 - (area_pv1v3 + area_pv1v2 + area_pv2v3)) < EPSILON;

Aria unui triunghi oarecare

Pentru calcularea ariei unui triunghi oarecare, se poate folosi formula lui Heron:

$$ A_{\Delta V_1 V_2 V_3} = \sqrt{s \cdot (s-a) \cdot (s-b) \cdot (s-c)} \\ s = \frac{1}{2} \cdot (a+b+c) \\ a=\lVert V_1-V_2 \rVert \\ b=\lVert V_1-V_3 \rVert \\ c=\lVert V_2-V_3 \rVert \\ $$

Pentru a calcula norma unui vector $\lVert\vec{V_1V_2}\rVert=\lVert V_1-V_2 \rVert$, se poate folosi biblioteca glm în felul următor:

float norm_vec12 = glm::length(v1 - v2);

Interpolarea informației din vârfuri

Pentru a calcula informația unui pixel prin interpolare între vârfuri, se pot folosi coordonatele baricentrice ale centrului pixelului. Aceste coordonate sunt:

$$ P'=(u,v,w) \\ u= \frac{A_{\Delta P V_1 V_2}}{A_{\Delta V_1 V_2 V_3}} \\ v=\frac{A_{\Delta P V_1 V_3}}{A_{\Delta V_1 V_2 V_3}} \\ w=\frac{A_{\Delta P V_2 V_3}}{A_{\Delta V_1 V_2 V_3}} \\ $$

Aceste coordonate sunt descrise prin raportul dintre ariile unuia dintre triunghiurile interioare din imaginea de mai jos și aria triunghiului mare.

Pentru calcularea culorii punctului se folosește:

$$ C_P= u \cdot C_{V_3} + v \cdot C_{V_2} + w \cdot C_{V_1} $$

Această abordare poate fi utilizată pentru a calcula prin interpolare între vârfuri orice tip de informație asociată vârfurilor. De exemplu, pentru a calcula valoarea de adâncime a unui pixel, descrisă mai sus, se folosește:

$$ Z_P= u \cdot Z_{V_3} + v \cdot Z_{V_2} + w \cdot Z_{V_1} $$

Cerințe laborator

Completați clasa TriangleRasterizer cu următoarele:

  1. 0.15p - Completați metodele CheckPointInsideTriangle() și ComputeTriangleArea() pentru a calcula dacă un punct se află în interiorul unui triunghi, conform indicațiilor de mai sus.
  2. 0.1p - Completați metoda ComputePixelColor() pentru a calcula informația de culoare prin interpolare între vârfuri. Utilizați coordonatele baricentrice, descrise mai sus.
  3. 0.05p - Completați metoda ComputePixelDepth() pentru a calcula informația de adâncime prin interpolare între vârfuri. Utilizați coordonatele baricentrice, descrise mai sus.

Rezultatul final pe care ar trebui să îl obțineti este următorul:

Se poate observa că fiecare din cele două triunghiuri se află parțial în spate și parțial în fața celuilalt. Acest rezultat este obținut deoarece triunghiul ce are în vârfuri culorile roșu, verde și albastru are toate coordonatele de adâncime egale cu 0.5, iar triunghiul ce are în vârfuri culorile galben, magenta și cyan, are coordonata de adâncime a vârfului de culoare cyan egală cu 1 și coordonatele de adâncime a celorlalte 2 vârfuri egale cu 0. Din acest motiv, apare efectul de întretăiere a celor două triunghiuri.

ppbg/laboratoare/01.txt · Last modified: 2023/10/12 12:31 by andrei.lambru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0