Introduzione in WebGL, parte 3 contesto WebGL e cancellazione

Negli articoli precedenti, abbiamo imparato come scrivere semplici vertex e frammenti di shader, creare una semplice pagina Web e preparare una tela per disegnare. In questo articolo, inizieremo a lavorare sul nostro codice boilerplate WebGL. 

Otterremo un contesto WebGL e lo useremo per cancellare la tela con il colore della nostra scelta. Woohoo! Questo può essere un minimo di tre righe di codice, ma ti prometto che non lo renderò così facile! Come al solito, cercherò di spiegare gli ingannevoli concetti di JavaScript mentre li incontriamo e di fornirti tutti i dettagli necessari per capire e prevedere il comportamento WebGL corrispondente.

Questo articolo fa parte della serie "Guida introduttiva in WebGL". Se non hai letto le parti precedenti, ti consiglio di leggerle prima:

  1. Introduzione agli shaders
  2. L'elemento Canvas

Ricapitolare

Nel primo articolo di questa serie abbiamo scritto un semplice shader che disegna un gradiente colorato e lo sbiadisce leggermente all'interno e all'esterno. Ecco lo shader che abbiamo scritto:

Nel secondo articolo di questa serie, abbiamo iniziato a lavorare per utilizzare questo shader in una pagina web. Facendo piccoli passi, abbiamo spiegato lo sfondo necessario dell'elemento canvas. Noi:

  • fatto una semplice pagina
  • aggiunto un elemento canvas
  • acquisito un contesto 2D per il rendering sulla tela
  • usato il contesto 2D per disegnare una linea
  • gestito i problemi di ridimensionamento della pagina
  • gestito i problemi di densità dei pixel

Ecco cosa abbiamo fatto finora:

In questo articolo prendiamo in prestito alcuni pezzi di codice dall'articolo precedente e adattiamo la nostra esperienza a WebGL anziché a disegni 2D. Nel prossimo articolo, se Allah vorrà, tratterò la gestione della visualizzazione e il ritaglio delle primitive. Ci vorrà un po ', ma spero che troverai l'intera serie molto utile!

Configurazione iniziale

Costruiamo la nostra pagina basata su WebGL. Useremo lo stesso codice HTML che abbiamo utilizzato per l'esempio di disegno 2D:

        

... con una modifica molto piccola. Qui chiamiamo la tela glCanvas invece di solo tela (MEH!).

Useremo anche lo stesso CSS:

html, body height: 100%;  body margin: 0;  canvas display: block; larghezza: 100%; altezza: 100%; sfondo: # 000; 

Tranne il colore di sfondo, che ora è nero.

Non useremo lo stesso codice JavaScript. Iniziamo senza codice JavaScript e aggiungiamo funzionalità bit per bit per evitare confusione. Ecco la nostra configurazione finora:

Adesso scriviamo un po 'di codice!

Contesto WebGL

La prima cosa che dovremmo fare è ottenere un contesto WebGL per il canvas. Proprio come abbiamo fatto quando abbiamo ottenuto un contesto di disegno 2D, usiamo la funzione membro getContext:

glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "sperimentale-WebGL");

Questa riga contiene due getContext chiamate. Normalmente, non dovremmo aver bisogno della seconda chiamata. Ma nel caso in cui l'utente stia utilizzando un vecchio browser in cui l'implementazione WebGL è ancora sperimentale (o Microsoft Edge), abbiamo aggiunto il secondo. 

La cosa bella del || operatore (o operatore) È che non deve valutare l'intera espressione se è stato trovato il primo operando vero. In altre parole, in un'espressione a || B, Se un valuta a vero, allora se B è vero o falso non ha alcun effetto sul risultato. Quindi, non abbiamo bisogno di valutare B ed è saltato interamente. Questo è chiamato Valutazione del cortocircuito.

