Erste Schritte in WebGL, Teil 3 WebGL-Kontext und Löschen

In den vorherigen Artikeln haben wir gelernt, wie Sie einfache Scheitelpunkte für Scheitelpunkte und Fragmente schreiben, eine einfache Webseite erstellen und eine Zeichenfläche für das Zeichnen vorbereiten. In diesem Artikel beginnen wir mit der Arbeit an unserem WebGL-Boilerplate-Code. 

Wir erhalten einen WebGL-Kontext und verwenden ihn, um die Leinwand mit der Farbe unserer Wahl zu löschen. Woohoo! Dies kann nur aus drei Zeilen Code bestehen, aber ich verspreche Ihnen, dass ich es Ihnen nicht so leicht mache! Wie üblich versuche ich, die kniffligen JavaScript-Konzepte zu erklären, während wir sie kennenlernen, und Ihnen alle Details zur Verfügung stellen, die Sie benötigen, um das entsprechende WebGL-Verhalten zu verstehen und vorherzusagen.

Dieser Artikel ist Teil der "Erste Schritte in WebGL" -Serie. Wenn Sie die vorherigen Teile nicht gelesen haben, empfehle ich Ihnen, sie zuerst zu lesen:

  1. Einführung in die Shader
  2. Das Canvas-Element

Rekapitulieren

Im ersten Artikel dieser Serie haben wir einen einfachen Shader geschrieben, der einen farbigen Verlauf zeichnet und ihn leicht ein- und ausblendet. Hier ist der Shader, den wir geschrieben haben:

Im zweiten Artikel dieser Serie haben wir begonnen, diesen Shader in einer Webseite zu verwenden. In kleinen Schritten erklärten wir den notwendigen Hintergrund des Canvas-Elements. Wir:

  • eine einfache Seite gemacht
  • ein Canvas-Element hinzugefügt
  • hat einen 2D-Kontext zum Rendern auf die Leinwand erworben
  • benutzte den 2D-Kontext, um eine Linie zu zeichnen
  • behandelte Probleme bei der Größenänderung von Seiten
  • behandelte Probleme mit der Pixeldichte

Was wir bisher gemacht haben:

In diesem Artikel leihen wir uns Code aus dem vorherigen Artikel und passen unsere Erfahrung an WebGL an, anstatt an 2D-Zeichnungen. Im nächsten Artikel - wenn Allah es will - werde ich die Handhabung des Viewports und das Beschneiden von Primitiven behandeln. Es dauert eine Weile, aber ich hoffe, dass Sie die gesamte Serie sehr nützlich finden werden!

Ersteinrichtung

Lassen Sie uns eine WebGL-basierte Seite erstellen. Wir werden den gleichen HTML-Code verwenden, den wir für das 2D-Zeichnungsbeispiel verwendet haben:

        

… Mit einer sehr kleinen Modifikation. Hier nennen wir die Leinwand glCanvas statt nur Segeltuch (meh!).

Wir werden auch das gleiche CSS verwenden:

html, Körper Höhe: 100%;  body margin: 0;  Zeichenfläche Anzeige: Block; Breite: 100%; Höhe: 100%; Hintergrund: # 000; 

Außer der Hintergrundfarbe, die jetzt schwarz ist.

Wir verwenden nicht denselben JavaScript-Code. Wir beginnen ganz ohne JavaScript-Code und fügen die Funktionalität Stück für Stück hinzu, um Verwirrung zu vermeiden. Hier ist unser bisheriges Setup:

Jetzt schreiben wir etwas Code!

WebGL-Kontext

Als erstes sollten wir einen WebGL-Kontext für die Leinwand erhalten. Genau wie bei der Erstellung eines 2D-Zeichnungskontexts verwenden wir die Member-Funktion getContext:

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

Diese Zeile enthält zwei getContext Anrufe. Normalerweise sollten wir den zweiten Anruf nicht brauchen. Nur für den Fall, dass der Benutzer einen alten Browser verwendet, in dem die WebGL-Implementierung noch experimentell ist (oder Microsoft Edge), haben wir den zweiten hinzugefügt. 

