Il Depth buffer è la superficie addetta alla gestione della profondità. Ogni pixel che sta per essere disegnato a video confronta la sua profondità restituisce un colore ed una profondità. Questo valore viene confrontato con il depth buffer, se minore sostituisce il valore e scrive il colore del pixel sul back buffer.
Normalmente il valore di distanza massima è 1 e passano questo controllo (chiamato depth test) solo i pixel con profondità minore (ricordo che la profondità è letta dopo che è stata applicata la projection matrix che la porta ad essere compresa tra 0 ed 1). In DirectX si può comunque variare questa regola ed utilizzare confronti diversi (ad esempio si può usare un uguaglianza in modo che il rendering successivo vada a video solo per distanze uguali a quelle già sullo schermo, utili per la tecnica chiamata ZOnly).
Il depth buffer però è utilizzato anche per altri scopi. Durante l’inizializzazione del depth buffer viene indicato il formato di precisione ed accanto a formati come D16 e D32 (ossia 16 e 32 bit di precisione) ne compaiono altri come D24S8. Questo formato ad esempio indica che 8 bit saranno dedicati allo stencil buffer.
Lo stencil buffer è, come il depth buffer, una superficie di controllo che impedisce a certi pixel di essere mandati a video ma a differenza di questo, lo stencil non utilizza la profondità come confronto ma delle regole che noi andremo a definire.
Facciamo un esempio.
Vogliamo creare un effetto in cui al centro della scena compare un cerchio contenente il livello successivo del nostro ipotetico gioco che va a sostituire quello precedente. Quello che noi vorremmo è che la prima scena sia renderizzata fuori da questo cerchio, l’altra all’interno.
Tramite lo stencil noi faremo questo: renderizzeremo un cerchio (o qualsiasi figura desideriate) ordinando di valorizzare ad 1 lo stencil buffer nella zona in cui i pixel sono stati tracciati; successivamente manderemo a video le due scene escludendo in una i pixel che compaiono dove lo stencil è uguale ad 1, nell’altra quella in cui i pixel sono diversi da 1.
Questo effetto è chiamato maschera.
Lo stencil si utilizza per molti effetti altri effetti tra cui ombre, fumo e luci volumetriche.
Le possibilità dello stencil sono molte e si basano su semplici regole.
Ogni volta che un pixel arriva al pixel shader viene confrontata la sua distanza con quella dei pixel già tracciati a video. Se il pixel è più vicino viene mandato a video allora significa che ha superato il depth test, altrimenti che ha fallito. Il caso di fallimento viene chiamato depthFail. Se il pixel supera il depth test allora viene confrontato con la funzione di stencil. Questa funzione confronta il valore dello stencil sul buffer con uno a nostro piacimento che impostiamo prima del rendering e che chiamiamo StencilRef. I tipi di confronti sono gli stessi del depth test (quindi maggiore, minore, uguale, etc). A questo punto si procede all’aggiornamento.
Sono 3 i casi
- Lo stencil test viene superato
- Lo stencil test fallisce
- Lo stencil test viene superato ma fallisce il depth stencil
Per ognuno può essere applicata una regola indipendente. Ad esempio si può decidere che se il test passa si incrementi il valore dello stencil e che si decrementi quando fallisce. Il valore per cui si incrementerà sarà quello di stencilRef. Quindi se noi impostiamo come operazione “sostituisci”, lo stencil assumerà il valore dello stencilRef in quel punto, così come incrementerà di stencilRef con operazione “aumenta”. In directX9 e 10 lo stencil può essere gestito indipendentemente sia per le superfici con cull diretto (che guardano verso di noi) che con cull invertito.
Ulteriore libertà è data dall’utilizzo di maschere bit a bit per modificare i valori letti e scritti.
Una maschera bit a bit è un modo per prendere solo un certo gruppo di bit da un numero. Facciamo un esempio. Il numero 11 in binario vale 1011 ma noi vogliamo utilizzare come confronto solo il primo e l’ultimo valore. Moltiplichiamo il valore per 0001
1001 x 0001 = 0001
Se fosse stato 8 sarebbe stato 1000 x 0001 = 0000. Questo è un semplice metodo per sapere se il numero è pari o disparo. I flags che vedete spesso in directX si basano sullo stesso principio
Esempio 2 flags di valori 4 e 8 ossia 100 e 1000. Se si fa l’OR (che equivale al più) otteniamo 1100. Per sapere se quel valore contiene il flags 4 basta fare 1100 x 0100 = 100 ossia il flags 4. Ovviamente il trucco funziona solo per potenze di 2.
Lo stesso trucco lo potete usare con lo stencil. Ci sono come per il rasterizer ed il blending 2 metodi per impostare le proprietà depth e stencil: da shader e da codice.
Da codice si utilizza la funzione CreateDepthStencilState del device per creare una classe di tipo ID3D10DepthStencilState. La funzione prende in input una struttura di tipo D3D10_DEPTH_STENCIL_DESC che contiene quanto descritto finora.
DepthEnable : attiva il depth test (con false non c’è la gestione della profondità)
DepthWriteMask : la maschera bit a bit per la profondità, di default a D3D10_DEPTH_WRITE_MASK ossia tutti i bit sono usati
DepthFunc : tipo di confronto, di default less. Utilizza l’enum D3D10_COMPARISON_FUNC
StencilEnable : con true attiva lo stencil
StencilReadMask : maschera usata in lettura
StencilWriteMask : maschera usata in scrittura
Ci sono all’interno della struttura altre due strutture di tipo D3D10_DEPTH_STENCILOP_DESC chiamate frontFace e backFace per descrivere il comportamento dello stencil per i due tipi di cull. Queste contengono
StencilFailOp : operazione in caso fallisca lo stencil test
StencilDepthFailOp : operazione in caso fallisca il depth test ma abbia successo lo stencil test
StencilPassOp : operazione in caso abbia successo lo stencil test
StencilFunc : funzione di confronto di tipo D3D10_COMPARISON_FUNC
Le funzioni di comparazioni sono le seguenti
D3D10_COMPARISON_NEVER
D3D10_COMPARISON_LESS
D3D10_COMPARISON_EQUAL
D3D10_COMPARISON_LESS_EQUAL
D3D10_COMPARISON_GREATER
D3D10_COMPARISON_NOT_EQUAL
D3D10_COMPARISON_GREATER_EQUAL
D3D10_COMPARISON_ALWAYS
Quelle di modifica dello stencil
D3D10_STENCIL_OP_KEEP : mantiene il valore
D3D10_STENCIL_OP_ZERO : lo porta a zero
D3D10_STENCIL_OP_REPLACE : lo sostituisce con il valore stencilRef
D3D10_STENCIL_OP_INCR_SAT : incrementa di stencilRef ma non supera il massimo
D3D10_STENCIL_OP_DECR_SAT : decrementa di stencilRef ma non scende mai sotto a zero
D3D10_STENCIL_OP_INVERT : inverte il valore
D3D10_STENCIL_OP_INCR : incrementa di stencilRef. Nel caso superi il massimo ritorna a zero
D3D10_STENCIL_OP_DECR : decrementa di stencilRef. Nel caso scenda sotto lo zero viene impostato al Massimo valore possible.
Il valore stencilRef viene passato al device quando si imposta lo stencil tramite l’istruzione OMSetDepthStencilState.
Da shader si utilizza la struttura DepthStencilState contenente
DEPTHENABLE
DEPTHWRITEMASK
DEPTHFUNC
STENCILENABLE
STENCILREADMASK
STENCILWRITEMASK
FRONTFACESTENCILFAIL
FRONTFACESTENCILZFAIL
FRONTFACESTENCILPASS
FRONTFACESTENCILFUNC
BACKFACESTENCILFAIL
BACKFACESTENCILZFAIL
BACKFACESTENCILPASS
BACKFACESTENCILFUNC
La vedete sempre nei miei esempi per impostare correttamente il depth test. I valori sono gli stessi della struttura da codice C++ eliminando le desinenze (esempio D3D10_COMP_NEVER diventa NEVER).
Vi lascio al mio demo che mostra tramite stencil il numero di pixel renderizzati in ogni punto. Questo si ottiene incrementando lo stencil per ogni pixel che va a video. Successivamente renderizziamo piani con colori diversi a seconda di quale valore vogliamo vedere ed impostiamo lo stencil in modo che passi il test solo quando il valore di stencil è quello che vogliamo.
Demo
I commenti sono disabilitati.