IT knowledge base
CTRL+F per cercare la tua parola chiave

Impara OpenGL. Parte 4.2. - Prova dello stampino

Immagine

Una volta che lo shader dei frammenti ha elaborato il frammento, viene eseguito un cosiddetto test stencil , che, come il test di profondità, può scartare i frammenti. Quindi i frammenti rimanenti vanno al test di profondità, che può espellere ancora più frammenti. Il test dello stencil si basa sul contenuto di un altro buffer chiamato buffer dello stencil . Possiamo aggiornarlo al momento del rendering per ottenere effetti interessanti.

Soddisfare
Parte 1. Inizio
  1. OpenGL
  2. Crea una finestra
  3. Ciao finestra
  4. Ciao triangolo
  5. Shader
  6. trame
  7. trasformazioni
  8. Sistemi di coordinate
  9. telecamera

Parte 2. Illuminazione di base
  1. Colori
  2. Nozioni di base sull'illuminazione
  3. Materiali (modifica)
  4. Mappe texture
  5. Fonti di luce
  6. Molteplici fonti di illuminazione

Parte 3. Caricamento di modelli 3D
  1. Libreria Assimp
  2. Classe poligono mesh
  3. Classe modello 3D

Parte 4. Funzionalità OpenGL avanzate
  1. Prova di profondità
  2. Prova dello stampino
  3. Miscelazione dei colori
  4. Ritaglio facce
  5. Buffer frame
  6. Carte cubiche
  7. Manipolazione avanzata dei dati
  8. GLSL avanzato
  9. Shader geometrico
  10. Istanza
  11. levigante

Parte 5. Illuminazione avanzata
  1. Illuminazione avanzata. Modello Blinn-Fong.
  2. Correzione gamma
  3. Mappe delle ombre
  4. Mappe ombre omnidirezionali
  5. Mappatura normale
  6. Mappatura della parallasse
  7. HDR
  8. fioritura
  9. Rendering differito
  10. SSAO

Parte 6. PBR
  1. Teoria
  2. Sorgenti luminose analitiche
  3. IBL. Irraggiamento diffuso.
  4. IBL. Irraggiamento speculare.

Prova dello stampino

Il buffer di stencil contiene tipicamente 8 bit per ogni valore di stencil per un totale di 256 diversi valori di stencil per fetta/pixel. Possiamo impostare questi valori a nostro piacimento, e quindi scartare o salvare i frammenti ogni volta che un certo frammento ha un certo valore di stencil.

Ogni libreria di finestre dovrebbe impostare uno stencil buffer per te. GLFW lo fa automaticamente, quindi non devi preoccuparti, ma altre librerie di finestre potrebbero non creare uno stencil buffer per impostazione predefinita, quindi assicurati di controllare la documentazione della tua libreria.

Di seguito è mostrato un semplice esempio di un buffer stencil:

img Innanzitutto, il buffer dello stencil viene riempito con zeri, quindi l'area del buffer, che sembra una cornice rettangolare, viene riempita con uno. Vengono visualizzati solo quei frammenti della scena, il cui valore dello stencil è uguale a uno, il resto viene scartato.

Le operazioni del buffer di stencil ci consentono di impostare un valore diverso per il buffer di stencil ovunque mostriamo i frammenti. Modificando i valori dello stencil buffer durante il rendering, eseguiamo un'operazione di scrittura . Nella stessa (o successiva) iterazione di rendering, possiamo leggere i valori dal buffer in modo che, in base ai valori letti, possiamo scartare o accettare determinati frammenti. Usando uno stencil buffer, puoi scherzare come preferisci, ma lo schema generale è:

  • Abilita la registrazione del buffer dello stencil.
  • Renderizza gli oggetti, aggiorna il contenuto del buffer dello stencil.
  • Disabilita la scrittura nel buffer dello stencil.
  • Disegna (altri) oggetti, questa volta scartando oggetti specifici in base al contenuto del buffer dello stencil.

In sintesi, possiamo dire che utilizzando uno stencil buffer, possiamo scartare alcuni frammenti basati su frammenti di altri oggetti nella scena.