Das Coole an der || Operator (oder Betreiber) Ist, dass nicht der gesamte Ausdruck ausgewertet werden muss, wenn der erste Operand gefunden wurde wahr. Mit anderen Worten, in einem Ausdruck a || b, ob ein bewertet zu wahr, dann ob b ist wahr oder falsch beeinflusst das Ergebnis überhaupt nicht. Daher müssen wir nicht bewerten b und es wird völlig übersprungen. Das nennt man Kurzschlussbewertung.

In unserem Fall, getContext ("experimental-webgl") wird nur ausgeführt wenn getContext ("webgl") schlägt fehl (kehrt zurück Null, das bewertet zu falsch in einem logischen Ausdruck). 

Wir haben auch eine andere Funktion der verwendet oder Operator. Das Ergebnis von or-ing ist weder wahr Noch falsch. Stattdessen ist es das erste Objekt, das ausgewertet wird wahr. Wenn keines der Objekte ausgewertet wird wahr, oder Rückgabe das am weitesten rechts liegende Objekt im Ausdruck. Dies bedeutet, nachdem die obige Zeile ausgeführt wurde, glContext enthält entweder ein Kontextobjekt oder Null, aber nicht wahr oder falsch.

Hinweis: Wenn der Browser beide Modi unterstützt (webgl und experimentell-webgl) dann werden sie als Aliase behandelt. Es würde absolut keinen Unterschied zwischen ihnen geben.

Die obige Zeile dort einfügen, wo sie hingehört:

var glContext; function initialize () // WebGL-Kontext abrufen, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ("experimental-webgl"); if (! glContext) alert ("Fehler beim Abrufen eines WebGL-Kontexts. Entschuldigung!"); falsch zurückgeben;  return true; 

Voila! Wir haben unsere initialisieren Funktion (ja, träum weiter!).

Behandlung von getContext-Fehlern

Beachten Sie, dass wir nicht genutzt haben Versuchen und Fang zu erkennen getContext Probleme wie im vorigen Artikel. Dies liegt daran, dass WebGL über eigene Fehlerberichterstattungsmechanismen verfügt. Es wird keine Ausnahme ausgelöst, wenn die Kontexterstellung fehlschlägt. Stattdessen feuert es a webglcontextcreationerror Veranstaltung. Wenn wir an der Fehlermeldung interessiert sind, sollten wir dies wahrscheinlich tun:

// Fehler beim Erstellen des Kontexterstellungsfehlers, var errorMessage = "WebGL-Kontext konnte nicht erstellt werden"; Funktion onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage;  glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

Diese Zeilen auseinander nehmen:

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

So wie wir dem Fensterladeereignis im vorherigen Artikel einen Listener hinzugefügt haben, haben wir der Leinwand einen Listener hinzugefügt webglcontextcreationerror Veranstaltung. Das falsch Argument ist optional; Ich füge es nur der Vollständigkeit halber hinzu (da dies im Beispiel der WebGL-Spezifikation enthalten ist). Es ist normalerweise für die Rückwärtskompatibilität enthalten. Es steht für useCapture. Wann wahr, es bedeutet, dass der Listener im aufgerufen wird Erfassungsphase der Ereignisausbreitung. Ob falsch, es wird im gerufen werden sprudelnde Phase stattdessen. In diesem Artikel finden Sie weitere Informationen zur Weitergabe von Ereignissen.

Nun zum Hörer selbst:

var errorMessage = "WebGL-Kontext konnte nicht erstellt werden"; Funktion onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage; 

In diesem Listener behalten wir eine Kopie der Fehlermeldung, falls vorhanden. Ja, eine Fehlermeldung ist absolut optional:

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

Was wir hier gemacht haben, ist ziemlich interessant. Fehlermeldung wurde außerhalb der Funktion deklariert, wir haben sie jedoch innen verwendet. Dies ist in JavaScript möglich und wird aufgerufen Verschlüsse. Was an Schließungen interessant ist, ist ihre Lebensdauer. Während Fehlermeldung ist lokal für die initialisieren Funktion, da es innen verwendet wurde onContextCreationError, es wird nicht zerstört, es sei denn onContextCreationError auf sich selbst wird nicht mehr verwiesen.

