» it.Trasparenza
This site relies heavily on Javascript. You should enable it if you want the full experience. Learn more.

it.Trasparenza

English

Traduzione parziale. Una traduzione completa potrebbe richiedere alcune ore. Attendere prego...

Situazioni in cui l'ordine di disegno non ha importanza

Depthbuffer, Colorbuffer, Forme Opache Miscelate Normalmente

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

  • manteniamo la modalità di blend di default (o connettiamo un nodo  Blend (EX9.RenderState) con il DrawMode impostato su Blend)
  • si usa la modalità default di comparazione di Z (il depthbuffer nel renderer), oppure usando un nodo ZWriteEnable (EX9.RenderState) con la Compare Function impostata su LessEqual).
  • non si lavora con l'alpha.

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).

Modo Aggiungi, Add. Un'altra situazione commutativa

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

Modalità Moltiplica, Multiply. Un'altra situazione commutativa

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.

Moltiplica con Alpha. Un'altra situazione commutativa

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

  • è 0 allora questo non dovrebbe influenzare il colore del pixel già nel colorbuffer. output 1 (che è bianco; quando viene moltiplicato con il pixel nel colorbuffer, non influenza il colore)
  • è 1 allora questo non dovrebbe influenzare il colore in uscita dal pixelshader. Esegue solo l'output C (i canali RGB non dovrebbero esserne influenzati)

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).

Approfondiamo

Alcuni dettagli sulle operazioni di blending (Blend Advanced)

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

La modalità default del blending

 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à...

La modalità Aggiungi, Add, di default

 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

La modalità Moltiplica, Multiply, di default

 Source Blend Mode = DestColor
 Destination Blend Mode = Zero
 C = C1 * Zero + C2 * DestColor
   = C2 * C1

Situazioni in cui l'ordine di disegno ha importanza

Trasparenza e Depthbuffer

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.

Trucchi che potrebbero essere più veloci, ma non precisi:

Selezionare uno spread di forme nello spazio della proiezione

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.

Una sola forma 3d

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.

todo:

  • think/talk about mixing add and blend mode when using a depthbuffer.
  • depthpeeling in detail
  • what more?
  • simplify
  • discuss other blend modes
  • discuss ZWriteEnable in detail
  • discuss AlphaTest
  • rename wikipage
  • add patches and effects

Prova della Commutatività di showNearestOnly

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

Shoutbox

~2d ago

joreg: vvvvTv S0204 is out: Custom Widgets with Dear ImGui: https://youtube.com/live/nrXfpn5V9h0

~2d ago

joreg: New user registration is currently disabled as we're moving to a new login provider: https://visualprogramming.net/blog/2024/reclaiming-vvvv.org/

~9d ago

joreg: vvvvTv S02E03 is out: Logging: https://youtube.com/live/OpUrJjTXBxM

~11d ago

~13d ago

joreg: Follow TobyK on his Advent of Code: https://www.twitch.tv/tobyklight

~16d ago

joreg: vvvvTv S02E02 is out: Saving & Loading UI State: https://www.youtube.com/live/GJQGVxA1pIQ

~16d ago

joreg: We now have a presence on LinkedIn: https://www.linkedin.com/company/vvvv-group

~24d ago

joreg: vvvvTv S02E01 is out: Buttons & Sliders with Dear ImGui: https://www.youtube.com/live/PuuTilbqd9w

~30d ago

joreg: vvvvTv S02E00 is out: Sensors & Servos with Arduino: https://visualprogramming.net/blog/2024/vvvvtv-is-back-with-season-2/

~30d ago