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

Impara OpenGL. Lezione 4.3 - Mescolare i colori

OGL3

Miscelazione dei colori

Blending in OpenGL (e le altre API grafiche, circa Per.) È questa la tecnica che di solito è associata all'implementazione della trasparenza degli oggetti. La traslucenza di un oggetto significa che non è riempito con un colore solido, ma che combina in varie proporzioni l'ombra del suo materiale con i colori degli oggetti dietro. Ad esempio, puoi prendere il vetro colorato in una finestra: il vetro ha una sua sfumatura, ma alla fine osserviamo una miscela della tonalità del vetro e di tutto ciò che è visibile dietro il vetro. In realtà, il termine mescolanza nasce da questo comportamento, poiché osserviamo il colore finale, che è una mescolanza dei colori dei singoli oggetti. Grazie a questo, possiamo vedere attraverso oggetti traslucidi.
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.

Gli oggetti semitrasparenti possono essere completamente trasparenti (tutti i colori passano attraverso) o parzialmente trasparenti (luce traslucida, ma aggiunge la propria tinta). Nella computer grafica è consuetudine indicare il grado di opacità con la cosiddetta componente alfa del vettore colore. Il componente alfa è il quarto elemento del vettore colore e devi averlo notato più di una volta nei tutorial precedenti. Tuttavia, fino a questo punto, abbiamo sempre mantenuto questo valore a 1.0, che equivale all'opacità completa. Impostando il componente alfa su 0.0, otterremmo la piena trasparenza. Un valore di 0,5 implicherebbe che il colore finale dell'oggetto è fissato al 50% dal suo materiale e al 50% dagli oggetti dietro di esso.
Tutte le trame che abbiamo usato finora contengono 3 componenti di colore: rosso, blu e verde. Alcuni formati di texture consentono anche di memorizzare un quarto componente alfa per ogni texel. Questo valore indica quali parti della trama sono semitrasparenti e fino a che punto. Ad esempio, una determinata trama del vetro di una finestra ha un componente alfa impostato su 0,25 per le aree in vetro e 0,0 per la cornice. In caso contrario, le parti in vetro sarebbero completamente rosse, ma a causa dell'opacità del 75%, il colore è determinato principalmente dallo sfondo della pagina Web corrente.
Presto aggiungeremo questa trama a una nuova scena, ma prima discuteremo una tecnica più semplice per ottenere la trasparenza nei casi in cui è necessaria la piena trasparenza o la piena opacità.

Frammenti che cadono