Nel nostro caso, getContext ( "sperimentale-WebGL") sarà eseguito solo se getContext ( "WebGL") fallisce (ritorna nullo, che valuta falso in un'espressione logica). 

Abbiamo anche usato un'altra funzionalità di o operatore. Il risultato di oring non è né l'uno né l'altro verofalso. Invece, lo è il primo oggetto che valuta vero. Se nessuno degli oggetti è valutato vero, o restituzioni l'oggetto più a destra nell'espressione. Ciò significa, dopo aver eseguito la riga sopra, glContext conterrà o un oggetto di contesto o nullo, ma no vero o falso.

Nota: se il browser supporta entrambe le modalità (WebGL e sperimentale WebGL) quindi vengono trattati come alias. Non ci sarebbe assolutamente alcuna differenza tra loro.

Mettendo la linea sopra a cui appartiene:

var glContext; function initialize () // Ottieni contesto WebGL, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "sperimentale-WebGL"); if (! glContext) alert ("Impossibile acquisire un contesto WebGL. Ci scusiamo!"); restituisce falso;  return true; 

Ecco! Abbiamo il nostro inizializzare funzione (sì, continua a sognare!).

Gestione degli errori getContext

Si noti che non abbiamo usato provare e catturare Rilevare getContext problemi come abbiamo fatto nel precedente articolo. È perché WebGL ha i suoi meccanismi di segnalazione degli errori. Non genera un'eccezione quando la creazione del contesto fallisce. Invece, spara a webglcontextcreationerror evento. Se siamo interessati al messaggio di errore, dovremmo probabilmente farlo:

// Listener di errori di creazione del contesto, var errorMessage = "Impossibile creare un contesto WebGL"; function onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage;  glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

Prendendo queste linee a parte:

glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

Proprio come quando abbiamo aggiunto un listener all'evento di caricamento della finestra nell'articolo precedente, abbiamo aggiunto un listener al canvas webglcontextcreationerror evento. Il falso l'argomento è facoltativo; Lo sto solo includendo per completezza (dal momento che l'esempio delle specifiche WebGL ce l'ha). Di solito è incluso per la retrocompatibilità. Sta per useCapture. quando vero, significa che l'ascoltatore verrà chiamato nel fase di acquisizione della propagazione dell'evento. Se falso, sarà chiamato nel fase di gorgogliamento anziché. Controlla questo articolo per ulteriori dettagli sulla propagazione degli eventi.

Ora per l'ascoltatore stesso:

var errorMessage = "Impossibile creare un contesto WebGL"; function onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage; 

In questo listener, manteniamo una copia del messaggio di errore, se presente. Sì, avere un messaggio di errore è totalmente facoltativo:

if (event.statusMessage) errorMessage = event.statusMessage;

Quello che abbiamo fatto qui è piuttosto interessante. messaggio di errore è stato dichiarato al di fuori della funzione, eppure lo abbiamo usato all'interno. Questo è possibile in JavaScript e viene chiamato chiusure. Ciò che è interessante delle chiusure è la loro vita. Mentre messaggio di errore è locale al inizializzare funzione, dal momento che è stato utilizzato all'interno onContextCreationError, non sarà distrutto a meno che onContextCreationError stesso non è più referenziato.

In altre parole, finché un identificatore è ancora accessibile, non può essere raccolto. Nella nostra situazione:

  • messaggio di errore vive perché onContextCreationError lo fa riferimento.
  • onContextCreationError vive perché è referenziato da qualche parte tra gli ascoltatori di eventi su tela. 

Quindi, anche se inizializzare termina, onContextCreationError è ancora referenziato da qualche parte nell'oggetto canvas. Solo quando è liberato può messaggio di errore essere raccolto dai rifiuti. Inoltre, le successive chiamate di inizializzare non influenzerà il precedente messaggio di errore. Ogni inizializzare la chiamata alla funzione avrà il suo messaggio di errore e onContextCreationError.

Ma non vogliamo davvero onContextCreationError vivere oltre inizializzare terminazione. Non vogliamo ascoltare altri tentativi di ottenere contesti WebGL in qualsiasi altro punto del codice. Così:

glCanvas.removeEventListener ("webglcontextcreationerror", onContextCreationError, false);

Mettere tutto insieme:

Per verificare che abbiamo creato con successo il contesto, ho aggiunto un semplice mettere in guardia:

alert ("Contesto WebGL creato con successo!");

Ora passa al Risultato scheda per eseguire il codice.

E non funziona! Ovviamente, perché inizializzare non è mai stato chiamato. Dobbiamo chiamarlo subito dopo aver caricato la pagina. Per questo, aggiungeremo queste righe sopra di esso:

window.addEventListener ('load', function () initialize ();, false);

Proviamo di nuovo:

Funziona! Voglio dire, dovrebbe a meno che non si possa creare un contesto! In caso contrario, assicurati di visualizzare questo articolo da un dispositivo / browser compatibile con WebGL.

Nota che abbiamo fatto un'altra cosa interessante qui. Abbiamo usato inizializzare nel nostro caricare ascoltatore prima che fosse addirittura dichiarato. Questo è possibile in JavaScript a causa di sollevamento. Sollevare significa che tutte le dichiarazioni vengono spostate nella parte superiore del loro ambito, mentre le loro inizializzazioni rimangono al loro posto.

Ora, non sarebbe bello provare se il nostro meccanismo di segnalazione degli errori funziona davvero? Abbiamo bisogno getContext fallire. Un modo semplice per farlo è ottenere un diverso tipo di contesto per la tela prima di tentare di creare il contesto WebGL (ricorda quando abbiamo detto che il primo ha avuto successo getContext cambia la modalità canvas in modo permanente?). Aggiungeremo questa riga appena prima di ottenere il contesto WebGL:

glCanvas.getContext ( "2d");

E:

Grande! Ora se il messaggio che hai visto è stato "Impossibile creare un contesto WebGL"o qualcosa di simile"La tela ha un contesto esistente di un tipo diverso"dipende dal fatto che il tuo browser supporti webglcontextcreationerror o no. Al momento di scrivere questo articolo, Edge e Firefox non lo supportano (era programmato per Firefox 49, ma non funziona ancora su Firefox 50.1). In tal caso, il listener di eventi non verrà chiamato e messaggio di errore rimarrà impostato su "Impossibile creare un contesto WebGL". Per fortuna, getContext ancora ritorna nullo, quindi sappiamo che non siamo riusciti a creare il contesto. Semplicemente non abbiamo il messaggio di errore dettagliato.

Il problema dei messaggi di errore WebGL è che ... non ci sono messaggi di errore WebGL! WebGL restituisce numeri che indicano stati di errore, non messaggi di errore. E quando capita di consentire i messaggi di errore, sono dipendenti dal driver. La formulazione esatta dei messaggi di errore non è fornita nelle specifiche: spetta agli sviluppatori di driver decidere come debbano essere messi. Quindi aspettati di vedere lo stesso errore formulato in modo diverso su dispositivi diversi.

Va bene allora. Dal momento che abbiamo fatto in modo che il nostro meccanismo di segnalazione degli errori funzioni, il "creato con successo"avviso e getContext ( "2d") non sono più necessari Li ometteremo.

Attributi del contesto

Torna ai nostri riveriti getContext funzione:

glContext = glCanvas.getContext ("webgl");

C'è dell'altro più di quanto sembri. getContext può opzionalmente assumere un altro argomento: un dizionario che contiene un insieme di attributi di contesto e i loro valori. Se nessuno è fornito, vengono utilizzate le impostazioni predefinite:

dizionario WebGLContextAttributes GLboolean alpha = true; GLboolean depth = true; Stencil goboleano = falso; Antialias glicolico = vero; Premloboverso GLbooleanoAlpha = true; GLboolean preserveDrawingBuffer = false; GLboolean preferLowPowerToHighPerformance = false; GLboolean failIfMajorPerformanceCaveat = false; ;

Spiegherò alcuni di questi attributi mentre li usiamo. È possibile trovare ulteriori informazioni su di essi nella sezione Attributi di contesto WebGL delle specifiche WebGL. Per ora, non abbiamo bisogno di a buffer di profondità per il nostro semplice shader (ne parleremo più avanti). E per evitare di doverlo spiegare, disabiliteremo anche premoltiplicato-alfa! Ci vuole un articolo a parte per spiegare correttamente la logica dietro di esso. Quindi, il nostro getContext la linea diventa:

var contextAttributes = depth: false, premultipliedAlpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);

