Table of Contents

Tema 3 - Firefly Forest

În cadrul acestei teme puteți opta pentru a lucra în echipe de maxim 2 studenți!

  • Cerințele sunt aceleași atât pentru o implementare în mod individual cât și în echipă
  • În cazul lucrului în echipă, în timpul prezentării temei va trebui să prezentați în mod diferențiat contribuția fiecăruia (să știe și să prezinte fiecare student ce a implementat)

În cadrul acestei teme veți implementa o scenă virtuală ce prezintă o pădure nocturnă, populată de licurici, care include și un turn de observație. Tema urmărește studierea conceptelor de iluminare și texturare.

Elementele vizuale principale ce compun scena vor fi un teren texturat, copaci animați cu un efect de vânt, licurici care emit lumină punctiformă și un turn de observație echipat cu lumini tip spotlight care proiectează un pattern de textură (light cookie). Pentru accentuarea atmosferei veți adăuga și un efect de ceață.

Puteți studia în următorul videoclip o posibilă implementare a cerințelor.

Scena virtuală

Scena va fi compusă dintr-o serie de modele 3D, anume:

Poziționarea camerei

Camera va fi de tip perspectivă și trebuie poziționată astfel încât să fie surprinse în același timp toate elementele de interes din scenă (terenul, copacii, turnul, licuricii). Nu este obligatoriu să controlați poziția/rotația camerei în timpul rulării aplicației, aceasta poate să rămână statică.

Construcția turnului de observație

Turnul de observație este construit folosind o serie de primitive. Varianta recomandată, care este utilizată și în demo, are următoarele componente:

  1. Stâlpi de susținere (4 paralelipede care trec prin platformă și ajung la acoperiș)
  2. Platformă (paralelipiped)
  3. Acoperiș (con turtit pe înălțime)
  4. Proiectoare pentru lumini (2 conuri)

Puteți propune alte variante de geometrie. Spre exemplu să realizați elemente diferite pentru susținerea bazei și a acoperișului. Simplificări ale variantei propuse nu vor aduce punctajul total.

Primitiva de tip con nu se regăsește în framework. Dacă doriți s-o utilizați, aceasta se poate descărca de aici cone.zip

Construcția copacilor

Copacii se aseamănă unor pini. Varianta recomandată, care este utilizată și în demo, are următoarele componente:

  1. Trunchi (cilindru)
  2. Frunziș (minim 6 paralelipipede)

Frunzișul trebuie dispus în mai multe niveluri pe înălțime, a căror dimensiune să scadă treptat.

Primitiva de tip cilindru nu se regăsește în framework. Dacă doriți s-o utilizați, aceasta se poate descărca de aici cylinder.zip

Trunchiului i se va aplica un efect simplu de subțiere pe înălțime. Frunzele vor avea o animație ciclică, simulând bătaia vântului. Aceste efecte se realizează în shader și sunt descrise mai jos.

Deformare în Vertex Shader (Copaci)

Trunchi

Trunchiului i se va aplica un efect de subțiere. În funcție de înălțimea vertex-ului, îl veți apropia de axa principală a cilindrului.

$$ t = \frac{y}{TREE\_HEIGHT}\\ scale = 1 - t\\ x_{final} = x_{original} \cdot scale\\ z_{final} = z_{original} \cdot scale $$

Frunziș

Frunzișul se deplasează în bătaia vântului. Aceasta presupune deplasarea orizontală a vârfurilor în funcție de înălțimea lor în coordonate obiect.

Ideea principală este că vertecșii de la baza copacului se mișcă mai puțin, iar cei de la vârf sunt afectați mai puternic. Deplasarea va fi modulată sinusoidal în funcție de timp pentru a crea o animație continuă de balansare.

Parametri de luat în considerare:

$$ t = \frac{y}{TREE\_HEIGHT}\\ wave = \sin(Time \cdot BendFrequency + t \cdot 0.5 + BendPhase)\\ offset = wave \cdot BendStrength \cdot t\\ x_{new} = x_{original} + offset $$

Amplitudine:

Fiecare parte a copacului are asociată propria amplitudine de oscilatie:

$$ Trunchi: offset = wave \cdot A_{trunchi} \cdot t = wave \cdot 0.0 \cdot t = 0 \\ Frunze\_Jos: offset = wave \cdot A_{mic} \cdot t\\ Frunze\_Mijloc: offset = wave \cdot A_{mediu} \cdot t\\ Frunze\_Varf: offset = wave \cdot A_{mare} \cdot t $$

Rezultatul final pe care trebuie să îl obțineți este:

Texturare

Fiecare element constitutiv al unui copac se va textura:

Terenul

