Come abbiamo visto il backbuffer è una grossa superficie (diciamo una matrice grande quanto lo schermo) che contiene l'immagine che stiamo creando man mano che disegniamo cose; ogni volta che disegniamo qualcosa Direct3D non fa altro che cambiare il valore numerico dei pixel interessati dal disegno con quelli della nuova immagine. Il tutto quindi si riduce a numeri. Abbiamo anche visto che la seconda superficie è il depth buffer (detto anche Z buffer) responsabile delle profondità: quando viene disegnato un modello Direct3D controlla sullo Z buffer la profondità di ogni pixel e solo se la profondità è minore (il pixel del modello 3D si trova avanti a quelli precedentemente disegnati) fa modificare sia il backbuffer che il depth buffer. Lo stencil buffer è la terza superficie speciale di DirectX ed in qualche modo opzionale visto che si potrebbe benissimo vivere senza. Per spiegare il funzionamento farò un esempio.
Immaginate di renderizzare un cubo. Nelle zone interessate dal rendering vengono modificati i pixel del backbuffer ma possiamo tenere in memoria il fatto che in quei punti è stato disegnato un oggetto. In questo caso diciamo allo stencil buffer di memorizzare questo fatto. Ora abbiamo sulla superficie stencil dei valori che ci indicano dove è stato disegnato qualcosa oppure no. Abbiamo creato una maschera. Finchè non puliamo lo stencil possiamo dire a DirectX di non disegnare i quei punti o di disegnare solo lì.
Osservate la figura
Nella prima vignetta disegno degli oggetti e "sporco" lo stencil. Nella seconda non uso lo stencil e la figura copre completamente quelle sottostanti. Nella terza invece dico al device di non disegnare dove lo stencil ha valore diverso da zero. La figura rimane quindi forata in quei punti che vengono detti maschera. Lo stencil buffer serve a questo, a creare maschere.
Possiamo modificare i valori dello stencil in moltissimi modi ed ovviamente non esiste solo "bianco e nero", ma tantissimi valori che possiamo usare per creare molte maschere sullo schermo. Se ad esempio chiedo allo stencil buffer di aumentare i valori ogni volta di 1 accadrà che dove vengono tracciati più pixel il valore sarà più alto.
Le cose che si possono realizzare sono praticamente infinite e molti degli effetti che vedete (soprattutto per console) usano spesso lo stencil.
Visualizzare scene all'interno di figure irregolari (scena di quake 3 in cui si vedono i personaggi nella scritta "Quake III") creare split screen irregolari ombre super realistiche (prossimi tutorial) filtri (si potrebbe inquadrare un scena con un visore a raggiX, lo stencil permetterebbe di visualizzare il retro dei muri salvati nello stencil) riflessioni
Questi sono solo un esiguo numero destinato a decuplicarsi quando si cominciano ad usare anche vertex e pixel shader (esempio il depth of field). Codice
Come per lo Z buffer lo stencil va creato in fase di inizializzazione.
AutoDepthStencilFormat = DepthFormat.D24S8
Ovviamente dopo il controllo della compatibilità.
In questo caso (il più compatibile) viene creato uno Z buffer a 24bit ed uno stencil a 8 bit (totale 32 bit). Dal punto di vista matematico significa che lo stencil supporta 2^8 valori ossia 256 (da 0 a 255). Altri formati sono
DepthFormat.D24X4S4
DepthFormat.D15S1
DepthFormat.D24SingleS8
Queste sono scarsamente compatibili.
Usate questa opzione nella creazione del device (sia fullscreen che in finestra). Ora avete anche lo stencil buffer ma non farà nulla finche non lo iniziate ad usare. Innanzitutto
device.Clear(ClearFlags.Target Or ClearFlags.ZBuffer Or ClearFlags.Stencil, Color.Blue, 1, 0)
Osservate le opzioni che puliscono anche lo stencil. Potete ovviamente non pulire lo stencil o pulire solo questo, a seconda di cosa vi serve. Per attivare lo stencil usate
device.RenderState.StencilEnable = True
mettetelo a false per disattivare l'effetto.
device.RenderState.StencilFunction=Compare.Always
device.RenderState.ReferenceStencil = 0
Questa opzione (always a default) serve a stabilire come si devono comportare i pixel. Always significa che il pixel verrà disegnato sempre, indipendentemente dal valore dello stencil. Reference è il valore usato per il paragone. Altre opzioni
Always =accetta sempre il nuovo pixel
Never =non accetta mai il nuovo pixel
GreaterEqual =solo dove il valore dello stencil del pixel è minore o uguale al reference
NotEqual =solo dove lo stencil ed il reference hanno valore differente
Greater = solo dove il valore dello stencil del pixel è minore al reference
LessEqual =solo dove il valore dello stencil del pixel è maggiore o uguale al reference
Equal =solo dove reference e stencil sono uguali
Less =solo dove il valore dello stencil del pixel è maggiore al reference
Tramite queste 2 opzioni i pixel dell'oggetto vengono (o non vengono) disegnati. Quando il pixel viene disegnato si dice che lo stencil test è passato altrimenti fallisce. Queste 2 possibilità (che possono verificarsi contemporaneamente per uno stesso oggetto) permettono di modificare i valori dello stencil
device.RenderState.StencilPass = StencilOperation.Keep
operazione eseguita quando il test passa
device.RenderState.StencilFail = StencilOperation.Keep
operazione quando il test fallisce
In entrambi i casi le operazioni da scegliere sono
Zero :in quel pixel lo stencil va a zero
Replace :il valore viene sostituito con quello del reference
Decrement :decrementa lo stencil del valore del reference. Se scende sotto zero il valore sale al massimo.
Increment :incrementa lo stencil del valore del reference. Se sale sopra il massimo torna a zero.
Invert :inverte il valore dello stencil (max - valore)
DecrementSaturation :decrementa lo stencil del valore del reference. Non scende sotto lo zero
IncrementSaturation :incrementa lo stencil del valore del reference. Non sale oltre al massimo.
Keep :non modifica il valore
Attenzione! Se un modello 3D è composto da più superfici visibili (ad esempio un cubo dietro un altro) effettua più operazioni, una per ogni pixel che viene rivolto verso lo schermo (anche se nascosto da altri pixel più avanti). Di conseguenza avrà più incrementi ma solo se la zona più in profondità viene renderizzata prima. Fate prove e vedrete.
Altre funzioni
device.RenderState.StencilZBufferFail
Come pass e fail ma accade se lo stencil passa ma lo Z no (per oggetti dietro ad altri).
StencilMask :maschera per lo stencil
StencilWriteMask :maschera per il referenze
Valore uguale a FFFFFFFF (valore esadecimale corrispondente a tanti uno: 1111 1111 1111 1111 1111 1111 1111 1111).
Cosa significa? Serve per fare delle operazioni di maschera binaria (usata spesso per i codici IP e per altre cose). Funziona così:
il numero 14 è in binario uguale a
1110
Con il numero 5 come maschera (che è uguale a 101) si effettua il prodotto carattere per carattere
1110 101 ------- 1100 Si usano per cambiare il valore dello stencil e del reference senza però modificarli definitivamente. Più che altro per poter trattare tutti insieme valori differenti. Ad esempio (valori fuori misura e in gruppi di 256)
173.151 con maschera 256.0 significa che tutti i valori da 173.0 a 173.151 vengono considerati come 173.0. Con numeri più piccoli potete fare in modo che valori compresi ad esempio tra 4 e 16 siano considerati uguali a 4 e quindi usare la stessa proprietà per tutti senza dover andare ad eseguire lo stencil per tutte (se ad esempio vi serve come maschera solo i valori tra 4 e 8 mica potete mettere 4<8!).
Cercate di afferrare il concetto e di applicarlo ai valori dello stencil (molti minori).
Esempio di maschera
In questo esempio vi dirò come creare una maschera semplice (un poligono in primo piano che non verrà disegnato ma che creerà una zona in cui disegnare).
1) disegnate l'oggetto maschera con opzione always per stencil function pass uguale a replace e reference uguale ad un intero qualsiasi diverso da zero.
2) fate il clear ma non dello stencil (solo backbuffer e Zbuffer)
3)con stencil function equal, reference uguale a quel valore intero scelto all'inizio e function uguale a keep (per non modificare lo stencil) renderizzate la scena. L'oggetto verrà disegnato solo dove c'era la maschera.
E se non voglio usare un clear? Allora sostituite la 1 con un never per stencil function e un replace nel Fail (l'oggetto non viene disegnato quindi fallisce il test dove dovrebbe essere disegnato). Non fate clear ed eseguite il 3: stesso risultato ma senza il clear e quindi più veloce.
Altrimenti?
Potete usare sempre il primo metodo ma modificare l'alphablending in modo da non disegnare nulla ma in realtà lo stencil lo considererà disegnato.
Per utilizzare bene lo stencil ci vuole un pò di fantasia ed entrare nell'ottica delle somme e delle sottrazioni delle immagini. Vi lascio all'esempio che mostra la complessità dello stencil nei modelli (ossia quante volte incrementa lo stencil) e un esempio di maschera (il modello 3D fa da maschera). Fate pratica e vi impadronirete. Ultimo avvisi:
attenti alla compatibilità ma anche alla pesantezza. Il depth è a 32 bit invece dei classici 16 e lo stencil attivato consuma risorse.
Esempio VB.Net
Esempio C#