Table of Contents

Laboratorul 04

Video Laborator 4: https://youtu.be/huCrfe9sbMQ
Autor: Alex Dinu

Operații de calcul cu vectori

Până acum în laboratoare ați folosit tipul de date glm::vec3 pentru a reprezenta vectori de 3 dimensiuni, în mod special pentru a reprezenta poziția unui obiect în spațiu. Poate apărea însă o confuzie între termenul de vector și poziție: pentru că prin definiție, un vector are o direcție și o magnitudine (sau lungime).

Niște exemple de vectori în spațiul 2D (în 3D este același principiu, dar este mai ușor de observat în 2D) arată așa:

Atenție! Se poate observa cum vectorul V și W sunt egali, deși au o origine diferită. Asta se întâmplă deoarece ei reprezintă aceeași direcție, cu aceeași lungime.

Prin urmare, dacă vrem să vizualizăm vectorii ca o poziție, putem să ne imaginăm un vector cu originea în (0, 0, 0) și apoi cu direcția către locul unde vrem să fie poziția, construind astfel un “vector de poziție” (cum este in acest exemplu, vectorul V).

Principalele operații cu vectori

1. Operatii cu un scalar:

Unde în loc de + pot fi folosiți operatorul de diferență, înmulțire sau împărțire.

2. Opusul unui vector:

Calculul unui vector cu direcția inversă se poate face înmulțind vectorul cu scalarul -1.

3. Adunarea și scăderea vectorilor:

Matematic, adunarea a doi vectori se face făcând suma pe componente, ca în exemplul de mai jos:

Vizual însă, efectul adunării este următorul (regula triunghiului):

În mod asemănător, efectul vizual al scăderii este următorul:

4. Lungimea unui vector:

In mod normal, lungimea se calculeaza folosind teorema lui Pitagora, deci unde lungimea vectorului ar fi: $$length = sqrt(x*x + y*y);$$

În OpenGL, există o funcție în GLM pentru calculul lungimii:

glm::vec3 v = glm::vec3(2, 3, 5);
float length = glm::length(v); // 6.164414

Un alt tip special de vector poartă denumirea de vector unitate care are o proprietate în plus, anume faptul că are lungimea 1. Pentru a obține acest rezultat se va împărți vectorul la lungimea lui. În OpenGL, funcția această este deja implementată pentru voi:

glm::vec3 v = glm::vec3(2, 3, 5);
glm::vec3 v_norm = glm::normalize(v); // (0.324443, 0.486664, 0.811107)

Normalizarea ajută la lucrul cu vectori, mai ales în cazuri unde ne interesează doar direcția acestora.

5. Produsul scalar (Dot-product):

O aplicație foarte interesantă este atunci când vectorii sunt unitate (au lungime 1), deoarece produsul scalar o să fie valoarea cosinusului. Un exemplu vizual este următorul:

În OpenGL, există o funcție în GLM pentru calculul produsului scalar:

glm::vec3 a = glm::vec3(0, 1, 0);
glm::vec3 b = glm::vec3(1, 0, 0);
float dotProd = glm::dot(a, b);

6. Produsul vectorial (Cross-product):

Produsul vectorial este definit numit în spațiul 3D, unde primește 2 vectori (care nu sunt paraleli) ca input și produce un al treilea vector care este perpendicular pe cei 2 vectori:

În OpenGL, există o funcție în GLM pentru calculul produsului vectorial:

glm::vec3 a = glm::vec3(0, 1, 0);
glm::vec3 b = glm::vec3(1, 0, 0);
glm::vec3 crossProd = glm::cross(a, b); // (0, 0, -1)

Pentru alte informații utile despre operațiile vectoriale puteți citi mai departe aici.

Transformări 3D

Obiectele 3D sunt definite într-un sistem de coordonate 3D, de exemplu XYZ. În cadrul acestui laborator vom implementa diferite tipuri de transformări ce pot fi aplicate obiectelor: translații, rotații și scalări. Acestea sunt definite în format matriceal, în coordonate omgene, așa cum ați învățat deja la curs. Matricile acestor transformări sunt următoarele:

Translația

$$ \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} $$

Rotația

Rotația față de axa OX

$$ \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} $$

Rotația față de axa OY

$$ \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} $$

Rotația față 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} $$

Rotația față de o axă paralelă cu axa OX

Rotația relativă la o axă paralelă cu axa OX se rezolvă în cel mai simplu mod prin:

  1. translatarea atât a punctului asupra căruia se aplică rotația cât și a punctului în jurul căruia se face rotația a.î. cel din urmă să se afle pe axa OX
  2. rotația normală (în jurul axei OX)
  3. translatarea rezultatului a.î. punctul în jurul căruia s-a făcut rotația să ajungă în poziția sa inițială