In alcuni casi, non è richiesta la trasparenza parziale: è necessario eseguire il rendering di qualcosa o niente in base al valore del colore della trama. Immagina un mucchio d'erba: l'implementazione più semplice di un mazzo richiederebbe una trama di erba su un quad 2D posizionato nella scena. Tuttavia, nel compito di simulare un mucchio d'erba, la forma del quad non aiuta molto: potremmo usare per nascondere parti della trama sovrapposta, lasciandone altre.
La texture sotto rappresenta esattamente il caso descritto: le sue aree sono completamente opache (componente alfa = 1.0), o completamente trasparenti (componente alfa = 0.0) - nessuna media. Puoi vedere che dove non c'è l'immagine di fili d'erba, è visibile lo sfondo del sito e non il colore della trama:
Pertanto, quando posizioniamo la vegetazione nella nostra scena, vorremmo vedere solo le parti della trama che corrispondono alle parti della pianta e scartare il resto della trama che riempie il poligono. Cioè, scartare i frammenti contenenti parti trasparenti della trama senza memorizzarli nel buffer di colore. Ma prima di sporcarci le mani direttamente con i frammenti, dobbiamo imparare a caricare le trame con un canale alfa.
Per fare questo, non dobbiamo cambiare molto nel codice familiare. La funzione loader da stb_image.h carica automaticamente il canale alfa dell'immagine, se disponibile. Ma in questo caso, devi dire esplicitamente a OpenGL quando crei una texture che usa un canale alfa:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); 
Assicurati anche che nel frammento shader stai campionando in un vettore con 4 componenti, in modo da non rimanere solo con valori RGB:
void main()
{
    // FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0);
    FragColor = texture(texture1, TexCoords);
}
Ora che abbiamo capito come caricare le trame con la trasparenza, è il momento di spargere alcuni ciuffi d'erba sulla scena utilizzata nel tutorial del test di profondità .
Creiamo un piccolo vettore che memorizzi la posizione dei ciuffi d'erba come glm :: vec3 :
vector<glm::vec3> vegetation;
vegetation.push_back(glm::vec3(-1.5f,  0.0f, -0.48f));
vegetation.push_back(glm::vec3( 1.5f,  0.0f,  0.51f));
vegetation.push_back(glm::vec3( 0.0f,  0.0f,  0.7f));
vegetation.push_back(glm::vec3(-0.3f,  0.0f, -2.3f));
vegetation.push_back(glm::vec3( 0.5f,  0.0f, -0.6f)); 
Ogni oggetto erba viene renderizzato come un singolo quad con una texture erba assegnata. Non è il metodo più entusiasmante per simulare l'erba in 3D, ma molto più efficace rispetto all'utilizzo di modelli poligonali. Con l'aiuto di piccoli accorgimenti, come l'aggiunta di un altro paio di quad ruotati con la stessa trama nella stessa posizione, puoi ottenere buoni risultati.
Poiché stiamo assegnando la trama dell'erba al quad, abbiamo bisogno di un nuovo VAO (oggetto array di vertici), riempire il VBO (oggetto buffer di vertice) e impostare i puntatori appropriati agli attributi di vertice. Successivamente, dopo aver eseguito il rendering della superficie del pavimento e dei cubi, eseguiamo il rendering della nostra erba:
glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);  
for(unsigned int i = 0; i < vegetation.size(); i++) 
{
    model = glm::mat4(1.0f);
    model = glm::translate(model, vegetation[i]);				
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}  
L'esecuzione del programma produrrà il seguente risultato:
Questo perché OpenGL stesso non sa cosa fare con i valori alfa, né quando rilasciare i frammenti. Dobbiamo specificare tutto questo manualmente. Fortunatamente, gli shader sono abbastanza facili da fare. In GLSL c'è una direttiva incorporata scartare , la cui chiamata porta alla cessazione dell'ulteriore elaborazione del frammento corrente senza entrare nel buffer di colore. Da qui emerge una soluzione: controlliamo il valore della componente alfa dell'elemento texture e, se è inferiore a una certa soglia, la scartiamo:
#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{             
    vec4 texColor = texture(texture1, TexCoords);
    if(texColor.a < 0.1)
        discard;
    FragColor = texColor;
}
In questo codice, scartiamo un frammento se il componente alfa del recupero della trama è inferiore a 0,1. Tale shader ci fornirà l'output solo di quei frammenti che si sono rivelati abbastanza opachi:

Si noti che quando si recuperano i limiti della trama, OpenGL interpola il valore sul confine con il valore successivo ottenuto dalla ripetizione della trama (poiché impostiamo il parametro di ripetizione della trama su GL_REPEAT ). Per l'uso normale delle trame questo va bene, ma per la nostra trama con trasparenza non funziona: il valore di texel completamente trasparente sul bordo superiore viene miscelato con i texel completamente opachi del bordo inferiore. Di conseguenza, potrebbe apparire un bordo colorato traslucido attorno al quad con la nostra trama. Per evitare questo artefatto, impostare il parametro di ripetizione su GL_CLAMP_TO_EDGE quando si utilizzano trame con trasparenza.

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);	
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    
Il codice di esempio è qui .

miscelazione

Sebbene scartare i frammenti sia un metodo comodo e facile, non fornisce la possibilità di fondere parzialmente i colori traslucidi. Per eseguire il rendering di immagini con oggetti con diversi gradi di opacità, dobbiamo attivare la modalità di fusione. Questo viene fatto come per la maggior parte delle modalità OpenGL:
glEnable(GL_BLEND);  
Ora, con il missaggio abilitato, vale la pena capire come funziona esattamente.
La fusione OpenGL viene eseguita con la seguente formula