Puoi abilitare il test dello stencil abilitandoGL_STENCIL_TEST ... D'ora in poi, tutte le chiamate di rendering influenzeranno il buffer dello stencil in un modo o nell'altro.

glEnable(GL_STENCIL_TEST);    

Nota che devi cancellare il buffer dello stencil ad ogni iterazione, proprio come il buffer di colore e profondità:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 

Per lo stencil buffer, esiste un analogo della funzioneglDepthMask utilizzato per parametrizzare il test di profondità. FunzioneglStencilMask ci consente di impostare una maschera di bit che parteciperà all'operazione AND bit per bit con i valori dello stencil scritti nel buffer. Per impostazione predefinita, la maschera di bit è una, che non influisce sull'output, ma se impostiamo la maschera su0x00 , quindi tutti i valori dello stencil verrebbero scritti come zeri. Equivalente all'impostazione della maschera del test di profonditàglDepthMask(GL_FALSE) ci sarebbe la seguente coppia di chiamate:

glStencilMask (0xFF); // every bit is written to the stencil buffer as is
glStencilMask (0x00); // every bit becomes zero in the stencil buffer (stop recording)

Nella maggior parte dei casi, basta scrivere just0x00 o0xFF a una maschera stencil, ma sarebbe bello sapere che è possibile impostare maschere di bit personalizzate.

Funzioni stencil

Proprio come nel test di profondità, abbiamo una certa capacità di controllare quando il test dello stencil passerà e quando no, e come questo dovrebbe influenzare il buffer dello stencil. Abbiamo solo due funzioni che possiamo usare per impostare il test dello stencil:glStencilFunc eglStencilOp ...

FunzioneglStencilFunc(GLenum func, GLint ref, GLuint mask) ha tre parametri:

  • func : Imposta la funzione di test dello schermo. Questa funzione viene applicata al valore dello stencil memorizzato e al valore del parametroref ... Opzioni possibili:GL_NEVER ,GL_LESS ,GL_LEQUAL ,GL_GREATER ,GL_GEQUAL ,GL_EQUAL ,GL_NOTEQUAL eGL_ALWAYS ... Il significato semantico di queste funzioni è simile alle funzioni di test di profondità.
  • ref : Definisce il valore di riferimento per il test dello stencil. Il contenuto del buffer dello stencil viene confrontato con questo valore.
  • mask : imposta la maschera utilizzata nell'operazione AND bit per bit con i valori memorizzati e di riferimento prima di confrontarli direttamente. Impostato su 1 per impostazione predefinita.

Quindi, nel caso del nostro semplice esempio di stencil che abbiamo mostrato all'inizio, la funzione sarebbe:

glStencilFunc(GL_EQUAL, 1, 0xFF)

Questo dice a OpenGL che ogni volta che il valore dello stencil del frammento è uguale al valore di riferimento 1, il frammento supera il test e viene disegnato, altrimenti viene scartato.

Ma la funzioneglStencilFunc descrive solo cosa dovrebbe fare OpenGL con il contenuto del buffer dello stencil, non come possiamo aggiornare il buffer. È qui che la funzione viene in nostro soccorsoglStencilOp ...

FunzioneglStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) contiene tre parametri con cui possiamo definire le azioni per ogni opzione:

  • sfail : Azione da intraprendere se il test dello stencil fallisce.
  • dpfail : azione da intraprendere se il test dello stencil ha esito positivo ma il test di profondità fallisce.
  • dppass : azione da intraprendere se entrambi i test passano

Quindi, per ogni caso, puoi eseguire una delle seguenti operazioni:

atto Descrizione
GL_KEEP Il valore dello stencil attualmente memorizzato viene mantenuto.
GL_ZERO Il valore dello stencil viene azzerato.
GL_REPLACE Il valore dello stencil viene sostituito dal valore di riferimento impostato dalla funzioneglStencilFunc ...
GL_INCR Il valore dello stencil viene aumentato di uno se è inferiore al valore massimo.
GL_INCR_WRAP Si comporta comeGL_INCR , ma quando si passa attraverso il massimo, il valore nel buffer viene azzerato.
GL_DECR Il valore dello stencil viene diminuito di uno se supera il valore minimo.
GL_DECR_WRAP Si comporta comeGL_DECR , ma quando si attraversa lo 0, il valore nel buffer è impostato al massimo.
GL_INVERT Bitwise inverte il valore del buffer dello stencil corrente.

