La tecnica dello shadow volume è stata una dei più famosi utilizzi dello stencil buffer. Come si può capire dal nome, lo shadow volume permette di visualizzare le ombre all'interno della scena. La tecnica si basa su una semplice ma geniale intuizione. Prendiamo un oggetto, una ipotetica sfera.
L'oggetto avrà un cono d'ombra e tutti gli oggetti che si trovano al suo interno avranno su di loro l'ombra della sfera (che su un pavimento sarà un cerchio). Trasformiamo questo cono in una mesh e renderizziamo con cull normale (vedendo i lati esterni) e poi con cull invertito (vedendo i lati interni) sopra un piano che rappresenterò l'oggetto in ombra.
Nel primo caso vedremo che la mesh coprirà la zona che va dall'oggetto al piano mostrando un semicerchio convesso. Nel secondo coprirà la stessa zona lasciando vuoto un semicerchio concavo. Se facciamo la differenza tra le due zone ciò che rimarrà sarà proprio l'ombra dell'oggetto.
Questo fenomeno è proprio l'ideale per lo stencil. Sarà sufficiente creare il cono d'ombra dell'oggetto e renderizzarlo con i due cull. Nel primo incrementeremo lo stencil di 1, nel secondo lo decrementeremo. La zona che rimarrà con lo stencil uguale ad 1 sarà proprio la zona d'ombra.
La tecnica viene usata fin dall'introduzione dello stencil all'interno di DirectX ma nel tempo sono state introdotte sempre nuove features per accellerarne il rendering.
La prima caratteristica che useremo è il double stencil, introdotto dalla versione DirectX9. Con il double stencil potremo impostare due comportamenti diversi per lo stencil nel caso di cull normale o invertito.
DepthStencilState StencilShadow
{
DepthEnable = true;
DepthWriteMask = ZERO;
DepthFunc = LESS;
StencilEnable = true;
StencilReadMask = 0xFFFFFFFF;
StencilWriteMask = 0xFFFFFFFF;
FrontFaceStencilFunc = ALWAYS;
FrontFaceStencilPass = INCR;
FrontFaceStencilFail = Keep;
BackFaceStencilFunc = ALWAYS;
BackFaceStencilPass = DECR;
BackFaceStencilFail = Keep;
};
Con questa importazione si vede che il cono d'ombra verrà renderizzato incrementando lo stencil quando il cull è normale (FrontFace) e decrementando quando il cull è invertito (BackFace). Lo stencil verrà impostato ad Always, ossia il cono d'ombra ignorerà i valori attualmente sullo stencil buffer e soprattutto non scriverà sull ZBuffer impostando la depthWriteMask a Zero (altrimenti l'ombra sarebbe un oggetto solido nella scena). Inoltre per non far visualizzare il cono d'ombra useremo come valori dell'alphablending Zero sia a source che a destination blending (in pratica il colore dell'oggetto non influirà sulla scena).
Per visualizzare l'ombra basterà un piano nero (o del colore che preferiamo) che comparirà solo quando lo stencil è uguale ad 1.
Ora viene la parte interessante: la generazione del cono d'ombra. Fino alle DirectX9 eravamo costretti a creare il cono tramite CPU creando un vertex buffer e valorizzandolo ogni volta che l'ombra cambiava (nel caso dei personaggi animati ogni frame). Questo sicuramente era motivo di grande carico sul processore che lavorava per creare il cono.
In DirectX10 esiste però il geometry shader, questo significa che sarà lo shader a creare il cono. Il cono d'ombra sarà formato dall'estrusione dei bordi della mesh rispetto alla luce. Questo si riassume con una regola: se un triangolo è rivolto verso la luce ma uno dei triangoli adiacenti no, allora il lato in comune con questo triangolo deve essere proiettato. Con il geometry shader faremo proprio questo.
Per sapere se il triangolo è rivolto verso la luce si usa il dot product tra la normale del triangolo e la direzione della luce: se maggiore di 0 allora il triangolo è rivolto in quella direzione.
Il metodo pubblicato nell'esempio è il cosiddetto metodo ZPass, più veloce ma con un difetto: se la telecamera entra nel cono d'ombra allora non funziona più bene. Il metodo che risolve questo problema è l'algoritmo dello ZFail, detto anche Carmack Reverse (è stato John Carmack, creatore della serie Doom ad inventarlo) che necessità la creazione del cono d'ombra comprensivo degli estremi e con differenti regole per lo stencil. Magari ve lo mostrerò in uno dei prossimi demo ma è comunque presente già tra quelli Microsoft.
Ultima nota, potete ottimizzare la gestione delle ombre tramite l'output buffer. In pratica potete semplicemente generare le ombre salvandole nel buffer ed usandole finchè non cambiano. Molto spesso infatti gli oggetti rimangono immobili nella scena e non necessitano di aggiornare le ombre.
Vi lascio al demo.
Demo