$$ display $$ \ begin {equation} \ bar {C} _ {risultato} = \ bar {\ color {green} C} _ {source} * \ color {green} F_ {source} + \ bar {\ color {rosso} C} _ {destinazione} * \ colore {rosso} F_ {destinazione} \ fine {equazione} $$ visualizza $$

Dove $ \ bar {\ color {verde} C} _ {sorgente} $ È il vettore del colore di origine. Questo è il valore del colore ottenuto dalla trama.
$ \ bar {\ colore {rosso} C} _ {destinazione} $ È il vettore del colore del ricevitore. Questo è il valore del colore attualmente memorizzato nel buffer di colore.
$ \ colore {verde} F_ {fonte} $ - moltiplicatore di origine. Specifica il grado in cui il componente alfa influisce sul colore della sorgente.
$ \ colore {rosso} F_ {destinazione} $È il moltiplicatore del ricevitore. Specifica il grado in cui il componente alfa influisce sul colore del target.
Dopo la fase di esecuzione del frammento shader e altri test (stencil e test di profondità, circa Per. ), questa formula di miscelazione è libera di fare qualsiasi cosa con i colori dei frammenti elaborati e i colori attualmente memorizzati nel buffer (i valori di colore dei frammenti del fotogramma precedente). I ruoli di origine e destinazione vengono assegnati automaticamente da OpenGL, ma possiamo impostare noi stessi i moltiplicatori. Innanzitutto, considera il seguente esempio:
Ci sono due quadrati e un verde traslucido che vorremmo dipingere sul rosso opaco. In questo caso, il colore di destinazione sarà il colore del quadrato rosso, il che significa che deve essere inserito per primo nel buffer dei colori.
Sorge la domanda: come scegliere i valori dei fattori nella formula di miscelazione? Bene, almeno dovremmo moltiplicare il colore verde del secondo quadrato per il suo valore alfa, quindi$ \ colore {verde} F_ {fonte} $ Prendiamo uguale alla componente alfa del vettore colore sorgente, ad es. 0.6. Su tale base è ragionevole ritenere che il ricevente fornisca un contributo al risultato proporzionale al grado di trasparenza che rimane disponibile. Se il quadrato verde fornisce il 60% del totale, il quadrato rosso dovrebbe ottenere il 40% (1. - 0.6). Quindi il moltiplicatore $ \ colore {rosso} F_ {destinazione} $impostato uguale alla differenza tra l'unità e la componente alfa del vettore del colore di origine. Di conseguenza, l'espressione di miscelazione diventa:

$$ display $$ \ begin {equazione} \ bar {C} _ {risultato} = \ begin {pmatrix} \ color {rosso} {0.0} \\ \ color {verde} {1.0} \\ \ color {blu} {0.0} \\ \ color {viola} {0.6} \ end {pmatrix} * \ color {verde} {0.6} + \ begin {pmatrix} \ color {rosso} {1.0} \\ \ color {verde} {0.0 } \\ \ color {blu} {0.0} \\ \ color {viola} {1.0} \ end {pmatrix} * \ color {rosso} {(1 - 0.6)} \ end {equation} $$ visualizza $$