Per impostazione predefinita, argomenti della funzioneglStencilOp installato in(GL_KEEP, GL_KEEP, GL_KEEP) in modo che, indipendentemente dai risultati del test, i valori nel buffer dello stencil vengano mantenuti. Il comportamento standard non aggiorna il buffer dello stencil, quindi se vogliamo scrivere nel buffer dello stencil, dobbiamo impostare almeno un'azione diversa da quella predefinita per qualsiasi opzione.

Quindi usandoglStencilFunc eglStencilOp possiamo impostare esattamente quando e come vogliamo aggiornare il buffer dello stencil e determiniamo anche quando il test dello stencil passerà e quando no, cioè quando i frammenti dovrebbero essere scartati.

Delineare gli oggetti

È improbabile che tu capisca appieno come funziona il test dello stencil in base alle sezioni precedenti, quindi dimostreremo un trucco utile che può essere implementato usando il test dello stencil. Questo è il contorno dell'oggetto .

cubo delineato

Non c'è bisogno di spiegare cosa si intende per contorno di un oggetto. Per ogni oggetto (o solo uno), creiamo un piccolo bordo colorato. Questo effetto è particolarmente utile quando, ad esempio, dobbiamo selezionare le unità in un gioco di strategia e poi mostrare all'utente quali sono state selezionate. L'algoritmo per formare un tratto per un oggetto è il seguente:

  1. Imposta la funzione stencil suGL_ALWAYS , prima di disegnare gli oggetti (che verranno delineati), aggiorna il buffer stencil con quelli in cui verranno disegnati i frammenti degli oggetti.
  2. Disegna oggetti.
  3. Disabilita il test di profondità e la scrittura del buffer dello stencil.
  4. Ingrandisci leggermente ogni oggetto.
  5. Usa uno shader di frammenti diverso che mostri solo un colore (colore del tratto).
  6. Disegna di nuovo l'oggetto, ma solo se il valore dello stencil dei suoi frammenti non è uguale a uno.
  7. Riattiva la registrazione del buffer dello stencil e il test di profondità.