Mit anderen Worten, Solange ein Bezeichner noch verfügbar ist, kann er nicht mit Müll gesammelt werden. In unserer Situation:

  • Fehlermeldung lebt weil onContextCreationError verweist darauf.
  • onContextCreationError lebt, weil es irgendwo unter den Zuhörern der Leinwandereignisse referenziert wird. 

Also auch wenn initialisieren endet, onContextCreationError wird immer noch irgendwo im Canvas-Objekt referenziert. Nur wenn es losgelassen wird Fehlermeldung Müll gesammelt werden. Darüber hinaus nachfolgende Aufrufe von initialisieren wirkt sich nicht auf den vorherigen aus Fehlermeldung. Jeden initialisieren Funktionsaufruf wird seine eigenen haben Fehlermeldung und onContextCreationError.

Aber wir wollen nicht wirklich onContextCreationError jenseits leben initialisieren Beendigung. Wir möchten nicht auf andere Versuche achten, WebGL-Kontexte an anderer Stelle im Code zu erhalten. So:

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

Alles zusammenstellen:

Um zu überprüfen, ob wir den Kontext erfolgreich erstellt haben, habe ich eine einfache hinzugefügt warnen:

alert ("WebGL-Kontext wurde erfolgreich erstellt!");

Wechseln Sie jetzt zu Ergebnis Registerkarte, um den Code auszuführen.

Und es geht nicht! Offensichtlich weil initialisieren wurde nie gerufen. Wir müssen es direkt nach dem Laden der Seite aufrufen. Dazu fügen wir diese Zeilen darüber hinzu:

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

Lass es uns erneut versuchen:

Es klappt! Ich meine, es sollte, wenn kein Kontext erstellt werden könnte! Wenn dies nicht der Fall ist, stellen Sie sicher, dass Sie diesen Artikel von einem WebGL-fähigen Gerät / Browser aus anzeigen.

Beachten Sie, dass wir hier eine andere interessante Sache gemacht haben. Wir verwendeten initialisieren in unserer Belastung Zuhörer, bevor es überhaupt erklärt wurde. Dies ist in JavaScript aufgrund von möglich Heben. Heben bedeutet, dass alle Deklarationen an die Spitze ihres Gültigkeitsbereichs verschoben werden, während ihre Initialisierungen an ihrer Stelle bleiben.

Wäre es nicht schön zu testen, ob unser Fehlerberichterstattungsmechanismus tatsächlich funktioniert? Wir brauchen getContext Versagen. Ein einfacher Weg, dies zu tun, besteht darin, zunächst einen anderen Kontext für die Zeichenfläche zu erhalten, bevor Sie versuchen, den WebGL-Kontext zu erstellen getContext ändert den Canvas-Modus dauerhaft?). Wir fügen diese Zeile unmittelbar vor dem WebGL-Kontext hinzu:

glCanvas.getContext ("2d");

Und:

Großartig! Nun, ob die Nachricht, die Sie gesehen haben, "Konnte keinen WebGL-Kontext erstellen"oder so ähnlich"Canvas hat einen vorhandenen Kontext eines anderen Typs"hängt davon ab, ob Ihr Browser dies unterstützt webglcontextcreationerror oder nicht. Zum Zeitpunkt der Erstellung dieses Artikels unterstützen Edge und Firefox ihn nicht (dies war für Firefox 49 geplant, funktioniert aber bei Firefox 50.1 nicht). In diesem Fall wird der Ereignis-Listener nicht aufgerufen und Fehlermeldung bleibt eingestellt auf "Konnte keinen WebGL-Kontext erstellen"Zum Glück, getContext kehrt immer noch zurück Null, Wir wissen also, dass wir den Kontext nicht erstellen konnten. Wir haben einfach keine detaillierte Fehlermeldung.