Il risultato della miscelazione sarà un colore composto per il 60% dal verde originale e per il 40% dal rosso originale - questo è un colore marrone indistinto:
Il risultato verrà memorizzato nel buffer colore, sovrascrivendo i vecchi valori.
Quindi, come diciamo a OpenGL che tipo di valori del fattore di fusione vogliamo usare? Fortunatamente per noi, c'è una funzione speciale:
glBlendFunc(GLenum sfactor, GLenum dfactor)
Ci vogliono due parametri che determinano i valori dei coefficienti di origine e di destinazione. L'API OpenGL definisce un elenco esaustivo di valori per questi parametri, consentendo di personalizzare la modalità di fusione a proprio piacimento. Qui darò i valori più "popolari" dei parametri. Nota che il vettore di colore costante $ \ bar {\ color {blu} C} _ {costante} $ impostato separatamente dalla funzione glBlendColor .
Per ottenere il risultato descritto nell'esempio con due quadrati, dovremmo scegliere tali parametri in modo che il coefficiente della sorgente sia uguale ad alfa (valore della componente alfa) del colore della sorgente e il coefficiente del ricevitore sia uguale a 1-alfa . Che equivale a chiamare:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  
È anche possibile regolare separatamente i coefficienti per i componenti RGB e alfa utilizzando la funzione glBlendFuncSeparate :
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
Tale chiamata imposta la fusione della componente RGB come nell'esempio precedente, specificando inoltre che la componente alfa del risultato sarà uguale alla componente alfa della sorgente.
OpenGL consente inoltre una personalizzazione ancora più flessibile della formula di miscelazione consentendo la scelta dell'operazione da eseguire tra i componenti della formula. Per impostazione predefinita, vengono aggiunti i componenti di origine e destinazione, ma puoi selezionare e sottrarre se questo è il tuo intento. Definisce il comportamento di una funzione
glBlendEquation(GLenum mode)
E ci sono tre opzioni per il valore del parametro:
  • GL_FUNC_ADD : predefinito, aggiungi componenti: $ \ bar {C} _ {risultato} = \ colore {verde} {Src} + \ colore {rosso} {Dst} $ ...
  • GL_FUNC_SUBTRACT : sottrae il componente sink dal componente sorgente: $ \ bar {C} _ {risultato} = \ colore {verde} {Src} - \ colore {rosso} {Dst} $ ...
  • GL_FUNC_REVERSE_SUBTRACT : Sottrae il componente sorgente dal componente sink: $ \ bar {C} _ {risultato} = \ colore {rosso} {Dst} - \ colore {verde} {Src} $ ...
Di solito, glBlendEquation non è richiesto perché la modalità predefinita GL_FUNC_ADD va bene per la maggior parte dei casi d'uso. Ma per approcci e tentativi non standard di creare una soluzione visiva insolita, potrebbero essere utili altre modalità per calcolare la formula di miscelazione.

Rendi trame traslucide

Quindi, abbiamo visto come la libreria esegue il missaggio. È tempo di mettere in pratica questa conoscenza creando un paio di finestre trasparenti. Utilizzeremo la stessa scena dell'inizio della lezione, ma al posto dei ciuffi d'erba posizioneremo oggetti con la trama della finestra già menzionata all'inizio della lezione.
Innanzitutto, attiva la modalità di fusione e seleziona i suoi parametri:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Poiché abbiamo abilitato la fusione, non è più necessario scartare i frammenti trasparenti. Riportiamo il codice del frammento shader al suo stato precedente:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture1;

void main()
{             
    FragColor = texture(texture1, TexCoords);
}  
Ora, durante l'elaborazione di ciascun frammento, OpenGL mescola il colore del frammento elaborato e il colore memorizzato nel buffer in base al valore del componente alfa del primo. Poiché la parte in vetro della finestra è traslucida, dovremmo essere in grado di vedere il resto della scena dietro la finestra:
Tuttavia, se guardi da vicino, noterai che il rendering non è corretto. Per qualche ragione, le parti semitrasparenti della finestra più vicina si sovrappongono ad altre finestre sullo sfondo!
Il motivo è che il test di profondità non tiene conto del fatto che il frammento sia trasparente o meno durante l'elaborazione. Di conseguenza, tutti i frammenti di un quad con una trama a finestra superano il test di profondità allo stesso modo, indipendentemente dal fatto che appartengano o meno alla parte in vetro. Sebbene i vecchi frammenti debbano rimanere dietro i pezzi di vetro, un test di profondità li scarterà.
In conclusione: non puoi visualizzare oggetti traslucidi, proprio come sperare che il test di profondità e la fusione decida come fare tutto correttamente. Per garantire che le finestre sovrapposte ad altre finestre vengano visualizzate correttamente, dobbiamo prima eseguire il rendering delle finestre che si trovano in lontananza. Pertanto, noi stessi dobbiamo ordinare le finestre per posizione dalla più lontana alla più vicina e visualizzarle secondo questo ordine.

