• Non ci sono risultati.

la Classe VR3ShadowController

Una nuova funzionalità inserita nella VR3Lib che non era prevista nella vecchia VRLib è il supporto integrato alla generazione di ombre proiettate da oggetti virtua- li. Abbiamo discusso di come ottenere questo eetto da un punto di vista teorico nel capitolo 3 e abbiamo visto una possibile realizzazione dell'algoritmo EVSM basata su shader GLSL in sez. 4.7. Gli shader visti nel capitolo 4 sono una semplicazione di quelli eettivamente costruiti per la proiezione di ombre nella VR3Lib, ma la struttura è simile e in ogni caso gli shader program che ne derivano vengono gestiti dal VR3ShaderManager.

Per attivare e congurare il servizio di shadow mapping, l'utente della VR3Lib deve creare un'istanza della classe VR3ShadowController e renderla attiva. Fin- tanto che nessun oggetto di tipo VR3ShadowController risulta attivo, non si ha la proiezione di ombre nella scena e il rendering avviene in un singolo passo guidato dal- le chiamate di disegno svolte dall'utente all'interno di un blocco Begin()...End() per la particolare scena. La creazione di uno shadow controller corrisponde anche alla sua attivazione (si rende tale controllore quello attivo). Un'applicazione può gestire diversi controllori durante il rendering, ma solamente uno di essi sarà attivo ad ogni istante.

Lo shadow controller attivo determina i parametri di scena che riguardano la proiezione delle ombre, in particolare:

• Denisce l'insieme degli oggetti (istanze di VR3Obj) che proiettano ombre (casters);

• Denisce l'insieme degli oggetti (istanze di VR3Obj) che ricevono ombre proiettate (receivers);

• Denisce un insieme di sorgenti d'ombra, e per ognuna di esse un insieme di parametri per la generazione delle ombre proiettate (per ogni sorgente d'ombra attiva verrà generata una shadow map da utilizzare durante il rendering della scena).

