În cadrul acestei teme, veți realiza un joc în care utilizatorul controlează un elicopter ce se deplasează pe suprafața unui asteroid deluros. Misiunea jucătorului este să culeagă mai multe mostre dintr-o resursă specială ce se găsește pe acest asteroid, resursă ce are forma unui copăcel. Controlul jocului se realizează în totalitate prin apăsarea butonului dreapta de la mouse. Acest buton este utilizat pentru selecția poziției de destinație pentru deplasarea elicopterului și pentru selecția copacilor de pe asteroid.
Puteți viziona mai jos un filmuleț demonstrativ cu o aplicație construită pe baza framework-ului de laborator, care acoperă cerințele.
Procesul de desenare a suprafeței asteroidului se realizează în mai mulți pași:
Primul pas necesită realizarea unei geometrii suport ce are forma unui plan. Această geometrie este creată dintr-o rețea poligonală ce conține cel puțin 10.000 de vârfuri. Un exemplu pentru această geometrie se găsește în următoarea imagine.
Geometria suport se realizează pe CPU și se trimite la desenare pentru a se deforma conform următorilor pași.
În ce de-al doilea pas, se realizează deformarea geometriei suport conform unei hărți de înălțime, similar precum în laboratorul 8. Rezultatul acestei operații se poate observa în imaginea de mai jos.
Suprafața deformată la pasul anterior se colorează prin eșantionarea a două texturi, pe baza inălțimii suprafeței, similar precum în bonusul din laboratorul 8. Rezultatul se poate vizualiza în următoarea imagine.
Ultimul pas este descris în secțiunea următoare și reprezintă deformarea suprafeței pentru a se obține o curbură. Acest rezultat se poate observa în imaginea de mai jos.
Rezultatul este doar un efect vizual ce curbează geometria în jurul elicopterului, astfel că acesta poate ajunge la limita geometriei lumii.
Efectul de curbură se realizează prin modificarea componentei y a coordonatelor pentru toate vârfurile din care este realizată suprafața asteroidului. Procesul este creat în vertex shader. Componenta y a tuturor vârfurilor se modifică după cum urmează:
$$ Pozitie_{v_y} = Pozitie_{v_y} - \|{Pozitie_{elicopter}-Pozitie_v}\|^2 \cdot factorCurbura $$
Factorul de curbură este proporțional cu dimensiunea obiectelor din lume. Pentru demo-ul de mai sus, este utilizat un factor de 0.02.
Pentru plasarea unui obiect pe suprafața asteroidului, trebuie să fie aplicate obiectului următoarele transformări:
Pentru realizarea transformărilor de mai sus, există două opțiuni:
Geometria elicopterului poate fi vizualizată în animația de mai jos.
Geometria elicopterului este compusă din două cuburi redimensionate neuniform pentru a defini cabina și coada elicopterului, împreună cu alte patru cuburi, de asemenea redimensionate neuniform, pentru a reprezenta cele două elice. Geometria cabinei și a cozii este desenată cu o culoare diferită față de cea a elicelor.
După cum se poate vedea și în imaginea de mai sus, cele două elice au o animație continuă de rotație.
Geometria marcajului destinație pentru elicopter poate fi vizualizată în imaginea de mai jos.
Paralelipipedul din partea superioară este obținut prin redimensionarea neuniformă a unui cub. Acesta are o animație continuă de oscilație în direcția sus-jos.
În partea de jos a marcajului, se află un disc de cerc ce se află puțin de-asupra terenului.
Geometria copacului se realizează prin desenarea unei ierarhii de paralelipipede cu mai multe niveluri, unde la fiecare nivel, se desenează un paralelipiped redimensionat uniform, rotit și translatat în capătul paralelipipedului de la nivelul anterior. Pentru a finaliza desenarea, se aplică la final toate transformările utilizate în desenarea paralelipipedului de la nivelul anterior. Rezultatul se poate vedea în imaginea de mai jos.
La fiecare nivel al ierarhiei de paralelipipede, se desenează 3 ramuri de ierarhie, fiecare rotită la 120 de grade față de axa OY, una față de cealaltă. Pentru copacul din imagine, s-au utilizat 6 niveluri ale ierarhiei de paralelipipede.
Pentru colorarea copacului, se utilizează procesul de mapare cilindrică. Se folosesc două texturi, una pentru trunchiul copacului și una pentru frunzișul coroanei. Pentru a calcula coordonatele de textura în vârfurile geometriei ce descrie copacul, se utilizează procesul de mapare cilindrică ce are următoarele formule:
$$ u = \frac{1}{2\pi}arctan(z/x) \\ v = \frac{y}{treeHeight} $$
unde u și v sunt componentele x și y ale coordonatei de textură, (x, y, z) reprezintă componentele coordonatei vârfului în spațiul lumii și treeHeight reprezintă înălțimea copacului în spațiul lumii.
Suplimentar, pentru îmbunătățirea calității vizuale, desenarea se realizează după cum urmează:
Rezultatul poate fi vizualizat în imaginea de mai jos.
Pentru a realiza procesul de selecție, se desenează toată scena într-un obiect de tip framebuffer creat de voi. Acest framebuffer conține 2 texturi de culoare:
RGBA32F
și tipul de dată float
.
Pentru a afișa informația din prima textură de culoare pe ecranul fereastrei date de sistemul de operare, trebuie copiat conținutul primei texturi de culoare din obiectul de tip framebuffer creat de voi în textura de culoare a obiectului de tip framebuffer implicit, ce aparține ferestrei. Pentru acest proces, puteți utiliza următorul cod:
// custom_framebuffer_object reprezinta identificatorul obiectului de tip framebuffer creat de voi glBindFramebuffer (GL_READ_FRAMEBUFFER, custom_framebuffer_object); glReadBuffer (GL_COLOR_ATTACHMENT0); glBindFramebuffer (GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer (0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
Texturile de culoare din interiorul obiectului de tip framebuffer creat de voi trebuie să aibă aceeași dimensiune precum textura de culoare din interiorul obiectului de tip framebuffer implicit, ce aparține ferestrei. Acest cod trebuie utilizat la finalul cadrului.
Pentru a realiza procesul de selecție a destinației, în canalele de culoare RGB ale celei de-a doua texturi de culoare ale obiectului de tip framebuffer creat de voi, trebuie să se păstreze coordonata (x, 0, z) a geometriei suport, după pasul de deformare pe baza hărții de înălțime și înainte de deformarea pe baza curburii. Cele două texturi de culoare pot fi văzute în imaginea de mai jos:
Rezultatul vizual din imagine este obținut după aplicarea deformării pe baza curburii, dar coordonata (x, 0, z) păstrată în cea de-a doua textură de culoare este cea calculată înainte de realizarea curburii. Această coordonată este utilizată pentru specificarea locației de destinație a elicopterului, fără să se țină cont de deformarea suprafeței asteroidului. Aceasta din urmă este doar un efect vizual realizat în vertex shader, cum s-a menționat anterior.
Pentru a extrage coordonata (x, 0, z) din cea de-a doua textură de culoare din obiectul de tip framebuffer creat de voi, la momentul apăsării butonului dreapta de la mouse de către utilizator, trebuie extrasă informația din pixelul în dreptul căruia se află cursorul la momentul apăsării butonului. Codul pentru acest proces arată în felul următor:
// x si y reprezinta pozitia mouse-ului pe ecran, in pixeli. float data [4]; y = window->props.resolution.y - y; // custom_framebuffer_object reprezinta identificatorul obiectului de tip framebuffer creat de voi glBindFramebuffer (GL_FRAMEBUFFER, framebuffer_object); glReadBuffer (GL_COLOR_ATTACHMENT1); glReadPixels (x, y, 1, 1, GL_RGBA, GL_FLOAT, data);
Pentru a realiza procesul de selecție a unui obiect, se păstrează suplimentar în canalul A din cea de-a doua textură de culoare a obiectului de tip framebuffer creat de voi un identificator pentru toate obiectele din scenă. O prezentare vizuală a acestor identificatori, sub formă de nuanțe de gri, se poate vedea în imaginea de mai jos.
Nuanțele de gri din cea de-a doua textură de culoare din imaginea de mai sus au scop de prezentare pentru a se putea vizualiza identificatorul sub forma unei culori. În situația în care cea de-a doua textură de culoare conține informație de tip RGBA32F
, aceasta permite păstrarea unor valori ce depașesc valoarea 1.
Elicopterul își începe deplasarea spre locația de destinație în momentul în care utilizatorul alege o astfel de locație prin apăsarea butonului dreapta de la mouse. La momentul alegerii destinației, se plasează pe suprafață obiectul de marcaj prezentat mai sus și acesta rămâne la poziția respectivă până la momentul în care elicopterul ajunge la destinație. În momentul în care elicopterul se oprește, obiectul de marcaj al destinației dispare.
Atașat de elicopter se află o cameră observator ce urmărește elicopterul. Suplimentar, în momentul deplasării elicopterului, acesta este orientat cu fața spre direcția de deplasare.
În situația în care se utilizează framework-ul de laborator, pentru desenarea geometriei din perspectiva camerei observator, se poate utiliza obiectul de tip Camera
, după cum urmează:
auto camera = GetSceneCamera(); // pozitia relativa a camerei fata de pozitia elicopterului glm::vec3 relativeCameraPosition = ...; // helicopterPosition este pozitia in lume a elicopterului controlat de jucator camera->SetPositionAndRotation( helicopterPosition + relativeCameraPosition, glm::quatLookAt(-glm::normalize(relativeCameraPosition), glm::vec3(0, 1, 0)) );
Pentru a orienta geometria elicopterului în direcția de deplasare, se utilizează o matrice de rotație în jurul axei OY. În situația în care se cunoaște direcția de deplasare, unghiul de rotație se poate calcula după cum urmează:
$$ unghi = arctan(\frac{directie_x}{directie_z}) $$
atan2()
pentru calcularea arctangentei.