Die Sache mit den WebGL-Fehlermeldungen ist, dass es keine WebGL-Fehlermeldungen gibt! WebGL gibt Zahlen zurück, die Fehlerzustände anzeigen, nicht Fehlermeldungen. Wenn Fehlermeldungen auftreten, sind sie vom Treiber abhängig. Der genaue Wortlaut der Fehlermeldungen ist nicht in der Spezifikation enthalten. Es liegt an den Treiberentwicklern, zu entscheiden, wie sie es setzen sollen. Erwarten Sie also, dass derselbe Fehler auf verschiedenen Geräten unterschiedlich dargestellt wird.

OK dann. Da wir sichergestellt haben, dass unser Fehlerberichterstattungsmechanismus funktioniert,Erfolgreich erstellt"Alarm und getContext ("2d") werden nicht mehr benötigt. Wir werden sie weglassen.

Kontextattribute

Zurück zu unseren verehrten getContext Funktion:

glContext = glCanvas.getContext ("webgl");

Es ist mehr als es auf den ersten Blick erscheint. getContext kann optional ein weiteres Argument annehmen: ein Wörterbuch, das eine Reihe von Kontextattributen und deren Werte enthält. Wenn keine angegeben ist, werden die Standardwerte verwendet:

dictionary WebGLContextAttributes GLboolean alpha = true; GLboolesche Tiefe = wahr; GLboolesche Schablone = falsch; GLboolesche Antialias = true; GLboolean premultipliedAlpha = true; GLboolean preserveDrawingBuffer = false; GLboolean PreferLowPowerToHighPerformance = false; GLboolean failIfMajorPerformanceCaveat = false; ;

Ich werde einige dieser Eigenschaften erklären, wenn wir sie verwenden. Weitere Informationen hierzu finden Sie im Abschnitt WebGL-Kontextattribute der WebGL-Spezifikation. Für den Moment brauchen wir keine Tiefenpuffer für unseren einfachen Shader (dazu später mehr). Und um es nicht erklären zu müssen, werden wir es auch deaktivieren vormultipliziert-alpha! Es bedarf eines eigenen Artikels, um die Gründe dafür richtig zu erklären. Also unser getContext Zeile wird zu:

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

Hinweis: Tiefe, Schablone und Antialias Attribute, wenn gesetzt wahr, sind Anfragen, keine Anforderungen. Der Browser sollte versuchen, diese zu befriedigen, dies kann jedoch nicht garantiert werden. Wenn sie jedoch eingestellt sind falsch, Der Browser muss einhalten.

Auf der anderen Seite die Alpha, premultipliedAlpha und preserveDrawingBuffer Attribute sind Anforderungen, die der Browser erfüllen muss.

klare Farbe

Jetzt, da wir unseren WebGL-Kontext haben, ist es an der Zeit, ihn tatsächlich zu verwenden! Eine der grundlegenden Operationen in der WebGL-Zeichnung ist das Löschen des Farbpuffers (oder einfach der Leinwand in unserer Situation). Das Löschen der Leinwand erfolgt in zwei Schritten:

  1. Einstellen der Farbe (kann nur einmal ausgeführt werden).
  2. Tatsächlich die Leinwand löschen.

OpenGL / WebGL-Aufrufe sind teuer, und es ist nicht garantiert, dass die Gerätetreiber schlau sind und unnötige Arbeit vermeiden. Als Faustregel gilt: Wenn wir die Verwendung der API vermeiden können, sollten wir sie vermeiden. 

Wenn wir also nicht bei jedem Frame oder in der Zeichnung die klare Farbe ändern müssen, sollten wir den Code in eine Initialisierungsfunktion und nicht in eine Zeichnungsfunktion schreiben. Auf diese Weise wird es nur einmal am Anfang und nicht bei jedem Frame aufgerufen. Da ist die klare Farbe nicht die einzige Zustandsvariable dass wir initialisieren werden, erstellen wir eine separate Funktion für die Statusinitialisierung:

Funktion initializeState () …

Und wir rufen diese Funktion aus dem initialisieren Funktion:

function initialize () … // Wenn fehlgeschlagen, if (! glContext) alert (errorMessage); falsch zurückgeben;  initializeState (); wahr zurückgeben; 

