Î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 va fi compusă dintr-o serie de modele 3D, anume:
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ă.
Turnul de observație este construit folosind o serie de primitive. Varianta recomandată, care este utilizată și în demo, are următoarele componente:
Copacii se aseamănă unor pini. Varianta recomandată, care este utilizată și în demo, are următoarele componente:
Frunzișul trebuie dispus în mai multe niveluri pe înălțime, a căror dimensiune să scadă treptat.
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.
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ș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 $$
Fiecare element constitutiv al unui copac se va textura:
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).
wrapping_mode-ului texturii cu GL_REPEAT.
De asemenea, vă recomandăm utilizarea unei texturi de tip “seamless” pentru acest task.
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.
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.
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.
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) \]
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ă.
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.
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:
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 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ă.
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ă)
Considerăm:
Definim vectorul dintre sursa de lumină și punctul iluminat:
\[ \vec{v} = \vec{X} - \vec{L} \]
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).
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.
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.
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.
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} \]
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
Î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):
Pentru întrebări vom folosi forumurile de pe Moodle. Orice nu este menționat în temă este la latitudinea fiecărui student!
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 va fi implementată în OpenGL și C++. Este indicat să folosiți framework-ul și Visual Studio.