Questo processo imposta il contenuto del buffer per ogni blocco di un oggetto su uno, e quando vogliamo disegnare i bordi, essenzialmente disegniamo versioni scalabili degli oggetti, e dove il test lo consente, viene disegnata la versione scalata (attorno ai bordi dell'oggetto ). Usando il test dello stencil, scartiamo quei frammenti degli oggetti in scala che si sovrappongono ai frammenti degli oggetti originali.

Per prima cosa, creiamo uno shader di frammenti molto semplice che emetta il colore del tratto. Abbiamo appena impostato il colore hardcoded e chiamato lo shadershaderSingleColor :

void main()
{
    FragColor = vec4(0.04, 0.28, 0.26, 1.0);
}

Abbiamo in programma di includere tratti solo per due contenitori, ma non per il pavimento. Pertanto, prima è necessario visualizzare il pavimento, quindi due contenitori (con la scrittura sul buffer dello stencil) e quindi versioni ingrandite dei contenitori (con scarto dei frammenti sovrapposti a frammenti già resi dei contenitori originali).

Innanzitutto, dobbiamo abilitare il test dello stencil e impostare le azioni da intraprendere se uno dei test ha esito positivo o negativo:

glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  

Se uno dei test fallisce, non facciamo nulla, lasciamo solo il valore corrente nel buffer dello stencil. Se sia il test di stencil che il test di profondità hanno esito positivo, sostituiamo il valore di stencil corrente con il valore di riferimento impostato tramiteglStencilFunc che in seguito imposteremo a 1.

Il buffer viene cancellato riempiendo con zeri e, per i contenitori, aggiorniamo il buffer dello stencil a 1 per ogni pezzo disegnato:

glStencilFunc (GL_ALWAYS, 1, 0xFF); // each fragment will update the stencil buffer
glStencilMask (0xFF); // enable writing to the stencil buffer
normalShader.use ();
DrawTwoContainers ();

UsandoGL_ALWAYS in funzioneglStencilFunc garantiamo che ogni frammento del contenitore aggiornerà il buffer dello stencil con un valore di stencil di 1. Poiché i frammenti superano sempre il test dello stencil, il buffer dello stencil viene aggiornato con un valore di riferimento ovunque li disegniamo.

Ora che il buffer dello stencil è stato aggiornato a uno, in cui abbiamo disegnato i contenitori, dobbiamo disegnare le versioni ingrandite dei contenitori, ma ora disabilitare la scrittura del buffer dello stencil:

glStencilFunc (GL_NOTEQUAL, 1, 0xFF);
glStencilMask (0x00); // disable writing to the stencil buffer
glDisable (GL_DEPTH_TEST);
shaderSingleColor.use ();
DrawTwoScaledUpContainers ();

Usiamo l'argomentoGL_NOTEQUAL nelglStencilFunc che assicura che disegniamo solo quelle parti degli oggetti che non sono uguali a uno, quindi disegniamo solo quelle parti degli oggetti che sono al di fuori degli oggetti precedentemente disegnati. Nota che abbiamo anche disabilitato il test di profondità in modo che gli elementi dei contenitori ingranditi, ad esempio i loro bordi, non vengano sovrascritti dal pavimento.

Assicurati anche di riattivare il test di profondità.

Lo schema generale di un oggetto per la nostra scena assomiglia a questo:

glEnable (GL_DEPTH_TEST);
glStencilOp (GL_KEEP, GL_KEEP, GL_REPLACE);
  
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

glStencilMask (0x00); // make sure we don't update the stencil buffer while painting the floor
normalShader.use ();
DrawFloor ()
  
glStencilFunc (GL_ALWAYS, 1, 0xFF);
glStencilMask (0xFF);
DrawTwoContainers ();
  
glStencilFunc (GL_NOTEQUAL, 1, 0xFF);
glStencilMask (0x00);
glDisable (GL_DEPTH_TEST);
shaderSingleColor.use ();
DrawTwoScaledUpContainers ();
glStencilMask (0xFF);
glEnable (GL_DEPTH_TEST);

Se comprendi l'idea generale alla base del test dello stencil, questo pezzo di codice non dovrebbe essere troppo difficile da capire. In caso contrario, prova a leggere più da vicino le sezioni precedenti e a comprendere appieno cosa fa ciascuna funzione, ora che hai visto un esempio del suo utilizzo.

Il risultato dell'applicazione dell'algoritmo del tratto alla scena dalla lezione del test di profondità è simile al seguente:

img

Controlla il codice sorgente qui per vedere il codice completo per l'algoritmo del tratto dell'oggetto

Potresti notare che i bordi tra i due contenitori si sovrappongono. Di solito, questo è esattamente ciò di cui hai bisogno (ricorda i giochi di strategia, quando selezioniamo più unità). Se vuoi un bordo completo attorno a ogni oggetto, devi cancellare il buffer dello stencil per ogni oggetto e armeggiare un po' con l'impostazione del test di profondità.

L'algoritmo di tracciamento degli oggetti che hai visto viene spesso utilizzato in alcuni giochi per eseguire il rendering di oggetti selezionati (pensa ai giochi di strategia) e un tale algoritmo può essere facilmente implementato nella classe del modello. Puoi quindi semplicemente impostare un flag booleano sulla classe del modello per disegnare con o senza bordi. Con un po' di creatività, puoi rendere i bordi più organici con filtri di post-elaborazione come la sfocatura gaussiana.

Il test dello stencil può fare molto di più che tracciare oggetti, come dipingere trame all'interno dello specchietto retrovisore in modo che si adattino alla cornice dello specchio. Oppure esegui il rendering delle ombre in tempo reale utilizzando la tecnica dei volumi delle ombre . Lo stencil buffer ci fornisce un altro ottimo strumento nella nostra già ampia cassetta degli attrezzi OpenGL.

PS
Grazie mille per l'aiuto e i commenti sulla traduzione in UberSchlag !
Se vuoi aiutare con la traduzione, partecipa alla conversazione su Telegram:
Impossibile aggiungere un collegamento al post originale (scrive: non è un collegamento ), quindi eccolo qui .