Wunderschönen! Modularität wird unseren nicht so kurzen Code sauberer und lesbarer halten. Jetzt das bevölkern initializeState Funktion:

function initializeState () // Setzen Sie die Farbe auf Rot, glContext.clearColor (1.0, 0.0, 0.0, 1.0); 

klare Farbe Es gibt vier Parameter: Rot, Grün, Blau und Alpha. Vier Floats, deren Werte sind geklemmt im Bereich [0, 1]. Mit anderen Worten, jeder Wert unter 0 wird 0, ein Wert größer als 1 wird 1 und jeder Wert dazwischen bleibt unverändert. Anfangs wird die klare Farbe auf alle Nullen gesetzt. Wenn also transparentes Schwarz bei uns in Ordnung wäre, hätten wir dies ganz weglassen können.

Den Zeichnungspuffer löschen

Nachdem Sie eine klare Farbe eingestellt haben, müssen Sie die Leinwand tatsächlich löschen. Aber man kann nicht umhin, eine Frage zu stellen, müssen wir die Leinwand überhaupt löschen?

In früheren Zeiten mussten Spiele, die Vollbild-Rendering durchführen, den Bildschirm nicht bei jedem Bild löschen (versuchen Sie es einmal mit der Eingabe) idclip in DOOM 2 und geh irgendwohin, wo du nicht sein solltest!). Der neue Inhalt würde nur die alten überschreiben, und wir würden den nicht trivialen klaren Vorgang speichern. 

Auf moderner Hardware ist das Löschen der Puffer extrem schnell. Darüber hinaus kann das Löschen der Puffer die Leistung tatsächlich verbessern! Einfach ausgedrückt: Wenn der Pufferinhalt nicht gelöscht wurde, muss die GPU möglicherweise den vorherigen Inhalt abrufen, bevor sie überschrieben wird. Wenn sie gelöscht wurden, müssen sie nicht aus dem relativ langsameren Speicher abgerufen werden.

Was aber, wenn Sie nicht den gesamten Bildschirm überschreiben möchten, sondern schrittweise hinzufügen möchten? Wie bei einem Malprogramm. Sie möchten nur die neuen Striche zeichnen, während Sie die vorherigen beibehalten. Ist das Verlassen der Leinwand ohne Löschen jetzt nicht sinnvoll??

Die Antwort ist immer noch nein. Auf den meisten Plattformen, die Sie verwenden würden Doppelpufferung. Dies bedeutet, dass alle Zeichnungen, die wir ausführen, auf einer Zeichnung ausgeführt werden Puffer zurück während der Monitor seinen Inhalt von a abruft Frontpuffer. Während des vertikalen Rücklaufs werden diese Puffer ausgetauscht. Die Rückseite wird zur Vorderseite und die Vorderseite zur Rückseite. Auf diese Weise vermeiden wir, in denselben Speicher zu schreiben, der gerade vom Monitor gelesen und angezeigt wird. Dadurch werden Artefakte durch unvollständiges Zeichnen oder zu schnelles Zeichnen vermieden..

Daher überschreibt der nächste Frame den aktuellen Frame nicht, da er nicht in denselben Puffer geschrieben wird. Stattdessen überschreibt es denjenigen, der sich vor dem Tauschen im vorderen Puffer befand. Das ist das letzte Bild. Was auch immer wir in diesem Rahmen gezeichnet haben, wird im nächsten nicht angezeigt. Es erscheint im nächsten und nächsten. Diese Inkonsistenz zwischen Puffern führt zu einem normalerweise unerwünschten Flackern.

Dies hätte jedoch funktioniert, wenn wir ein einzelnes gepuffertes Setup verwendet hätten. In OpenGL haben wir auf den meisten Plattformen eine explizite Steuerung der Pufferung und des Austauschs der Puffer, nicht jedoch in WebGL. Es ist Sache des Browsers, selbstständig damit umzugehen. 

Ähm… Vielleicht ist es nicht die beste Zeit, aber es gibt eine Sache, den Zeichenpuffer zu löschen, den ich vorher nicht erwähnt habe. Wenn wir es nicht ausdrücklich klarstellen, würde dies für uns implizit geklärt werden! 

In WebGL 1.0 gibt es nur drei Zeichenfunktionen: klar, DrawArrays, und drawElements. Nur wenn wir eines davon im aktiven Zeichenpuffer aufrufen (oder wenn wir gerade den Kontext erstellt oder die Leinwand verkleinert haben), muss es dem HTML-Seiten-Compositor zu Beginn des nächsten Compositing-Vorgangs angezeigt werden. 

Nach dem Zusammensetzen befinden sich die Zeichenpuffer wird automatisch gelöscht. Der Browser darf intelligent sein und das automatische Löschen der Puffer vermeiden, wenn wir sie selbst löschen. Aber das Endergebnis ist dasselbe; Die Puffer werden trotzdem gelöscht.

Die gute Nachricht ist, dass es immer noch eine Möglichkeit gibt, Ihr Malprogramm zum Laufen zu bringen. Wenn Sie darauf bestehen, inkrementelle Zeichnungen zu erstellen, können Sie das festlegen preserveDrawingBuffer Kontextattribut beim Erfassen des Kontexts:

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

Dies verhindert, dass die Leinwand nach dem Compositing automatisch gelöscht wird, und simuliert ein einzelnes gepuffertes Setup. Eine Möglichkeit besteht darin, den Inhalt des Frontpuffers nach dem Tauschen in den Backpuffer zu kopieren. Das Zeichnen in einen Back-Buffer ist immer noch erforderlich, um das Zeichnen von Artefakten zu vermeiden. Dies hat natürlich einen Preis. Dies kann die Leistung beeinträchtigen. Verwenden Sie nach Möglichkeit andere Methoden, um den Inhalt des Zeichenpuffers zu erhalten, z. B. das Zeichnen in a Frame Buffer Objekt (was über den Rahmen dieses Tutorials hinausgeht).

klar

Machen Sie sich bereit, wir werden jetzt jeden Moment die Leinwand räumen! Lassen Sie uns der Modularität halber den Code schreiben, der die Szene für jedes Bild in einer separaten Funktion zeichnet:

function drawScene () // Lösche den Farbpuffer, glContext.clear (glContext.COLOR_BUFFER_BIT); 

Jetzt haben wir es geschafft! klar nimmt einen Parameter, ein Bitfeld, das angibt, welche Puffer gelöscht werden sollen. Es stellt sich heraus, dass wir normalerweise mehr als nur einen Farbpuffer benötigen, um 3D-Sachen zu zeichnen. Beispielsweise wird ein Tiefenpuffer verwendet, um die Tiefen jedes gezeichneten Pixels zu verfolgen. Wenn die GPU im Begriff ist, ein neues Pixel zu zeichnen, kann sie mit Hilfe dieses Puffers leicht entscheiden, ob dieses Pixel von dem vorherigen Pixel, das sich an seiner Stelle befindet, verdeckt oder von diesem verdeckt wird. 

Es geht so:

  1. Berechnen Sie die Tiefe des neuen Pixels.
  2. Lesen Sie die Tiefe des alten Pixels aus dem Tiefenpuffer.
  3. Wenn die Tiefe des neuen Pixels ist näher als die Tiefe des alten Pixels überschreiben, überschreiben Sie die Farbe des Pixels (oder mischen Sie mit dem Pixel) und stellen Sie seine Tiefe auf die neue Tiefe ein. Andernfalls verwerfen Sie das neue Pixel.

Ich habe "näher" anstelle von "kleiner" verwendet, weil wir die explizite Kontrolle über die Tiefenfunktion (welcher Operator im Vergleich verwendet werden soll). Wir können entscheiden, ob ein größerer Wert ein näheres Pixel (rechtshändiges Koordinatensystem) oder umgekehrt (linkshändig) bedeutet.. 