Nota: profondità, stampino e antialias attributi, se impostato su vero, sono richieste, non requisiti. Il browser dovrebbe cercare il meglio per soddisfarli, ma non è garantito. Tuttavia, quando sono impostati su falso, il browser deve rispettare.

D'altra parte, il alfa, premultipliedAlpha e preserveDrawingBuffer gli attributi sono requisiti che devono essere soddisfatti dal browser.

clearColor

Ora che abbiamo il nostro contesto WebGL, è ora di usarlo! Una delle operazioni di base nel disegno WebGL sta cancellando il buffer dei colori (o semplicemente la tela nella nostra situazione). La pulizia della tela viene eseguita in due passaggi:

  1. Impostazione del colore chiaro (può essere fatto solo una volta).
  2. In realtà cancellando la tela.

Le chiamate OpenGL / WebGL sono costose e i driver dei dispositivi non sono garantiti per essere terribilmente intelligenti ed evitare lavori inutili. Pertanto, come regola generale, se possiamo evitare di utilizzare l'API, dovremmo evitare di utilizzarlo. 

Quindi, a meno che non sia necessario cambiare il colore chiaro di ogni fotogramma o disegno a metà, dovremmo scrivere il codice impostandolo in una funzione di inizializzazione anziché in uno di disegno. In questo modo, viene chiamato solo una volta all'inizio e non con ogni frame. Dal momento che il clear-color non è l'unico variabile di stato che inizializzeremo, creeremo una funzione separata per l'inizializzazione dello stato:

function initializeState () ...

E chiameremo questa funzione dal inizializzare funzione:

function initialize () ... // Se fallito, if (! glContext) alert (errorMessage); restituisce falso;  initializeState (); ritorna vero; 

Bellissimo! La modularità manterrà il nostro codice non così breve più pulito e più leggibile. Ora per popolare il initializeState funzione:

function initializeState () // Imposta clear-color su red, glContext.clearColor (1.0, 0.0, 0.0, 1.0); 

clearColor accetta quattro parametri: rosso, verde, blu e alfa. Quattro galleggianti, i cui valori sono serrato all'intervallo [0, 1]. In altre parole, qualsiasi valore inferiore a 0 diventa 0, qualsiasi valore maggiore di 1 diventa 1 e qualsiasi valore compreso tra questi rimane invariato. Inizialmente, il clear-color è impostato su tutti gli zeri. Quindi, se il nero trasparente fosse ok con noi, avremmo potuto ometterlo del tutto.

Cancellare il Buffer del Disegno

Dopo aver impostato il colore chiaro, ciò che rimane è in realtà cancellare la tela. Ma non si può fare a meno di fare una domanda, dobbiamo assolutamente cancellare la tela?

Ai vecchi tempi, i giochi che eseguivano il rendering a schermo intero non avevano bisogno di cancellare lo schermo ogni fotogramma (prova a digitare idclip in DOOM 2 e vai da qualche parte che non dovresti essere!). I nuovi contenuti sovrascrivono solo quelli vecchi e salveremmo l'operazione non banale. 

Su hardware moderno, la cancellazione dei buffer è estremamente veloce. Inoltre, la cancellazione dei buffer può effettivamente migliorare le prestazioni! Per dirla semplicemente, se il contenuto del buffer non è stato cancellato, la GPU potrebbe dover recuperare i contenuti precedenti prima di sovrascriverli. Se sono stati cancellati, non è necessario recuperarli dalla memoria relativamente più lenta.

Ma cosa succede se non vuoi sovrascrivere l'intero schermo, ma aggiungerlo in modo incrementale? Come quando si realizza un programma di pittura. Si desidera disegnare solo i nuovi tratti, mantenendo quelli precedenti. Non ha senso l'atto di lasciare la tela senza schiarirsi?

La risposta è ancora no. Sulla maggior parte delle piattaforme che useresti doppio buffer. Ciò significa che tutto il disegno che eseguiamo è fatto su a back buffer mentre il monitor recupera i suoi contenuti da a buffer frontale. Durante la ritraccia verticale, questi buffer vengono scambiati. La parte posteriore diventa anteriore e quella anteriore diventa la parte posteriore. In questo modo evitiamo di scrivere sulla stessa memoria che viene attualmente letta dal monitor e visualizzata, evitando così artefatti dovuti a disegno incompleto o disegno troppo veloce (avendo disegnato diversi frame scritti mentre il monitor ne sta ancora tracciando uno solo).