Terenul va fi format dintr-un plan. Acesta trebuie scalat suficient astfel încât marginile acestuia să nu fie evidente din perspectiva camerei.

Pe acest plan va trebui aplicată o textură care se repetă. Deoarece terenul va trebui scalat în mod semnificativ, simpla aplicare a texturii ar conduce la întinderea acesteia pe întreg planul. Pentru a evita acest efect, coordonatele de texturare pot fi scalate în shader sau mesh-ul poate avea coordonate UV în afara domeniului uzual (0,0) → (1,1).

Nu uitați că este necesară setarea wrapping_mode-ului texturii cu GL_REPEAT. De asemenea, vă recomandăm utilizarea unei texturi de tip “seamless” pentru acest task.

Licuricii

Construcție și randare

Licuricii sunt reprezentați de sfere mici cu shader emisiv. Aceștia emit propria lor culoare fără a fi iluminați de alte surse de lumină, creând efectul de strălucire caracteristic. Culoarea emisivă este considerată valoare finală, fără calcule de iluminare Phong, și este apoi atenuată prin ceață pentru consistență cu restul scenei.

Lumina punctiformă produsă de licurici este tratată în secțiunea de Iluminare → Lumini punctiforme (Licurici). În această secțiune se prezintă doar cum ar trebui să fie creată culoarea corespunzătoare (componenta emisivă) modelului de licurici (sfera).

Comportament și mișcare

Licuricii se vor deplasa pe traiectorii orbitale fie în jurul camerei, fie în jurul turnului (alegeți voi varianta care vă place). Fiecare licurici va avea:

Traiectoria rezultată va fi o combinație între mișcarea circulară orizontală și oscilația verticală:

\[ x = C_x + R \cdot \cos(\omega t + \phi) \\ z = C_z + R \cdot \sin(\omega t + \phi) \\ y = C_y + h_0 + A \cdot \sin(\omega_v t + \phi_v) \]

unde \((C_x, C_y, C_z)\) reprezintă poziția în jurul căreia licuricii orbitează, \(R\) este raza orbitală, și \(A\) este amplitudinea oscilației verticale.

În GIF-ul următor este ilustrat un exemplu pentru randarea și comportamentul licuricilor.

Efectul de ceață (Fog)

Pentru a adăuga un plus atmosferei și pentru a simula adâncimea, se va implementa un efect de ceață în Fragment Shader.

Se va folosi un model de ceață cu atenuare liniară, în care densitatea ceții crește proporțional cu distanța dintre fragment și cameră. Fragmentele apropiate de cameră vor fi vizibile clar, în timp ce cele îndepărtate vor fi estompate progresiv spre culoarea ceții.

Calcul densitate ceață

Fie:

Distanța fragmentului față de cameră se calculează ca:

\[ d = \|\vec{P} - \vec{C}\| \]

Factorul de densitate a ceții variază liniar între 0 (fără ceață) și 1 (ceață completă), limitat la intervalul \([0, 1]\):

\[ f_{\text{fog}} = \text{clamp}\left(\frac{d}{d_{\text{max}}}, 0, 1\right) \]

Combinarea culorilor

Culoarea finală a fragmentului se obține prin interpolarea liniară între culoarea obiectului și culoarea ceții, folosind funcția mix:

\[ \vec{C}_{\text{final}} = \text{mix}(\vec{C}_{\text{obiect}}, \vec{C}_{\text{fog}}, f_{\text{fog}}) \]

unde \(\vec{C}_{\text{obiect}}\) este culoarea fragmentului calculată după iluminare și texturare, iar \(\vec{C}_{\text{fog}}\) este culoarea ceții (de exemplu, gri).

Puteți observa în GIF-ul de mai jos scena cu și fără efectul de ceată.

Pentru un efect realistic, setați culoarea de ștergere a ecranului să fie acceași cu cea a ceții.

Iluminare

Iluminarea scenei se va implementa folosind 2 tipuri de surse de lumină: punctiformă și spotlight. Fiecare sursă de lumină va avea o culoare specifică care va influența calculele de iluminare.

Iluminarea trebuie să fie implementată folosind modelul de shading Phong, precum și modelul de calcul al reflexiei luminii Phong.

Lumini punctiforme (Licurici)

Fiecare licurici va avea asociată o lumină punctiformă care va ilumina terenul și copacii din apropiere. Această lumină:

În GIF-ul următor este ilustrat un exemplu pentru iluminarea punctiformă produsă de lucurici:

Implementarea voastră trebuie să suporte randarea mai multor surse de lumină în același frame!

Lumini Spotlight