Der Begriff Rechts- oder Linkshändigkeit bezieht sich auf die Richtung Ihres Daumens (Z-Achse), wenn Sie Ihre Finger von der X-Achse zur Y-Achse bewegen. Ich bin schlecht beim Zeichnen, also schauen Sie sich diesen Artikel im Windows Dev Center an. WebGL ist standardmäßig Linkshänder. Sie können jedoch durch Ändern der Tiefenfunktion Rechtshänder erstellen, solange Sie den Tiefenbereich und die erforderlichen Transformationen berücksichtigen.

Da wir beim Erstellen unseres Kontexts keinen Tiefenpuffer ausgewählt haben, muss der einzige Puffer, der gelöscht werden muss, der Farbpuffer sein. Also setzen wir die COLOR_BUFFER_BIT. Wenn wir einen Tiefenpuffer hätten, hätten wir dies stattdessen getan:

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

Das einzige, was bleibt, ist anzurufen drawScene. Machen wir es gleich nach der Initialisierung:

window.addEventListener ('load', function () // Alles initialisieren, initialize (); // Zeichne beginnen, drawScene ();, false);

Wechseln Sie zu Ergebnis Tab um unsere schöne rote Farbe zu sehen!

Canvas-Alpha-Compositing

Eine der wichtigsten Fakten über klar ist, dass es kein Alpha-Compositing anwendet. Selbst wenn wir explizit einen Wert für Alpha verwenden, der ihn transparent macht, wird die klare Farbe ohne Compositing in den Puffer geschrieben und ersetzt alles, was zuvor gezeichnet wurde. Wenn Sie also eine Szene auf der Leinwand gezeichnet haben und dann mit einer transparenten Farbe löschen, wird die Szene vollständig gelöscht. 

Der Browser führt jedoch weiterhin Alpha-Compositing für die gesamte Leinwand durch und verwendet den im Farbpuffer vorhandenen Alpha-Wert, der beim Löschen möglicherweise festgelegt wurde. Fügen Sie etwas Text unterhalb der Leinwand hinzu und löschen Sie ihn mit einer halbtransparenten roten Farbe, um ihn in Aktion zu sehen. Unser HTML wäre:

 

Shhh, ich verstecke mich hinter der Leinwand, damit du mich nicht sehen kannst.

und das klar Zeile wird zu:

// Klarfarbe auf transparentes Rot setzen, glContext.clearColor (1.0, 0.0, 0.0, 0.5);

Und jetzt die Enthüllung:

Schau genau. Oben in der oberen linken Ecke… da ist absolut nichts! Natürlich können Sie den Text nicht sehen! Das liegt daran, dass wir in unserem CSS angegeben haben # 000 als Hintergrund der Leinwand. Der Hintergrund fungiert als zusätzliche Ebene unter der Leinwand, sodass der Browser den Farbpuffer mit einem Alpha-Composite-Hintergrund erstellt, während der Text vollständig ausgeblendet wird. Um dies klarer zu machen, ändern wir den Hintergrund in Grün und sehen, was passiert:

Hintergrund: # 0f0;

Und das Ergebnis:

Sieht vernünftig aus Diese Farbe scheint zu sein rgb (128, 127, 0), Dies kann als Ergebnis des Mischens von Rot und Grün mit Alpha gleich 0,5 betrachtet werden (außer wenn Sie Microsoft Edge verwenden, in dem die Farbe stehen sollte rgb (255, 127, 0) weil es vorläufig nicht unterstützt wird. Wir können den Text immer noch nicht sehen, aber zumindest wissen wir, wie die Hintergrundfarbe unsere Zeichnung beeinflusst.

Alpha Blending

Das Ergebnis lädt jedoch zur Neugierde ein. Warum wurde Rot halbiert? 128, während grün halbiert wurde 127? Sollten sie nicht beide sein? 128 oder 127, abhängig von der Gleitpunktrundung? Der einzige Unterschied besteht darin, dass die rote Farbe im WebGL-Code als klare Farbe festgelegt wurde, während die grüne Farbe im CSS festgelegt wurde. Ich weiß ehrlich nicht, warum das so ist, aber ich habe eine Theorie. Es ist wahrscheinlich wegen der Mischfunktion verwendet, um die beiden Farben zusammenzuführen.

Wenn Sie etwas transparentes auf etwas anderes zeichnen, setzt die Mischfunktion ein. Sie bestimmt, wie die endgültige Farbe des Pixels (AUS) ist von der obersten Ebene (Quellebene) zu berechnen, SRC) und die darunter liegende Ebene (Zielebene), DST). Beim Zeichnen mit WebGL haben wir viele Blending-Funktionen zur Auswahl. Wenn der Browser jedoch die Leinwand mit den anderen Ebenen alpha-zusammengesetzt hat, haben wir nur zwei Modi (vorerst): vormultipliziert-alpha und nicht premultiplied-alpha (nennen wir es normal Modus). 

