\\ Home : Articoli : Stampa
Trapezoidal Shadow Mapping
Di RobyDx (del 29/07/2007 @ 08:45:25, in DirectX9, linkato 2118 volte)

Lo shadow mapping si basa su un concetto semplice ma geniale. Osservate l'immagine:

TSM_Desc

La scena viene renderizzata su una texture inquadrando la scena dalla telecamera. Il pixel shader restituisce per ogni pixel la sua distanza dalla luce. La matrice world_view_projection usata per renderizzare questa scena viene salvata ed utilizzata durante il rendering della scena. Moltiplicando la posizione del vertice per questa motrice si ottiene infatti la posizione del vertice come se fosse inquadrato dalla luce.

float4 pos_light=mul(position, shadow_matrix);

float4 omog_coord=pos_light/pos_light.w

float2 shadow_map_coordinate= omog_coord* BiasMatrix;

dove biasMatrix è la matrice che converte le coordinate omogenee (che variano da -1 a 1) in coordinate texture (da 0 a 1).

queste coordinate permettono per ogni pixel di leggere la shadowmap. La piramide ad esempio leggerà sulla punta non in ombra il valore grigio chiaro (quello corrispondente alla punta che si vede nella shadowmap), mentre nella parte in ombra quella grigio scura (corrispondente al parallelogramma). In pratica per ogni pixel noi avremo la distanza (la shadowmap contiene la distanza) che ha l'oggetto più vicino alla luce da quella direzione. Se la direzione del pixel è superiore a quella della shadowmap in quel punto significa che esiste un oggetto più vicino alla luce e quindi questo è in ombra. La tecnica così com'è sempre perfetta ma non lo è. Il primo problema è la precisione. Sulla superficie illuminata del cubo staremo al limite dell'uguaglianza (il valore della shadowmap che dovrebbe essere uguale alla distanza non sarà mai perfettamente uguale a causa della precisione delle variabili). Per questo basta fare in modo di aggiungere un valore di correzione per fare in modo che ci sia ombra quando la shadowmap è minore della distanza - qualcosa. Il vero problema però è la dimensione. Se dobbiamo inquadrare una scena molto grande potrebbe non bastare neanche una risoluzione 10000 x 10000 per avere una qualità accettabile. Tuttavia molto spazio è sprecato in quanto viene data la stessa risoluzione sia agli oggetti vicini che a quelli lontani. La soluzione viene nella tecnica trapezoidal shadow mapping. Queste prende ciò che è visibile nella scena dalla telecamera (che forma un volume a forma appunto di un trapezoide) e fa in modo che ciò che si trova vicino alla telecamera abbia una percentuale maggiore di spazio rispetto a ciò che sta lontano. In questo modo c'è un nettissimo miglioramento della qualità e si ottiene un algoritmo molto robusto ed adatto a tutte le situazioni. Il procedimento è comunque abbastanza complesso per chi non è molto pratico di trasformazioni matriciali. L'algoritmo funziona in questo modo:

  1. viene calcolato il trapezio 3D contenente la scena vista dalla telecamera
  2. il trapezio 3D viene proiettato sulla scena vista dalla luce ed approssimato ad un trapezio 2D
  3. il trapezio viene posizionato al centro della scena, ruotato ed ingrandito lungo la base minore
  4. l'intera scena viene quindi ridimensionata per occupare il quadrato con vertici 1,1  e -1,-1

La matrice che effettua queste trasformazioni viene usata per renderizzare la scena vista dalla posizione della luce e per posizionare la texture. Si ricorda che la texture ha posizione da 0 a 1 mentre la proiezione della scena ha posizione da -1 a 1 quindi sommare 1 e dividere per 2 e successivamente invertire la Y.

Il demo contiene una conversione dal C al C# del codice per ottenere la trapezoidal matrix in modo ottimizzato. Nel demo è possibile variare 3 parametri:

  • bias: ossia la percentuale di allontanamento dell'ombra dal modello per evitare artefatti nell'ombra
  • delta: parametro che corregge il posizionamento della scena
  • xi: permette di suddividere la qualità tra gli oggetti lontani e vicini

Il codice comprende una funzione che restituisce la trapezoidal matrix. A questa è necessario passare la view matrix, la projection matrix, i parametri delta e xi e 2 boundingbox receiver e caster. Il boundingbox è il parallelepipedo che contiene un oggetto rappresentato come angolo minore e maggiore. Receiver indica gli oggetti che ricevono l'ombra, caster quelli che proiettano (i 2 possono coincidere). La classe boundingbox genera i boundingbox a partire dalle mesh. Per problemi di approssimazione è preferibile aumentare leggermente la dimensione del box per evitare tagli alla scena. I parametri vanno impostati in base al tipo di scena che si vuole ottenere in modo da migliorare al massimo la qualità. Ad ogni modo la tecnica dipende sempre dalla dimensione della texture. E' preferibile quindi una texture di risoluzione 1024x1024. Il formato utilizzato è R32F che permette di avere valori molto grandi e con molta precisione.

Per maggiori dettagli consultate l'ideatore della tecnica http://www.comp.nus.edu.sg/~tants/tsm.html

Ringrazio infine pixel per il supporto per la collaborazione nello sviluppo dell'algoritmo e Vince per aver realizzato il modello 3D della scena. Vi lascio al demo.

Requisito Shader 2.0

Esempio C#