Spotlight-urile emit lumină într-un con limitat, definit prin poziție, direcție și unghi de cutoff (\(\alpha\)). Un fragment este iluminat doar dacă se află în interiorul conului, iar intensitatea luminii scade gradual către margini. Implementarea luminii de tip spotlight se bazează pe cea pe care ați realizat-o în Laboratorul 08.

Light Cookies (Gobo Lights)

Light cookies sunt texturi proiectate prin conul de lumină al unui spotlight pentru a crea pattern-uri de umbră și lumină.

În iluminatul fizic (teatru, cinematografie) există două concepte înrudite:

În grafica pe calculator, cei doi termeni sunt folosiți interschimbabil – ambii se referă la aplicarea unei texturi (textură alpha sau RGB) asupra unei lumini pentru a modula intensitatea și/sau culoarea acesteia.

Mai jos puteți observa câteva example de light cookies din diverse aplicații grafice.

Pentru un spotlight, putem proiecta o textură (light cookie) pe fragmentele iluminate, similar unei proiecții tip perspectivă. În această secțiune sunt prezentați pașii matematici necesari pentru a determina coordonatele de textură corespunzătoare fiecărui fragment.

În figura următoare puteți observa o diagramă ce prezintă notațiile necesare pentru a calcula proiecția texturii de tip light cookie. Reveniți la diagramă când este necesar pentru a face corespondența cu derivarea matematică prezentată în subsecțiunile ce urmează.

Calculele prezentate în continuare trebuie realizate toate în Fragment Shader! Ordinea de implementare pentru luminile spotlight este:

  • Implementare iluminare spotlight “clasică” (calcul bazat pe cut_off, spot_light, spot_light_limit, light_att_factor pentru a obține contribuția difuză și speculară a luminii - contribuția de bază)
  • Implementare efect light cookie
  • Modularea contribuției de bază cu rezultatul efectului de light cookie

Considerăm:

Definim vectorul dintre sursa de lumină și punctul iluminat:

\[ \vec{v} = \vec{X} - \vec{L} \]

Sistemul de coordonate al spotlight-ului

Pentru a putea exprima punctele în raport cu direcția spotului, definim un sistem local de coordonate al spotlight-ului, format din trei vectori ortogonali (forward, right, up), care definesc un sistem de coordonate ortonormat \((\hat{x}, \hat{y}, \hat{z})\).

Direcția spotlight-ului este dată de vectorul \(\vec{d}_{light}\) (corespunzător variabilei light_direction din shader-ul laboratorului 8). Acest vector este considerat deja normalizat și definește axa \(\hat{z}\) (forward) a sistemului local de coordonate al spotlight-ului.

Pentru a construi o bază ortonormată completă, avem nevoie de un vector arbitrar \(\vec{a}\) care să nu fie paralel cu \(\hat{z}\). Putem defini:

\[ \vec{a} = \begin{cases} (0, 1, 0), & \text{daca } | \hat{z} \cdot (0, 1, 0)| \neq 1 \\ (1, 0, 0), & \text{altfel} \end{cases} \]

Cu alte cuvinte, găsim \(\vec{a}\) astfel încât produsul scalar dintre acesta și \(\hat{z}\) să fie diferit de 1 (să nu fie paraleli).

La implementare, verificarea \(|\hat{z} \cdot (0, 1, 0)| \neq 1\) trebuie făcută cu o toleranță numerică pentru a evita erorile de precizie în virgulă mobilă.

Axa \(\hat{x}\) (right) a spotlight-ului se obține prin produs vectorial între vectorul de referință și axa \(\hat{z}\) (forward), urmat de normalizare:

\[ \hat{x} = \frac{\vec{a} \times \hat{z}}{\|\vec{a} \times \hat{z}\|} \]

Axa \(\hat{y}\) (up) este apoi determinată ca produs vectorial între două axe unitare ortogonale:

\[ \hat{y} = \hat{z} \times \hat{x} \]

Obținem astfel baza ortonormată \((\hat{x}, \hat{y}, \hat{z})\) ce reprezintă sistemul de coordonate al spotlight-ului.

Punct în spațiul spotlight-ului

Pentru a exprima poziția punctului \(\vec{X}\) de coordonate \((x, y, z)\) în sistemul local al spotlight-ului, proiectăm vectorul \(\vec{v}\) pe axele noului sistem de coordonate:

\[ x = \vec{v} \cdot \hat{x} \\ y = \vec{v} \cdot \hat{y} \\ z = \vec{v} \cdot \hat{z} \]

Coordonata \(z\) măsoară distanța punctului de-a lungul axei principale a spotului.

Proiecția pe planul de referință \(z = 1\)

Pentru proiecția texturii de tip light cookie, considerăm un plan de referință perpendicular pe axa \(\hat{z}\), situat la coordonata:

\[ z = 1 \]