Pertanto, il frame successivo non sovrascrive il frame corrente, perché non è scritto nello stesso buffer. Invece, sovrascrive quello che era nel buffer frontale prima di scambiare. Questo è l'ultimo frame. E qualunque cosa abbiamo disegnato in questa cornice non apparirà nel prossimo. Apparirà nel prossimo. Questa incoerenza tra i buffer causa uno sfarfallio normalmente non desiderato.

Ma questo avrebbe funzionato se stessimo usando una singola configurazione bufferizzata. In OpenGL su molte piattaforme, abbiamo il controllo esplicito sul buffering e lo swapping dei buffer, ma non su WebGL. Spetta al browser gestirlo da solo. 

Umm ... Forse non è il momento migliore, ma c'è una cosa che riguarda la pulizia del buffer di disegno che non ho menzionato prima. Se non lo chiariamo esplicitamente, ciò sarebbe implicitamente chiarito per noi! 

Ci sono solo tre funzioni di disegno in WebGL 1.0: chiaro, drawArrays, e drawElements. Solo se chiamiamo uno di questi nel buffer di disegno attivo (o se abbiamo appena creato il contesto o ridimensionato l'area di disegno), deve essere presentato al compositore di pagine HTML all'inizio della prossima operazione di compositing. 

Dopo il compositing, i buffer di disegno sono automaticamente cancellato. Il browser può essere intelligente ed evitare di cancellare automaticamente i buffer se li cancelliamo da soli. Ma il risultato finale è lo stesso; i respingenti saranno comunque cancellati.

La buona notizia è che c'è ancora un modo per far funzionare il tuo programma di disegno. Se insisti a fare disegni incrementali, possiamo impostare il preserveDrawingBuffer attributo contesto quando si acquisisce il contesto:

glContext = glCanvas.getContext ("webgl", preserveDrawingBuffer: true);

Ciò impedisce che il canvas venga automaticamente cancellato dopo il compositing e simula una singola configurazione bufferizzata. Un modo è fatto copiando il contenuto del buffer anteriore nel buffer posteriore dopo lo scambio. Il disegno su un back buffer è ancora necessario per evitare di disegnare artefatti, quindi non può essere aiutato. Questo, ovviamente, ha un prezzo. Potrebbe influire sulle prestazioni. Quindi, se possibile, utilizzare altri approcci per conservare il contenuto del buffer di disegno, come il disegno su a frame buffer object (che va oltre lo scopo di questo tutorial).

chiaro

Preparati, libereremo la tela da un momento all'altro! Ancora una volta, per modularità, scriviamo il codice che disegna la scena ogni fotogramma in una funzione separata:

function drawScene () // Cancella il buffer dei colori, glContext.clear (glContext.COLOR_BUFFER_BIT); 

Ora ce l'abbiamo fatta! chiaro accetta un parametro, un campo di bit che indica quali buffer devono essere cancellati. Risulta che di solito abbiamo bisogno di più di un semplice buffer di colori per disegnare oggetti 3D. Ad esempio, un buffer di profondità viene utilizzato per tenere traccia delle profondità di ogni pixel disegnato. Usando questo buffer, quando la GPU sta per disegnare un nuovo pixel, può facilmente decidere se questo pixel occlude o è occluso dal pixel precedente che risiede al suo posto. 

Va così:

  1. Calcola la profondità del nuovo pixel.
  2. Leggi la profondità del vecchio pixel dal buffer di profondità.
  3. Se la profondità del nuovo pixel è più vicino rispetto alla profondità del vecchio pixel, sovrascrivi il colore del pixel (o fondilo con esso) e imposta la sua profondità sulla nuova profondità. Altrimenti, elimina il nuovo pixel.

Ho usato "più vicino" anziché "più piccolo" perché abbiamo il controllo esplicito su funzione di profondità (quale operatore usare in confronto). Decidiamo se un valore maggiore significa un pixel più vicino (sistema di coordinate destrorso) o viceversa (mancino). 

La nozione di destrimano o mancino si riferisce alla direzione del pollice (asse z) mentre si piegano le dita dall'asse x all'asse y. Non sono bravo a disegnare, quindi, guarda questo articolo in Windows Dev Center. Per impostazione predefinita, WebGL è mancino, ma è possibile farlo destrimani modificando la funzione di profondità, purché si tenga conto dell'intervallo di profondità e delle trasformazioni necessarie.

Dato che abbiamo scelto di non avere un buffer di profondità quando abbiamo creato il nostro contesto, l'unico buffer che deve essere cancellato è il buffer dei colori. Quindi, impostiamo il COLOR_BUFFER_BIT. Se avessimo un buffer di profondità, avremmo invece fatto questo:

glContext.clear (glContext.COLOR_BUFFER_BIT | glContext.GL_DEPTH_BUFFER_BIT);

L'unica cosa rimasta è chiamare drawScene. Facciamolo subito dopo l'inizializzazione:

window.addEventListener ('load', function () // Inizializza tutto, initialize (); // Inizia a disegnare, drawScene ();, false);

Passare al Risultato scheda per vedere il nostro bel rosso chiaro!

Canvas-Compositing

Uno dei fatti importanti su chiaro è che non applica alcun alfa-compositing. Anche se usiamo esplicitamente un valore per alpha che lo rende trasparente, il clear-color verrebbe semplicemente scritto nel buffer senza alcun compositing, sostituendo tutto ciò che è stato disegnato prima. Pertanto, se hai una scena disegnata sulla tela e poi la si cancella con un colore trasparente, la scena verrà completamente cancellata. 

Tuttavia, il browser esegue ancora il compositing alfa per l'intero canvas e utilizza il valore alfa presente nel buffer dei colori, che avrebbe potuto essere impostato durante la cancellazione. Aggiungiamo un testo sotto la tela e poi cancelliamo con un colore rosso semitrasparente per vederlo in azione. Il nostro HTML sarebbe:

 

Shhh, mi sto nascondendo dietro la tela in modo che tu non possa vedermi.

e il chiaro la linea diventa:

// Imposta il colore da trasparente a rosso trasparente, glContext.clearColor (1.0, 0.0, 0.0, 0.5);

E ora, rivelano:

Guarda da vicino. Lassù nell'angolo in alto a sinistra ... non c'è assolutamente nulla! Certo che non puoi vedere il testo! È perché nel nostro CSS, abbiamo specificato # 000 come lo sfondo della tela. Lo sfondo agisce come un livello aggiuntivo sotto l'area di disegno, quindi il browser esegue il compositing alfa del buffer dei colori su di esso mentre nasconde completamente il testo. Per renderlo più chiaro, cambieremo lo sfondo in verde e vediamo cosa succede:

sfondo: # 0f0;

E il risultato:

Sembra ragionevole. Questo colore sembra essere rgb (128, 127, 0), che può essere considerato come il risultato della fusione di rosso e verde con alfa uguale a 0.5 (eccetto se si utilizza Microsoft Edge, in cui il colore deve essere rgb (255, 127, 0) perché al momento non supporta l'alpha premoltiplicato). Non possiamo ancora vedere il testo, ma almeno sappiamo come il colore di sfondo influisce sul nostro disegno.

Miscelazione alfa

Il risultato invita alla curiosità, però. Perché il rosso è stato dimezzato 128, mentre il verde era dimezzato 127? Non dovrebbero entrambi essere entrambi 128 o 127, in base all'arrotondamento in virgola mobile? L'unica differenza tra loro è che il colore rosso è stato impostato come il colore chiaro nel codice WebGL, mentre il colore verde è stato impostato nel CSS. Onestamente non so perché succede, ma ho una teoria. Probabilmente è a causa del funzione di fusione usato per unire i due colori.

Quando si disegna qualcosa di trasparente sopra qualcos'altro, si attiva la funzione di fusione. Definisce il colore finale del pixel (SU) deve essere calcolato dal livello in alto (livello sorgente, SRC) e il livello sottostante (livello di destinazione, DST). Quando si disegna con WebGL, abbiamo molte funzioni di fusione tra cui scegliere. Ma quando il browser compone alfa la tela con gli altri livelli, abbiamo solo due modalità (per ora): premoltiplicato-alfa e non premoltiplicato-alfa (chiamiamolo normale modalità). 

La normale modalità alfa è simile a:

OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ.SRCᴀ + DSTʀɢʙ.DSTᴀ (1 - SRCᴀ)

Nella modalità alfa premoltiplicata, si presume che i valori RGB siano già moltiplicati con i corrispondenti valori alfa (da cui il nome pre-moltiplicato). In tal caso, le equazioni sono ridotte a:

OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ + DSTʀɢʙ (1 - SRCᴀ)

Dal momento che non usiamo premoltiplied-alpha, ci basiamo sul primo gruppo di equazioni. Queste equazioni presuppongono che le componenti del colore siano valori in virgola mobile che vanno da 0 a 1. Ma questo non è il modo in cui sono effettivamente memorizzati nella memoria. Invece, sono valori interi che vanno da 0 a 255. Così srcAlpha (0.5) diventa 127 (o 128, basato su come lo arrotondate), e 1 - srcAlpha (1 - 0,5) diventa 128 (o 127). È per metà 255 (che è 127.5) non è un numero intero, quindi finiamo con uno dei livelli che perdono a 0.5 e l'altro guadagnando a 0.5 nei loro valori alfa. Caso chiuso!

Nota: l'alpha-compositing non deve essere confuso con le modalità di fusione CSS. Prima viene eseguito il compositing alfa e quindi il colore calcolato viene unito al livello di destinazione utilizzando le modalità di fusione.   

Torna al nostro testo nascosto. Proviamo a rendere lo sfondo trasparente-verde:

background: rgba (0, 255, 0, 0.5);

Finalmente:

Dovresti essere in grado di vedere il testo ora! È a causa di come questi strati sono dipinti l'uno sopra l'altro:

  1. Il testo viene disegnato per primo su uno sfondo bianco.
  2. Il colore di sfondo (che è trasparente) viene disegnato sopra di esso, risultando in uno sfondo verde-biancastro e un testo verdastro.
  3. Il buffer dei colori si fonde con il risultato, risultando nella ... cosa sopra. 

Doloroso, giusto? Fortunatamente, non dobbiamo occuparci di tutto questo se non vogliamo che la nostra tela sia trasparente! 

Disattivazione dell'alfa

var contextAttributes = depth: false, alpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);

Ora il nostro buffer di colori non avrà un canale alfa per iniziare! Ma questo non ci impedirebbe di disegnare cose trasparenti? 

La risposta è no. In precedenza, ho menzionato qualcosa su WebGL con funzioni di fusione flessibili indipendenti da come il browser unisce la tela con altri elementi di pagina. Se usiamo una funzione di fusione che si traduce in una miscelazione alfa premoltiplicata, allora non abbiamo assolutamente bisogno del canale alfa del buffer di disegno:

OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ + DSTʀɢʙ (1 - SRCᴀ) 

Se ci limitiamo a ignorare outAlpha nel complesso, non perdiamo davvero nulla. Tuttavia, qualunque cosa disegniamo ha ancora bisogno di un canale alfa per essere trasparente. È solo il buffer di disegno che ne manca uno.

Premultiplied-alpha funziona bene con il filtro della trama e altre cose, ma non con la maggior parte degli strumenti di manipolazione delle immagini (non abbiamo ancora discusso le trame - supponiamo che siano immagini che dobbiamo disegnare). La modifica di un'immagine che è memorizzata in modalità alfa premuscolata non è conveniente perché accumula errori di arrotondamento. Ciò significa che vogliamo mantenere le nostre trame non premolteplicate finché continuiamo a lavorarci. Quando è il momento di testare o rilasciare, dobbiamo:

  • Converti tutte le trame in premoltipliche-alfa prima di raggrupparle con l'applicazione.
  • Lascia le texture essere e convertirle al volo mentre le si carica.
  • Lascia le trame essere e fai in modo che WebGL si premoltiplichino per noi usando: