Se si usa un depthbuffer e si lavora con oggetti 3d, allora non ha importanza l'ordine con il qual si inviano gli oggetti al renderer. La cosa viene infatti gestita dal depthbuffer in questo modo:
per quanto sia difficile immaginarsi cosa succeda all'interno di un singolo frame in vvvv, e soprattutto cosa fa un renderer in un frame, dato che possiamo solo vedere il risultato finale del suo lavoro, e per molti frame al secondo, proviamo a dire che siamo a metà di un frame, e cioè che abbiamo già inviato degli oggetti al renderer che lui ha provveduto a disegnare.
Il depthbuffer attivato esegue calcoli per stabilire, per ogni pixel che contribuisce a disegnare la geometria degli oggetti, a quale profondità si trovi la corrispondente geometria 3d. Queste informazioni sulla profondità devono essere pensate in relazione alla telecamera, oppure pensarle come la componente Z di un punto sulla geometria nello "spazio della proiezione" (leggere it.EX9.Spaces)?.
A questo punto, quando il renderer disegna il prossimo oggetto sopra quello precedente, prima viene eseguito il vertexshader per ottenere tutta la geometria in questo spazio della proiezione (relativo alla telecamera), quindi il pixelshader deve essere eseguito solo per quei pixels che appartengono ad oggetti 3d che staranno davanti in relazione alla profondità memorizzata.
Alla fine non importa in che ordine si disegnano degli oggetti. Vedremo sempre quelle parti della geometria 3d che sono più vicine alla telecamera, cioè a noi.
Tutti i pixels insieme vengono anche chiamati colorbuffer. Così possiamo descrivere un renderer o una texture DX9 come una combinazione di colorbuffer e depthbuffer.
Matematicamente questa operazione (la neutralità dell'ordine di disegno) verrebbe definita commutativa.
Descriviamola formalmente:
showNearestOnly ( (C1, D1), (C2, D2) ) = showNearestOnly ( (C2, D2), (C1, D1) )
dove C e D si riferiscono alle informazioni sul colore e sulla profondità di un pixel.
Pensate a C1 e D1 come a queste informazioni sul pixel già memorizzate nel colorbuffer e nel depthbuffer e C2 e D2 come alle informazioni sulla geometria che sta passando in questo momento attraverso il rasterizer nello stesso pixel.
showNearestOnly: if D2 <= D1 then D=D2 C=C2 else D=D1 C=C1 (mantiene le informazioni su colore e profondità)
Fate attenzione che showNearestOnly (mostraSoloilpiùVicino, un nome inventato per riassumere l'operazione) funziona in questa maniera semplice solo se
Più avanti nella pagine ci sarà una prova di questa commutatività. Si mostra come questo sia vero in tutti i casi in cui D1 <> D2. Vale a dire: se si disegnano più oggetti con la stessa profondità, l'ultimo oggetto ad essere disegnato sarà quello visibile ( e quindi torna ad essere importante l'ordine di disegno).
ZWriteEnable ha un pin per gestire questo comportamento (Depth Bias).
Se non si lavora con un depthbuffer e si passa alla modalità Add per tutti gli oggetti (con il nodo GlobalRenderState (EX9) ), allora ci troviamo in un'altra situazione commutativa (non ha importanza l'ordine di disegno).
Sommare oggetti è sempre commutativo:
C1 + C2 = C2 + C1
Ora non abbiamo bisogno di D perché non usiamo il depthbuffer.
All'inizio questa era l'impostazione di default per vvvv.
Si può usare anche l'alpha in questa situazione:
C1*A1 + C2*A2 = C2*A2 + C1*A1
Ancora una volta senza depthbuffer.
C1 * C2 = C2 * C1
Tenete presente che quesi casi sono validi perché si assume che la modalità sia Multiply nel nodo Blend (EX9.RenderState). In questo caso alpha non è supportato.
Dovremmo avere uno sfondo chiaro ed anche mantenere le forme di un colore luminoso, almeno per iniziare.
Perché alpha sia supportato e venga mantenuta la commutatività, allora una possibilità potrebbe essere quella di fare un'operazione nel pixelshader, che codifica l'alpha nel colore in output (RGB) -in ogni caso ancora non si otterrebbe una texture trasparente nel renderer.
Ecco un suggerimento per il pixelshader:
se l'alpha in uscita dal pixelshader
dall'interno del pixelshader (ultima riga prima del return):
col.rgb = lerp(1, col.rgb, col.a); col.a = 1;
dato che l'output del pixelshader può essere ancora descritto solo con C2 (nessun alpha viene usato nella modalità Moltiplica) non dobbiamo dimostrare che sia ancora commutativo.
Ancora una volta questo è valido se tutti gli oggetti vengono disegnati nella modalità Moltiplica del nodo Blend (EX9.RenderState).
Abbiamo già descritto il pixel nel colorbuffer con C1, A1 e l'output del pixelshader come C2 e A2.
Il nodo Blend (EX9.RenderState Advanced)si riferisce al colorbuffer come destinazione ed al pixelshader come risorsa.
L'output dell'operazione di blending può essere descritta così:
C = C1 * DestinationBlendMode + C2 * SourceBlendMode
Source Blend Mode = SrcAlpha Destination Blend Mode = InvSrcAlpha
C = C1 * InvSrcAlpha + C2 * SrcAlpha = C1 * (1-A2) + C2 * A2
Nel primo capitolo abbiamo trattato solo forme opache (A2=1). Questo può essere quindi semplificato con:
C = C2
per tutti quei pixels che hanno passato il test sulla profondità...
Source Blend Mode = SrcAlpha Destination Blend Mode = One
C = C1 * One + C2 * SrcAlpha = C1 + C2*A2
in questo esempio A1 è ancora implicito in C1, ed eccolo invece in forma esplicita
C = add(add(background, shape1), shape2) = add(background + c1*a1, shape2) = background + c1*a1 + c2*a2 = background + c2*a2 + c1*a1
Source Blend Mode = DestColor Destination Blend Mode = Zero
C = C1 * Zero + C2 * DestColor = C2 * C1
Se si vuole usare la trasparenza e il blending di default, allora dovremmo prima disegnare tutte le forme opache, per le quali l'ordine non conta.
A questo punto dovremmo disegnare le forme trasparenti sopra (il depthbuffer garantirà che vengano disegnate solo quelle più vicine).
Questo passaggio è necessario perché il blending di default non è commutativo per A2<>1.
In ogni caso dovremo assicurarci che se disegniamo più forme trasparenti, queste siano ordinate da dietro (valori maggiori per Z nello spazio proiezione) a davanti (valori minori per Z).
La cosa migliore sarebbe farlo per ogni pixel dell'immagine risultante.
Questo processo viene chiamato depth peeling, ed in un certo senso è dispendioso di risorse ed impegnativo.
Ci sono delle scritte a questo proposito, ma pare che nessun utente di vvvv si sia mai confrontato con questa cosa. Esiste anche un documento (qui) a proposito di come riuscire nell'impresa con più render targets, e sembra molto promettente.
Se tutte le forme trasparenti sono contenute in un solo grande spread, e queste non si intersecano tra loro, allora è possibile scegliere tutte le slices sulla base della profondità Z e disegnarle da dietro a davanti.
Abbiamo quindi bisogno di moltiplicare gli assi/le posizioni delle forme per ViewProjection Matrix (che sarebbe View*Projection). Se si usa il modulo Camera (Transform Softimage) basterà usare allora l'output ViewProjection Transform.
La moltiplicazione può essere fatta con * (3d Vector) (vedi il nodo:multiply-(3d-vector) ).
Allora ordineremo il valore Z in uscita con il nodo Sort, invertiremo, Reverse, i primi indici in modo da avere le forme ordinate in ordine decrescente sul valore di Z (da lontano a vicino), quindi useremo un nodo GetSlice (Node) per ordinare le trasformazioni. Tenete presente che dovranno essere ordinati anche altri parametri come colori o textures inviati allo shader per avere un risultato armonico.
Anche solo una semplice forma come una sfera trasparente può causare problemi.
Ha facce anteriori e posteriori e se si vuole disegnarla da dietro a davanti, dobbiamo tener conto che prima si disegna la parte posteriore (interiore) e poi quella anteriore.
Si può farlo usando uno spread per un nodo Cull.
Usare la modalità Clockwise per disegnare la parte posteriore/interna e la modalità per disegnare la parte anteriore, che si affaccia alla telecamera.
Abbiamo affermato che
showNearestOnly ( (C1, D1), (C2, D2) ) = showNearestOnly ( (C2, D2), (C1, D1) )
rendiamo ancora più chiaro quale sia il risultato di showNearestOnly:
(C, D) = showNearestOnly ( (C1, D1), (C2, D2) )
giusto un paio di colori in più e un nuovo valore profondità per quel pixel.
Questo è quanto ci offre la parte sinistra dell'equazione:
showNearestOnly ( (C1, D1), (C2, D2) ) = ( if D2 <= D1 then D=D2 C=C2 else D=D1 C=C1 )
la parte destra:
showNearestOnly ( (C2, D2), (C1, D1) ) = ( if D1 <= D2 then D=D1 C=C1 else D=D2 C=C2 )
trasformiamo la parte destra finché non sarà uguale alla sinistra:
= ( if D2 >= D1 then D=D1 C=C1 else D=D2 C=C2 )
abbiamo appena riformulato l'argomento "if" senza cambiare la sua semantica
= ( if D2 < D1 then D=D2 C=C2 else D=D1 C=C1 )
abbiamo imposto un NOT all'argomento della dichiarazione "if" ed allo stesso tempo capovolto le parentesi di "then" ed "else"; anche in questo caso non è stata cambiata la semantica .
= showNearestOnly ( (C1, D1), (C2, D2) ) for all D1 <> D2
Se paragoniamo questa espressione con l'equazione a sinistra, troveremo che sono uguali per ogni D1<>D2...
anonymous user login
~2d ago
~2d ago
~9d ago
~11d ago
~13d ago
~16d ago
~16d ago
~24d ago
~30d ago
~30d ago