Coordonatele punctului proiectat pe acest plan se obțin printr-o împărțire perspectivă:

\[ \vec{p} = \begin{pmatrix} \dfrac{x}{z} \\ \dfrac{y}{z} \end{pmatrix} \]

Punctul \(\vec{p}\) reprezintă intersecția razei de lumină cu planul de proiecție.

Normalizare în funcție de unghiul spotului

Fie \(\alpha\) unghiul de deschidere al spotului. La coordonata \(z = 1\), raza conului este:

\[ R = \tan(\alpha) \]

Pentru a normaliza coordonatele proiectate astfel încât interiorul spotului să corespundă domeniului \([-1, 1]\), împărțim coordonatele lui \(\vec{p}\) la această rază:

\[ \vec{n} = \dfrac{1}{\tan(\alpha)} \, \vec{p} = \begin{pmatrix} \dfrac{x}{z \tan(\alpha)} \\ \dfrac{y}{z \tan(\alpha)} \end{pmatrix} \]

Maparea către coordonate de textură

Coordonatele normalizate \(\vec{n}\) sunt în domeniul \([-1, 1]\). Texturile sunt adresate în domeniul \([0, 1]\), așadar facem o mapare liniară:

\[ \mathbf{t} = (u, v) = \dfrac{\vec{n}}{2} + \dfrac{1}{2} \]

unde \(\mathbf{t}\) reprezintă coordonatele de textură (UV) folosite pentru eșantionare.

Coordonatele \(\mathbf{t} = (u, v)\) sunt folosite pentru a eșantiona textura light cookie, obținând culoarea în punctul respectiv:

\[ \vec{c} = (c_R, c_G, c_B) \]

Valoarea \(\vec{c}\) este utilizată pentru a modula contribuția luminii spotului asupra fragmentului, pe fiecare canal RGB separat. Mai concret, această eșantionare se realizeză în GLSL folosind funcția texture2D:

c = texture2D(cookieTexture, vec2(u, v)).rgb

Integrare în scenă

În scenă ar trebui să existe două lumini de tip spotlight, orientate înspre sol, atașate turnului de observație - pentru fiecare lumină “sursa” va fi înfățișată sub forma unui model de con (vedeți conurile colorate cu negru în imaginea de mai jos, precum și un exemplu de rezultat pentru lumina de tip spot + light cookies):

Barem [150p]

Exemple de Funcționalități Bonus

Întrebări și răspunsuri

Pentru întrebări vom folosi forumurile de pe Moodle. Orice nu este menționat în temă este la latitudinea fiecărui student!

Notare

Baremul este orientativ. Fiecare asistent are o anumită libertate în evaluarea temelor (de exemplu, să dea punctaj parțial pentru implementarea incompletă a unei funcționalități sau să scadă pentru hard coding). Același lucru este valabil atât pentru funcționalitățile obligatorii, cât și pentru bonusuri.

Tema trebuie încărcată pe Moodle. Pentru a fi punctată, tema trebuie prezentată la laborator. Vor exista laboratoare speciale de prezentare a temelor (care vor fi anunțate).

Indicații suplimentare

Tema va fi implementată în OpenGL și C++. Este indicat să folosiți framework-ul și Visual Studio.

Pentru implementarea temei, în folderul src/lab_m1 puteți crea un nou folder, de exemplu Tema3, cu fișierele Tema3.cpp și Tema3.h (pentru implementare POO, este indicat să aveți și alte fișiere). Pentru a vedea fișierele nou create în Visual Studio în Solution Explorer, apăsați click dreapta pe filtrul lab_m1 și selectați Add→New Filter. După ce creați un nou filtru, de exemplu Tema3, dați click dreapta și selectați Add→Existing Item. Astfel adăugați toate fișierele din folderul nou creat. În fișierul lab_list.h trebuie adăugată și calea către header-ul temei. De exemplu: #include “lab_m1/Tema3/Tema3.h”

Arhivarea Proiectului

  • În mod normal arhiva trebuie să conțină toate resursele necesare compilării și rulării
  • Înainte de a face arhiva asigurați-vă că ați curățat proiectul Visual Studio:
    • Click dreapta pe proiect în Solution ExplorerClean Solution
    • Ștergeți folderul /build/.vs (dacă nu îl vedeți, este posibil să fie ascuns)
  • În cazul în care arhiva tot depășește limita de 50MB (nu ar trebui), puteți să ștergeți și folderul /deps sau /assets întrucât se pot adăuga la testare. Nu este recomandat să faceți acest lucru întrucât îngreunează mult testarea în cazul în care versiunea curentă a bibliotecilor/resurselor diferă de versiunea utilizată la momentul scrierii temei.