Si noti che per i casi con trasparenza totale (il caso con erba), l'operazione di scarto non causa il problema descritto, poiché non si verifica alcuna miscelazione.

Rendering mantenendo l'ordine

Affinché la fusione funzioni correttamente, quando si esegue il rendering di un insieme di oggetti, è necessario iniziare l'output dal più lontano e terminare con il più vicino. Gli oggetti opachi che non richiedono la fusione possono essere visualizzati nel modo consueto utilizzando un buffer di profondità, qui non è richiesto alcun ordinamento. Ma la parte opaca della scena deve essere renderizzata prima che vengano visualizzati gli elementi che utilizzano la fusione. Di conseguenza, la procedura per il rendering di una scena contenente oggetti sia opachi che trasparenti è la seguente:
  1. Visualizza tutti gli oggetti opachi.
  2. Ordina gli oggetti trasparenti per eliminazione.
  3. Disegna oggetti trasparenti in ordine.

Un modo per ordinare è ordinare in base alla distanza dall'oggetto all'osservatore. Questo valore è definito come la distanza tra i vettori delle posizioni della telecamera e l'oggetto stesso. Successivamente, memorizzeremo questa distanza insieme al vettore di posizione dell'oggetto nel contenitore della mappa della libreria standard C++. Il contenitore associativo della mappa organizzerà automaticamente gli elementi memorizzati in base ai valori chiave, quindi è sufficiente inserire tutte le coppie di oggetti distanza-posizione:
std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < windows.size(); i++)
{
    float distance = glm::length(camera.Position - windows[i]);
    sorted[distance] = windows[i];
}
Di conseguenza, avremo un contenitore con le posizioni degli oggetti finestra ordinati per distanza dal più piccolo al più grande.
Al momento del disegno, dobbiamo passare attraverso il contenitore in ordine inverso (dal più grande al più piccolo) e disegnare le finestre nelle posizioni appropriate:
for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) 
{
    model = glm::mat4(1.0f);
    model = glm::translate(model, it->second);				
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
} 
Qui usiamo un iteratore inverso per il contenitore per attraversarlo in ordine inverso. Allo stesso tempo, ogni oggetto finestra viene spostato nella posizione corrispondente e disegnato. Una modifica del codice relativamente semplice ha portato a una completa risoluzione del problema precedentemente indicato:
Come puoi vedere, la scena ora viene visualizzata correttamente. Il codice sorgente per l'esempio è qui .
Vale la pena notare che il semplice ordinamento per intervallo, sebbene abbia funzionato bene in questo caso, non tiene conto di caratteristiche come rotazioni, ridimensionamento e altre trasformazioni di oggetti. Inoltre, gli oggetti di forma complessa richiederebbero una metrica più sofisticata per l'ordinamento rispetto alla semplice distanza dalla fotocamera.
Inoltre, l'ordinamento non è gratuito: la complessità di questo compito è determinata dal tipo e dalla composizione della scena e il processo stesso richiede costi computazionali aggiuntivi. Esistono anche metodi più avanzati per visualizzare scene contenenti oggetti sia trasparenti che opachi: ad esempio, l'algoritmo Order Independent Transparency (OIT ). Ma trattare questo argomento va oltre lo scopo di questo tutorial. E devi cavartela con la solita implementazione di mixaggio. Ma non c'è motivo di essere tristi, conoscere i limiti della tecnologia e fare attenzione può ottenere risultati piuttosto impressionanti!
PS : c'è di nuovo un link utile nei commenti. Puoi vedere dal vivo come la scelta dei metodi di fusione influisce sul risultato.
PPS: Abbiamo un eanmosTelegram-Config per il coordinamento della traduzione. Se hai un serio desiderio di aiutare con la traduzione, allora sei il benvenuto!