In questa lezione parleremo di come vengono gestite luci ed ombre in Unity, e delle differenze fra i rendering path Forward e Deferred, e di come questi influenzano luci ed ombre. Come in molti programmi di grafica 3D, anche in Unity non esiste un trattamento davvero realistico della luce, ma il tutto viene simulato attraverso una serie di approssimazioni.
Nel mondo reale la luce non si ferma sulle superfici che colpisce ma rimbalza, si riflette, si rifrange nei liquidi. Tutti questi effetti citati non sono impossibili da ottenere, ma vanno simulati uno per uno ed a volte potrebbero essere molto costosi in termini di rendering, fino a risultare impossibili (ad esempio su mobile). Alcuni vengono ottenuti con gli shader (come la riflessione, la rifrazione), altri sono il prodotto di semplici GameObject che - coma accade spesso in Unity - fanno uso del componente Light.
La luce rimbalza anche sugli oggetti, illuminando di riflesso altri oggetti. Questo comportamento ricade in quello che viene definito Global Illumination, che è un processo di illuminazione molto complesso che può essere di tipo pre-calcolato (anche detto baked) o dinamico. Nel primo caso viene pre-calcolata prima che il gioco venga eseguito (un po' come nei film in 3D), e ne parleremo in dettaglio quando menzioneremo le Lightmap. Nel secondo caso, viene calcolata in tempo reale mentre il gioco è in esecuzione… questa soluzione non è ancora disponibile in Unity 4 (mentre esiste in Unreal Engine), ma verrà in parte implementata in Unity 5.
Così come le luci, anche le ombre possono essere di tipo semplice, e dinamico (ovvero che si muovono) oppure baked. Ombre in movimento sono necessario in caso si voglia dare un'ombra ad un personaggio, ma questo processo è dispendioso (si pensi che le ombre dinamiche su mobile non erano presenti nelle prime versioni di Unity…).
Le ombre statiche (baked), sono invece adatte agli ambienti in cui gli elementi non si muovono: possono essere pre-calcolate e disegnate in una texture che contiene anche le informazioni di luce (le Lightmap citate prima, appunto), e sono molto più morbide e dettagliate delle ombre dinamiche.
Esistono 4 tipi di luci in Unity: 3 dinamiche, ed una baked. Per creare una luce basta selezionare GameObject > Create Other > Directional Light
(oppure una delle altre). Vediamole in dettaglio una ad una. Tralasceremo la Area Light
, di cui parleremo nella lezione dedicata al Lightmapping.
La Directional Light
La Directional Light è una luce adatta a simulare un'illuminazione esterna, perché i suoi raggi sono paralleli e infiniti. Per questo motivo, questa luce influenza tutti gli oggetti nella scena indipedentemente dalla loro distanza, e non importa il suo posizionamento.
Inoltre, se le ombre non sono attive i suoi raggi non vengono bloccati dagli oggetti, quindi ci si potrebbe ritrovare nella strana situazione in cui un oggetto in una stanza chiusa risulti comunque illuminato su un lato da una Directional… è perfettamente normale.
Questo è come appare una Directional nella Scene View:
La Directional è indicata da un cilindro, con dei segmenti gialli che specificano la direzione dei raggi di luce. Come detto la posizione non è importante, e infatti nell'immagine possiamo vedere che anche se la Directional punta via dal cubo, questo è comunque illuminato dalla luce sul lato destro, così come è illuminato il piano.
La rotazione però lo è, e se ruotiamo la Directional sull'asse Y l'illuminazione cambia:
Una Directional si presenta così nell'Inspector:
Chiaramente, Type ci permette di trasformare una Directional in un altro tipo di luce, mentre Color e Intensity ne controllano colore e luminosità. Di solito, una luce solare si attesta sullo 0.5 di Intensity, anche perché a questo vanno unite altre fonti di luce (magari di tipo Point), l'ambient, e i rimbalzi della Global Illumination (se presente).
Un Cookie (nelle luci) è una texture in bianco e nero, che funge da maschera, come se fosse stato applicato uno stencil davanti ad un proiettore. Se Cookie viene assegnato, Cookie size definisce quanto questo è grande.
Draw Halo permette di disegnare un bagliore intorno alla luce, usando una texture che sarà applicata in corrispondenza della fonte di luce. Per far funzionare Draw Halonon è sufficiente abilitarlo, ma bisogna anche assegnare la texture da usare andando in Edit > Render Settings
.
Nota: se non vedete il bagliore, allontanate la luce dalla camera: Unity è tarato per non mostrare bagliori troppo vicini.
Le Flare, come i bagliori, sono effetti di luce che si applicano per simulare il cosiddetto Lens Flare, ovvero il bagliore derivato dalle lenti di obiettivi fotografici. Le Flare non sono semplici texture, ma un formato apposito gestito da Unity.
Per mostrare le Flare bisogna fare tre operazioni: per prima cosa importare il package che ne contiene un po' (andando in Assets > Import Package > Light Flares
), poi assegnarne una nella proprietà Flare del componente Light, ed infine configurarla nei Render Settings con Flare Strength e Flare Fade Speed.
Ecco come si presenta una Flare:
Infine, per ora menzioniamo solo l'opzione Culling Mask: così come per gli oggetti Camera, si può dire alla luce di illuminare solo oggetti su alcuni layer. Selezionando i layer opportuni nella Culling Mask si possono creare effetti particolari, come ad esempio avere una luce che illumini la GUI (ammesso che questa sia in 3D!) che non è la stessa che illumina la scena.
Le Point Light
Le Point Light sono luci puntiformi, adatte a creare effetti come fiamme, torce medievali, fuochi da campo.
Come detto, è facile convertire una Directional in una Point Light semplicemente cambiandone il Type. In alternativa, possiamo crearne una da GameObject > Create Other > Point Light
.
L'unica differenza importante con la Directional è che la Point Light ha un Range, che indica la distanza massima alla quale la Point Light illumina, espresso in unità di Unity. Il gizmo della luce si presenta così:
Come si può vedere, è una sfera che indica l'area di influenza. Sulla superficie ci sono 6 quadratini che possono essere trascinati per cambiarne l'ampiezza.
Un oggetto che si trovi al bordo di questa sfera riceve la luce solo in maniera minima, mentre uno vicino al centro la riceve in pieno (ovvero quanto il valore di Intensity).
A causa delle sue caratteristiche, è importante dove viene piazzata una Point Light, ma è ininfluente la sua rotazione (esattamente il contrario della Directional).
Le Spotlight
Le luci Spot sono dei faretti, che proiettano un cono di luce che ha un'ampiezza (Spot Angle) ed una distanza massima (Range). Per questo motivo, è importante sia la posizione che la rotazione delle Spotlight.
Una Spotlight si presenta così:
Come si può vedere, proietta un cono di luce dai bordi morbidi, ed ha un gizmo che è anch'esso un cono. Muovendo i quattro quadratini collegati ai raggi si varia lo Spot Angle, muovendo quello centrale si cambia il Range.
Così come per la Point, gli oggetti vicino alla fine del cono saranno illuminati solo marginalmente, mentre quelli vicino alla fonte subiranno un'illuminazione maggiore (come il cubo nell'immagine).
Le ombre dinamiche
Così come le luci, anche le ombre sono un'approssimazione. Per le ombre Unity usa un metodo chiamato shadow mapping: in pratica, crea una mappa di profondità a partire dal punto di vista della luce (non della camera) e la usa per decidere dove proiettare le ombre.
Di conseguenza, la qualità delle ombre è molto influenzata dalla risoluzione di questa texture. La risoluzione si può settare in Edit > Project Settings > Quality Settings
, modificando l'opzione Shadow Resolution.
Un altra opzione importante che si trova lì è Shadow Distance. Questo numero (espresso in unità di Unity) indica la distanza massima a cui vengono disegnate le ombre. Maggiore è la distanza massima, più Unity deve dividere la texture della shadow map per coprire tutta la distanza a cui si vedono le ombre.
Di conseguenza, avere ombre lontane comporta che tutte le ombre (anche quelle più vicine) saranno renderizzate ad una risoluzione minore. Molto spesso avere ombre troppo lontane non serve (il giocatore non le noterebbe comunque), quindi è meglio tenere questo valore più basso possibile per tenere al massimo la qualità delle ombre più vicine.
Per ottenere le ombre, è sufficiente modificare lo Shadow Type nell'inspector di una luce Directional, scegliendo Hard Shadows (l'ombra è “dura”) o Soft Shadows (l'ombra ha dei bordi sfumati, chiaramente più pesante).
Rivedendo la luce creata ad inizio lezione, con le ombre risulterebbe così:
Come si può vedere, l'ombra risulta un po' seghettata a causa della shadow map a bassa risoluzione.
Una volta abilitate le ombre, possiamo regolarle nell'Inspector della luce. Per le Hard Shadows, Strength ne indica l'opacità. Per risultati ottimali conviene rimanere su un valore 0.7
o qualcosa simile.
Bias serve ad evitare artefatti grafici nelle ombre. Ad esempio, quando Unity cerca di disegnare un'ombra proveniente da una luce Directional che punta verso il basso, potrebbero nascere artefatti grafici sulle facce molto verticali degli oggetti, dovuti alle imprecisioni di shadow map a bassa risoluzione.
Un esempio concreto:
In quest'immagine, un cubo viene illuminato da una luce quasi perpendicolare (ruotata di 108.1 gradi sull'asse X). La luce ha Hard Shadows con Bias a 0
. Sul lato 'davanti', appare un artefatto grafico, una riga più scura sull'edge superiore.
Se portiamo il bias a 0.04
, l'artefatto scompare:
Questo perché il bias è un distacco fra l'ombra e l'edge che la proietta. Un distacco minimo (fra 0.01 e 0.05, di solito) che l'utente in generale non noterà, ma che può contribuire ad evitare brutture come quella sopra.
Altre impostazioni delle ombre sono Resolution (lo stesso che si trova in QualitySettings, solo che qui possiamo specificarlo per ogni luce) e, nel caso di ombre Soft, i parametri Softness e Softness Fade, che regolano la morbidezza dei bordi delle ombre (conviene fare un po' di prove per vedere la resa).
Niente ombre su mobile?
Bisogna ricordare che le ombre sono un effetto 'molto' pesante, spesso impossibile da avere su piattaforme come quelle mobile (ovvero, l'effetto funziona, ma riduce il framerate drasticamente). In questi casi, è meglio evitare di avere ombre in realtime per tutti gli oggetti e passare ad ombre ed illuminazione baked, come vedremo meglio nella lezione dedicata al Lightmapping.
Debuggare le ombre
Può capitare di attivare le ombre, ma di non vederle. Questo può dipendere da una miriade di fattori, alcuni già citati nella lezione, altri no. Vediamo i più comuni in una comoda checklist:
- Verificare che le ombre siano attive nell'opzione Shadows in
Edit > Project Settings > Quality
; - Verificare che la Shadow Distance sia sufficiente a coprire la distanza fra la camera e le ombre che si vorrebbero vedere;
- Verificare che la luce sia una Directional, e che sia l'unica con le ombre se parliamo di forward rendering (vedi sotto);
- Verificare che il materiale (quindi lo shader) che dovrebbe ricevere l'ombra supporti le ombre (alcuni shader cartoon non lo fanno ad esempio);
- Verificare che la Strength sia maggiore di
0
; - Verificare che l'oggetto che dovrebbe proiettare l'ombra non utilizzi uno shader della famiglia Trasparent.
Forward e Deferred rendering
Unity mette a disposizione tre modi di renderizzare la scena: vertex, forward e deferred. Mentre vertex è un metodo ottimizzato per vecchi hardware, forward e deferred sono due metodi attuali ed ognuno con i suoi pro e i suoi contro.
Ad esempio, è possibile avere anti-aliasing in forward rendering ma non in deferred. Nel deferred non si possono avere oggetti semitrasparenti.
I punti di forza del deferred però sono sull'illuminazione. Innanzitutto possiamo avere luci virtualmente infinite, laddove il forward rendering ha un numero massimo di luci “pixel”, che va impostato nei Quality Settings citati sopra, sotto Pixel Light Count
. Questo numero indica le luci da calcolare al pixel, tutte le altre sono da calcolare al vertice (quindi meno precise e realistiche). Il problema è che spesso su piattaforme mobile è meglio tenere questo numero a uno, massimo due.
Nel deferred rendering invece, possiamo avere più fonti di luce che influenzano tutte gli oggetti nel loro range. Questo apre infinite possibilità di illuminazione dinamica, con torcie, fari di automobile, esplosioni, ecc. che invece di avere un “effetto di luce” finto, possono essere delle vere e proprie luci che influenzano gli oggetti circostanti.
Inoltre, nel forward rendering solo una luce Directional può avere le ombre. Nel deferred tutti i tipi di luce (Spot, Directional, Point) possono generare ombre, ed il numero di luci che proiettano ombre non è limitato a uno.
Di conseguenza, il deferred rendering è preferibile quando disponibile (non è disponibile su tutte le piattaforme, e richiede Unity Pro), a meno che non sia necessario avere corpi semitrasparenti o anti-aliasing. Per maggiori dettagli sui rendering path, vedere qui.