Luci 1620 Visite) DirectX 11
L’elemento più importante nel mondo visivo è la luce. Tutto ciò che noi vediamo dipende unicamente da questo fattore e per questo le luci sono l’elemento chiave di ogni rappresentazione visiva, dal disegno agli effetti speciali nei film.
Per prima cosa bisogna partire da un presupposto importante: la luce reale non può essere rappresentata, e forse non lo sarà mai. La luce è composta da infinite particelle che rimbalzano contro gli oggetti determinandone il colore.
Quello che possiamo fare è solo cercare di simularla, cosa che nei videogiochi è la sfida più difficile. Nei software di rendering dove non serve che il risultato sia in tempo reale si usano tecniche in cui si simula la luce proiettando un certo numero di raggi facendogli eseguire il percorso che farebbe la luce. Nei videogiochi non si può. Ogni vertice non è in grado di vedere tutti gli altri, così come nessun pixel ne è in grado. L’illuminazione è gestista singolarmente a livello di vertice e di pixel. Per questo sono state elaborate dai matematici diverse formule matematiche di illuminazione atte a simulare la luce: queste formule prendono il nome di reflection model (modelli di riflessione). Esistono centinaia di formule base, ognuna che differisce dalle altre per complessità o tipo di materiale. In questo tutorial partiremo dalla formula più semplice: la Lambertian.
Innanzitutto bisogna partire dal concetto di normale. La normale è la direzione in cui è rivolta una superficie, quindi nel caso di DirectX parliamo di triangoli. Tale vettore è scomposto in 3 vettori in modo da essere assegnato al vertice. In questo caso si parla di normale al vertice.
Un vettore normale è rappresentato da 3 valori XYZ ed ha lunghezza pari ad 1. La normale è l’elemento fondamentale per tutti i modelli di illuminazione esistenti.
Per chi non fosse pratico di algebra vettoriale (materia che consiglio a tutti di approfondire) una direzione è definita come differenza tra il punto di destinazione e quello di origine.
Ad esempio dati 2 punti A (3,1) e B (7,4) il vettore direzione AB è uguale a (7-3,4-1) ossia (4,3). La lunghezza di AB è 5 (calcolata con pitagora in quanto il vettore AB è l’ipotenusa del triangolo rettangolo avente cateti pari a 4 e a 3). Dividendo 4 e 3 per 5 otteniamo il vettore AB normalizzato ossia
AB=(0.8,0.6)
Questa è una direzione normalizzata. La normale è appunto la direzione tra 2 punti.
L'istruzione normalize in HLSL o Normalize in DirectX effettuano le normalizzazioni di vettori.
Esistono diversi tipi di sorgenti luminose. Le principali sono quelle direzionali e le luci puntiformi, dette anche omni light. Nel primo caso parliamo di sorgenti luminose che provengono da distanza infinita e che non perdono intensità. Il sole è la tipica fonte luminosa che utilizza le luci direzionali dato che proviene da una distanza sufficientemente ampia. Le luci puntiformi invece provengono da sorgenti vicine alla scena, come lampadine o torce. La loro proprietà è quella di proiettare luce intorno ad essa e non da una sola direzione. In entrambi i casi abbiamo comunque una direzione. Nella luce direzionale la direzione della luce è definito nello stesso modo della normale, mentre nella luce puntiforme avremo come direzione la differenza tra la destinazione (il vertice ad esempio) e la sorgente.
La formula di Lambert dice che l’intensità della luce in un punto è inversamente proporzionale all’angolo che c’è tra il vettore normale ed il vettore incidente della luce.
I = N x -L
Il simbolo x è inteso in questo contesto come prodotto fattoriale. Il prodotto fattoriale tra 2 vettori equivale alla somma degli elementi del vettore ottenuto moltiplicando membro a membro due vettori.
A(2,3,4) x B (4,5,6) = ( 2*4 + 3*5 + 4*6)= 47
Il prodotto tra 2 vettori normalizzati equivale al coseno dell’angolo tra i due vettori e varia da 1 per raggi che guardano nella stessa direzione, 0 per angoli perpendicolari tra loro e -1 per angoli opposti.
Quanto più la luce sarà incidente, tanto più l’intensità tenderà al massimo. Per dare un colore alla luce possiamo infine moltiplicare l’intensità che otteniamo (che è un solo float, ricordate) per il colore del materiale e per il colore della luce (possiamo usare dei float4 per questo). Nel colore del materiale rientra anche quello della texture ovviamente.
Ecco la formula finale
Float4 finalColor = saturate( lightColor * materialColor * dot(N,-L));
La formula può essere ulteriormente raffinata aggiundo formule di attenuazione (ad esempio si può scegliere che la luce diminuisca in base alla distanza o all'angolo), luci ambientali. Ecco un esempio di formula di illuminazione completa basata su lambert
FinalColor = ambientColor + funzioneAttenuazione * lightColor * materialColor * dot(N,L)
Passiamo ora ad un nuovo modello di illuminazione, il Phong , quello di default per le vecchie DirectX (con la differenza però che noi calcoliamo tutto nel pixel shader evitando i difetti dell'interpolazione lineare ed ottenendo una qualità molto più elevata).
Il modello di illuminazione phong riprende il modello lambertian inserendo però l'effetto speculare (il bagliore delle auto è un esempio di specular).
La luce speculare dipende sia dall'oggetto, attraverso le normali, sia dalla posizione dell' osservatore.
La formula di illuminazione è la seguente
Float4 D= lightDiffuseColor * dot(N,-L);
float3 V=normalize(eyePosition - vertexPosition);
float3 R=normalize(2.0F * N * dot( N, L) - L);
float4 S= lightSpecularColor * pow(max(0.0F,dot(R,V)),power);
Float4 finalColor = A + MaterialDiffuseColor * D + MaterialSpecularColor * S;
- D è la diffuse light, calcolata come nel precedente tutorial.
- L è la direzione della luce
- V è il vettore EyeDirection che si ottiene come direzione tra la posizione della telecamera e quella del vertice
- R è il vettore riflessione
- S è la specular light che si ottiene elevando a potenza il prodotto fattoriale tra R e V. L'esponziale è il valore scalare power, che definisce l'intensità.
- A è la luce ambientale, ossia un incremento nel valore per simulare la presenza di luce diffusa
- MaterialDiffuseColor e MaterialSpecularColor sono i colori che l'oggetto emetto a contatto con la luce diffuse e specular
La somma tra la luce ambientale, diffusa e speculare è la phong light, probabilmente il modello di illuminazione più diffuso.
Notare infine che in caso di più sorgenti luminose il valore D ed il valore S sono le somme dei medesimi valori calcolati singolarmente per ogni sorgente.
Vi rimando al tutorial numero 7 della serie (Github o in fondo dal primo tutorial) che mostra anche come caricare un oggetto dal formato Obj WaveFront