Der normale Alpha-Modus sieht folgendermaßen aus:

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

Es wird angenommen, dass im vormultiplizierten Alpha-Modus die RGB-Werte bereits mit ihren entsprechenden Alpha-Werten (daher der Name) multipliziert werden vor multipliziert). In diesem Fall reduzieren sich die Gleichungen auf:

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

Da wir kein Premultiplied-Alpha verwenden, verlassen wir uns auf den ersten Satz von Gleichungen. Diese Gleichungen setzen voraus, dass die Farbkomponenten Fließkommawerte sind, die von reichen 0 zu 1. Aber so werden sie nicht wirklich gespeichert. Sie sind stattdessen ganzzahlige Werte, die von reichen 0 zu 255. So srcAlpha (0,5) wird 127 (oder 128, je nachdem, wie Sie es abrunden), und 1 - srcAlpha (1 - 0,5) wird 128 (oder 127). Es ist wegen der Hälfte 255 (welches ist 127,5) ist keine ganze Zahl, so dass am Ende eine der Ebenen a verliert 0,5 und der andere gewinnt a 0,5 in ihren Alpha-Werten. Fall abgeschlossen!

Hinweis: Alpha-Compositing darf nicht mit den CSS-Mischmodi verwechselt werden. Das Alpha-Compositing wird zuerst ausgeführt, und dann wird die berechnete Farbe mit der Zielebene unter Verwendung der Mischmodi gemischt.   

Zurück zu unserem versteckten Text. Versuchen wir, den Hintergrund transparent zu machen:

Hintergrund: rgba (0, 255, 0, 0,5);

Endlich:

Sie sollten jetzt den Text sehen können! Dies liegt daran, wie diese Ebenen übereinander gemalt werden:

  1. Der Text wird zuerst auf einen weißen Hintergrund gezeichnet.
  2. Die Hintergrundfarbe (die transparent ist) wird darüber gezeichnet, was einen weißlich grünen Hintergrund und einen grünlichen Text ergibt.
  3. Der Farbpuffer wird mit dem Ergebnis gemischt, was zu dem oben genannten… Ergebnis führt. 

Schmerzhaft, richtig? Glücklicherweise müssen wir uns nicht mit all dem auseinandersetzen, wenn unsere Leinwand nicht transparent sein soll! 

Alpha deaktivieren

var contextAttributes = Tiefe: falsch, Alpha: falsch; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);

Nun hat unser Farbpuffer keinen Alphakanal zum Start! Aber das würde uns nicht daran hindern, transparentes Material zu zeichnen? 

Die Antwort ist nein. Vorhin habe ich etwas über WebGL erwähnt, das flexible Mischfunktionen hat, unabhängig davon, wie der Browser die Leinwand mit anderen Seitenelementen mischt. Wenn wir eine Blending-Funktion verwenden, die zu einem Premultiplied-Alpha-Blending führt, brauchen wir absolut keinen Zeichenpuffer-Alphakanal:

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

Wenn wir es einfach ignorieren outAlpha Insgesamt verlieren wir nichts. Was auch immer wir zeichnen, benötigt jedoch einen Alphakanal, um transparent zu sein. Nur dem Zeichenpuffer fehlt ein.

Premultiplied-alpha spielt gut mit Texturfiltern und anderen Dingen, aber nicht