Visto come realizzare l'algoritmo EVSM in sez. 4.7, si pone subito il problema di dover eettuare due passi di rendering per tutti gli oggetti che devono proiettare ombre: prima di tutto bisogna disegnare tali oggetti all'interno delle shadow map (una per ogni sorgente d'ombra), dopo di che bisogna eettivamente renderizzarli nella nestra durante il passo di rendering della scena. Per facilitare le operazio- ni dell'utente, vorremmo evitare una chiamata esplicita alla VR3Obj::Draw() per ogni oggetto che deve essere disegnato all'interno di una shadow map. La soluzio- ne adottata è quindi quella di costruire un oggetto shadow controller che si occupi di eettuare automaticamente i passi di rendering addizionali all'inizio del rende- ring della scena (alla chiamata di una funzione Begin() tra quelle a disposizione della scena). Questo procedimento garantisce trasparenza in fase di rendering, ma è necessario sapere quali oggetti vanno disegnati all'interno delle shadow map pri- ma della eventuale invocazione della funzione VR3Obj::Draw() da parte dell'utente nel blocco Begin()...End(); per questo motivo ogni shadow controller mantiene l'insieme degli oggetti caster.

CAPITOLO 5. STRUTTURA E FUNZIONAMENTO DELLA VR3LIB 157 Volendo utilizzare tutti gli oggetti della scena sia come caster che come recei- ver, la situazione tipica sarà quella in cui ogni oggetto denito fa parte sia del- l'insieme dei caster che di quello dei receiver. In eetti quando si crea un oggetto VR3ShadowController è possibile congurarlo come `automatic' e in tal caso la ge- stione dei due insiemi di cui sopra avviene automaticamente disegnando nella shadow map tutti gli oggetti come verrebbero renderizzati nella scena chiamando la Draw() su tutti gli oggetti senza padre (dal punto di vista della sorgente d'ombra). In mo- dalità automatica dunque gli insiemi di caster e receiver verranno automaticamente riempiti con tutti gli oggetti non gli di altri oggetti deniti dopo la creazione dello shadow controller. I gli vengono intesi come estensioni dell'oggetto padre quando disegnati come conseguenza del disegno del padre: quando un oggetto è un caster o un receiver, anche i suoi gli si comporteranno come tali durante il suo disegno.

Un oggetto VR3ShadowController può anche venir congurato in modalità `manual' e in tal caso l'utente dovrà specicare singolarmente quali oggetti aggiun- gere all'insieme dei caster e a quello dei receiver. Questo complica l'interfaccia dal punto di vista dell'utente, ma consente una certa essibilità nell'utilizzo del servizio di shadow mapping che può anche portare ad un miglioramento nelle prestazioni (evitando ad esempio di aggiungere all'insieme dei ricevitori oggetti che sicuramente non verranno investiti da ombre proiettate per come sono posizionati i vari elementi della scena).

Gli insiemi di oggetti caster e receiver vengono quindi utilizzati come segue: • Insieme di oggetti caster (VR3ShadowController::m_casters)

La funzione membro Draw() di questi oggetti verrà invocata una volta per ogni shadow source attiva quando si disegnano le shadow map, la chiamata si propaga agli oggetti gli indipendentemente dal fatto che questi siano o meno nell'insieme caster e quindi anche gli oggetti gli proietteranno un'ombra. In modalità automatica non vi saranno oggetti gli nell'insieme dei caster (normalmente infatti non si desidera disegnare direttamente un oggetto glio). • Insieme di oggetti receiver (VR3ShadowController::m_receivers)

Durante il rendering della scena nale, dopo aver prodotto le shadow map ltrata, quando un oggetto viene disegnato, si controlla se fa parte dell'insieme dei ricevitori. In caso aermativo si attiva un ag che viene propagato ai gli causando anche per essi la ricezione di ombre proiettate. In modalità automatica non vi saranno oggetti gli nell'insieme dei receiver: l'aggiunta di un oggetto glio a questo insieme è infatti utile solamente nel caso in cui

vogliamo disegnare direttamente nella scena un oggetto glio bypassando il legame di parentela (e normalmente non è così).

Ogni shadow controller gestisce un insieme di sorgenti d'ombra. Ognuna di queste sorgenti può venire attivata e disattivata singolarmente e causa (se attiva all'inizio del rendering) la costruzione e il ltraggio di una EVSM da usare durante il disegno eettivo della scena. All'interno di tutte le shadow map generate in questo modo vi saranno gli oggetti visibili facenti parte dell'insieme dei caster (e relativi gli). Ogni sorgente d'ombra che si vuole utilizzare deve venire inizializzata e attivata prima di produrre eettivamente l'eetto sperato sugli oggetti virtuali. In fase di inizializza- zione di una sorgente d'ombra bisogna specicare un certo numero di parametri che possono essere raggruppati come segue:

• Parametri relativi al volume di vista della sorgente d'ombra

Inuenzano la regione di spazio entro la quale sarà possibile osservare la pro- iezione di ombre da parte degli oggetti virtuali correttamente registrati come caster e la ricezione delle ombre da parte dei receiver.

• Parametri relativi alla texture (shadow map) generata

La dimensione della mappa generata in termini di risoluzione è fondamentale per regolare le prestazioni dell'algoritmo: con una mappa troppo piccola si ottengono buone prestazioni ma le ombre possono sembrare troppo spigolo- se o tremolanti (ickering), mentre con una mappa troppo grande il tempo necessario per il ltraggio può risultare inaccettabile.

• Parametri relativi all'algoritmo EVSM

Con questo insieme di parametri si congura l'algoritmo per ottenere ombre più o meno realistiche, e si può avere una notevole inuenza sulle prestazioni. Questi parametri sono stati presentati in sez. 4.7 e si possono riassumere nella lista che segue:

 Filter size (dimensione del ltro gaussiano utilizzato).  Intensity (intensità dell'ombra generata)

 Minimum variance (varianza minima della tecnica EVSM)

 Bleeding reduction factor (fattore di riduzione del light bleeding)  Depth scale (costante k applicata all'esponente, vedi algoritmo 3.6)

CAPITOLO 5. STRUTTURA E FUNZIONAMENTO DELLA VR3LIB 159 Naturalmente è possibile alterare i parametri di una sorgente d'ombra anche dopo l'inizializzazione (ad esempio se si vuole cambiare il raggio di ltraggio del ltro di blur gaussiano) utilizzando opportune funzioni dell'oggetto VR3ShadowController. La classe VR3ShadowController gestisce anche una serie di opzioni per il rendering delle shadow map:

• backface culling (per poligoni disegnati nella shadow map),

• shadow source view frustum culling (per evitare il disegno nella shadow map di oggetti completamente esterni al volume di vista della shadow source, che verrebbero comunque scartati dalla pipeline OpenGL nello stadio di primitive clipping),

• shadow source culling (per evitare il disegno di una shadow map e il ltraggio della stessa nel caso in cui gli eetti della sorgente d'ombra corrispondente non fossero visibili in base alla posizione e orientamento del volume di vista dell'osservatore).

La creazione di diversi shadow controller può essere desiderabile qualora l'illumina- zione (e di conseguenza le ombre) cambi istantaneamente ad un certo punto durante l'esecuzione dell'applicazione. In tal caso si possono modicare tutti i parametri di proiezione delle ombre in modo molto rapido attivando un diverso shadow controller congurato in precedenza. È importante notare inoltre che alcune sorgenti d'ombra possono essere inizializzate ma mantenute disattive no a che non sono eettivamen- te necessarie (in questo modo ci si risparmia il costo di inizializzazione durante il ciclo di rendering, che è piuttosto elevato perché si devono creare i FBO e le texture necessarie per la shadow map e il ltraggio della stessa).

Nel caso in cui ogni frame si componga di diversi blocchi Begin()...End() è possibile calcolare le shadow map solamente per il primo blocco e riutilizzarle per il blocchi successivi tramite il ag VR3BEGFL_KEEPSHADOWMAPS da fornire alla funzione utilizzata per entrare nel blocco (una delle funzioni Begin() della classe VR3Scene). In g. 5.6 è riportato un diagramma UML che illustra la struttura della classe VR3ShadowController.

Figura 5.6: Classe VR3ShadowController

In gura notiamo la funzione membro privata DrawShadowMaps() della classe VR3ShadowController. Questa funzione viene chiamata durante l'ingresso in un blocco Begin()...End() nel caso in cui vi sia uno shadow controller attivo e causa il disegno e il ltraggio di tutte le shadow map necessarie (una per ogni sorgente d'ombra attiva). Gli insiemi degli oggetti caster e receiver sono membri dati di tipo std::unordered_set: questo tipo identica un contenitore disordinato ad accesso rapido (basato su funzione hash). La funzione hash nel nostro caso opera su dei puntatori; non entriamo nel dettaglio delle sue operazioni, ma è suciente sapere che la funzione prevista di default è idonea nel caso di puntatori. Il contenito- re unordered_set è incluso nel nuovo standard del linguaggio di programmazione C++ `C++0x' ed è già disponibile su una grande varietà di sistemi operativi e compi- latori. Si utilizza un contenitore ad accesso rapido per l'insieme dei ricevitori perché il controllo di appartenenza di un oggetto a questo insieme avviene durante il rende- ring. Per l'insieme dei caster invece non sarebbe necessario un contenitore di questo tipo, ma si è scelto di utilizzare comunque un approccio con hashing per consentire

CAPITOLO 5. STRUTTURA E FUNZIONAMENTO DELLA VR3LIB 161 ricerche rapide da parte dell'utente della libreria anche durante il rendering.