Similar se procedeaza și pentru axele paralele cu OY și OZ.

La curs veți învăța cum puteți realiza rotații față de axe oarecare (care nu sunt paralele cu OX, OY sau OZ).

Scalarea

Scalarea față de origine

$$ \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} $$

Dacă $sx = sy = sz$ atunci avem scalare uniformă, altfel avem scalare neuniformă.

Scalarea față de un punct oarecare

Scalarea relativă la un punct oarecare se rezolvă în cel mai simplu mod prin:

  1. translatarea atât a punctului asupra căruia se aplică scalarea cât și a punctului față de care se face scalarea a.î. cel din urmă să fie originea sistemului de coordonate
  2. scalarea normală (față de origine)
  3. translatarea rezultatului a.î. punctul față de care s-a făcut scalarea să ajungă în poziția sa inițială

Utilizarea bibliotecii GLM

În cadrul laboratorului folosim biblioteca GLM care este o bibliotecă implementată cu matrici în formă coloană, exact același format ca OpenGL. Forma coloană diferă de forma linie prin ordinea de stocare a elementelor matricei în memorie, Matricea de translație arată în modul următor în memorie:

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

Din această cauză, este convenabil ca matricile să fie scrise manual în forma aceasta:

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

În framework-ul de laborator, în fișierul transform3D.h sunt definite funcțiile pentru calculul matricilor de translație, rotație și scalare. În momentul acesta toate funcțiile întorc matricea identitate. În cadrul laboratorului va trebui să modificați codul pentru a calcula matricile respective.

În cadrul laboratorului, în fișierul lab4.cpp, există o serie de obiecte (cuburi) pentru care, în funcția Update(), inainte de desenare, se definesc matricile de transformări. Comanda de desenare se dă prin funcția RenderMesh(), care are ca parametru și matricea de transformări.

modelMatrix = glm::mat4(1);
modelMatrix *= Transform2D::Translate(1, 2, 1);
RenderMesh(meshes["box"], modelMatrix);

Pentru exemplul anterior, matricea de translație creată va avea ca efect translatarea cubului curent cu (1, 2, 1). Pentru efecte de animație continuă, pașii de translație ar trebui să se modifice în timp.

Transformarea fereastră-poartă

în laboratorul 3, s-a discutat despre transformarea fereastră-poartă si a fost parcursă matematica din spatele ei. în realitate, transformarea fereastră-poartă face parte din transformările fixe din banda grafică și nu este o operație care poate fi modificată de programatorul care se folosește de OpenGL.

Modul în care se poate interacționa cu transformarea fereastră-poartă în OpenGL este funcția:

glViewport(GLint x, GLint y, GLint width, GLint height)

Această funcție specifică OpenGLului că ceea ce urmează să fie trimis către randare va trebui să apară pe ecran în coordonatele fizice: $$(x, y) – (x + width, y + height)$$

Atenție! Dacă nu este șters conținutul depth bufferului după ce scena a fost desenată în primul viewport, este posibil ca al 2lea viewport să fie randat în spatele obiectelor din scenă, producându-se efecte vizuale neplăcute.

Depth Buffer: Depth bufferul (sau Z bufferul) este o structură în care se menține adâncimea în scenă a fiecărui fragment din imaginea desenată.

glViewport poate fi extrem de util în diverse situații în care s-ar dori redarea mai multor puncte de vedere dintr-o scenă în același timp, ca de exemplu: oglinzi retrovizoare într-un joc cu mașini sau o hartă văzută de sus care se actualizează odată cu mișcarea jucătorului, cum se poate observa în animația de mai jos:

Cerințe laborator

  1. Completați funcțiile de translație, rotație și scalare din /lab4/transform3D.h
  2. Să se realizeze animații la apăsarea tastelor (în OnInputUpdate) pentru cele 3 cuburi, astfel:
    • cu tastele W, A, S, D, R, F să se deplaseze primul cub în scenă
    • cu tastele 1 și 2 să se scaleze al doilea cub (să se mărească și să se micșoreze) față de centrul propriu
    • cu tastele 3, 4, 5, 6, 7, 8 să se rotească al treilea cub față de axele locale OX, OY, OZ
  3. Randați toate obiectele din scenă și în viewportul mic din colț
  4. Aplicați modificări asupra viewportului mic din colt, astfel:
    • I, J, K, L să deplaseze viewportul mic în ecran (sus, stânga, jos, respectiv dreapta) (în OnKeyPress)
    • U, O să micșoreze, respectiv mărească viewportul mic (în OnKeyPress)