Herzlich Willkommen zu unserem achten Teil unserer WebGL Grundlagen. In diesem Teil thematisieren wir das Blending. Daneben werden wir noch erklären, wie der depth buffer funktioniert.
So wird das Ergebnis nach der entsprechenden Umsetzung aussehen:
Ein wichtiger Hinweis: Dieser Teil richtet sich an Personen mit fortgeschrittenen Programmierkenntnissen, aber ohne wirkliche Erfahrung mit 3D-Grafiken. Sie werden ein gutes Verständnis für den Code mitbringen und es zum Laufen bringen, so dass Sie so schnell wie möglich mit der Erstellung einer eigenen Website in 3D beginnen können.
Es existieren zwei verschiedene Möglichkeiten, den Code für dieses Beispiel zu erhalten. In den Browsereinstellungen mit „View Source“, während Sie sich die Live-Version ansehen oder wenn Sie GitHub verwenden, können Sie es aus dem Repository kopieren. Unabhängig davon für welchen Weg Sie sich entscheiden, sollten Sie ihren bevorzugten Texteditor herunterladen und Sie den Code genau ansehen.
Aber bevor wir mit dem Code beginnen, gibt es noch ein wenig Theorie. Zunächst klären wir die Frage, was unter Blending eigentlich genau zu verstehen ist. Um dies bewerkstelligen zu können, gehen wir im Folgenden ein wenig näher auf den Bepth Buffer ein.
Der Depth Buffer
Der Frame Puffer ist letztendlich das, was angezeigt wird. Aber was passiert, wenn man zwei Dinge zeichnet? Was passiert z.B. wenn Sie ein Quadrat zeichnen, dessen Mittelpunkt bei (0, 0, -5) und dann ein weiteres mit der gleichen Größe bei (0, 0, -10) liegt? Sie möchten nicht, dass das zweite Quadrat das erste überschreibt, da es deutlich weniger entfernt ist und ausgeblendet werden sollte.
Die Art und Weise zur Handhabung derartiger Probleme greift WebGL auf den Depth Buffer zurück. Nachdem die Fragmente in den Frame Buffer geschrieben werden, speichert der Depth Buffer neben den normalen RGBA-Farbwerten auch einen Tiefenwert, welcher sich auf den Z-Wert bezieht, der dem Fragment zugeordnet ist, aber nicht exakt mit dem Z-Wert übereinstimmt.
Was ich mit verwandt meine? WebGL mag es, wenn alle Z-Werte im Bereich von 0 bis 1 liegen, mit 0 am nächsten und 1 am weitesten entfernt. Das alles verbirgt sich hinter der Projektionsmatrix, die wir mit unserem Aufruf zur Perspektive am Anfang von drawScene erstellen. Fürs erste müssen Sie nur wissen, dass je größer der Z-Puffer-Wert ist, desto weiter entfernt ist etwas. Also völlig gegensätzlich zu unseren normalen Koordinaten.
Ok, das ist der Depth Buffer. Erinnern Sie sich daran, dass wir ganz zu Beginn die folgende Code-Zeile zur Initialisierung unseres WebGL-Kontextes verwendet haben:
Dies ist eine Anweisung an WebGL, was in dem Fall zu tun ist, wenn ein neues Fragment in den Frame Buffer geschrieben wird und bedeutet im Grunde genommen, dass auf den Depth Buffer geachtet werden sollte. Es wird in Kombination mit einer anderen WebGL-Konfiguration, der Depth Function, verwendet. Die Depth Function hat eigentlich standardmäßig einen vernünftigen Wert, aber wenn wir es aktiv auf den Standardwert setzen, würde es folgendermaßen aussehen:
Die Code-Zeile sagt aus, dass wenn ein Fragment einen Z-Wert hat, welcher kleiner als der aktuelle Wert ist, dass dann der neue Wert verwendet werden soll und nicht der alte. Dieser Test allein in Kombination mit dem Code ist völlig ausreichend, um ein vernünftiges Verhalten zu vermitteln. Dinge, die nahe beieinander liegen, verschleiern Dinge, die weiter entfernt sind.
Blending.
Das Blending ist einfach eine Alternative zu diesem Verfahren. Bei dem Depth Buffer verwenden wir die Depth Function, um zu bestimmen, ob das vorhandene Fragment durch das neue ersetzt werden soll oder nicht. Beim Blending verwenden wir eine Blend Function, um die Farben der vorhandenen und der neuen Fragmente zu einem völlig neuen Fragment zu kombinieren, welches wir dann in den Puffer schreiben.
Schauen wir uns nun den Code an. Fast alles ist so wie im Beitrag 7 und fast alles Wichtige ist in einem kurzen Abschnitt in drawScene. Zuerst prüfen wir, ob das Kontrollkästchen „Blending“ aktiviert ist.
Wir stellen die Funktion ein, mit der die beiden Fragmente kombiniert werden sollen:
Die Parameter dieser Funktion legen fest, wie das Blending durchgeführt wird. Das ist knifflig, aber nicht schwierig. Zuerst definieren wir zwei Begriffe: Das Source Fragment ist dasjenige, welches wir gerade zeichnen und das Ziel Fragment ist dasjenige, das sich bereits im Frame Buffer befindet. Der erste Parameter der gl.blendFunc bestimmt den Quellfaktor und der zweite den Zielfaktor. Diese Faktoren sind Zahlen, die in der Blending-Funktion verwendet werden. In diesem Fall sagen wir, dass der Source Factor der Alpha-Wert des Source Fragment ist und der Zielfaktor hat einen konstanten Wert von 1. Es gibt noch weitere Verpflichtungen: Wenn Sie z.B. SRC_COLOR verwenden, um die Source Farbe anzugeben, erhalten Sie separate Source Faktoren für die Rot-, Grün-, Blau- und Alpha-Werte, die jeweils den originalen RGBA-Komponenten entsprechen.
Stellen wir uns nun vor, dass WebGL versucht, die Farbe eines Fragments zu berechnen, wenn es ein Ziel mit RGBA-Werten von (Rd, Gd, Bd, Ad) und ein nachfolgendes Source Fragment mit den Werten (Rs, Gs, Bs, Bs, As) hat.
Wir nehmen zusätzlich an, dass wir RGBA-Quellfaktoren von (Sr, Sg, Sb, Sb, Sa) und Zielfaktoren von (Dr, Dg, Db, Da) haben.
Jede Farbkomponente berechnet WebGL wie folgt:
In unserem Fal sagen wir also:
Normalerweise ist dies kein idealer Weg, um Transparenz zu schaffen, aber es funktioniert in diesem Fall sehr gut, wenn die Beleuchtung eingeschaltet ist. Und dieser Punkt ist es wert, hervorgehoben zu werden. Blending ist nicht das selbe wie Transparenz, es handelt sich lediglich um eine Technik, mit der man Effekte im Transparenzstil erzeugen kann.
Weiter gehts:
Das ist sehr einfach – wie viele Dinge in WebGL ist das Blending standardmäßig deaktiviert, also müssen wir es einschalten:
Das ist ein wenig interessanter. Wir müssen das Depth Testing deaktivieren. Wenn wir dies nicht tun, dann erfolgt das Blending nur in einigen Fällen. Wenn wir z.B. eine Kante unseres Würfels zeichnen, welches sich zufällig auf der Rückseite befindet, dann wird das Bild in den Frame Buffer geschrieben. Anschließend wird das vordere Bild wie gewollt darüber gemischt. Wenn wir jedoch zuerst die Vorderseite und dann die Rückseite zeichnenm wird die Rückseiten durch das Depth Testing verworfen, bevor wir zur Blending Function gelangen, so dass Sie nicht zum Bild beitragen. Das ist nicht das, was wir wollen.
Scharfsinnige Leser werden gemerkt haben, dass es eine starke Abhängigkeit gibt, wenn man sich auf die Reihenfolge bezieht in der Dinge gezeichnet werden, die wir bisher nicht kennengelernt haben. Abschließend noch der folgende Code:
Hier laden wir einen Alpha-Wert aus einem Textfeld in der Seite und schieben ihn bis zu den Shadern. Das liegt daran, dass das Bild, welches wir für die Textur verwenden keinen eigenen Alphakanal hat, so dass es schön ist das Alpha anpassen zu können, um zu sehen, wie es das Bild beeinflusst.
Der Rest des Codes in drawScene ist nur das, was notwendig ist, um die Dinge in der normalen Art und Weise zu handhaben, wenn das Blending ausgeschaltet ist:
Das ist alles, was sich im Code verändert hat.
Kommen wir also zu dem Punkt, wo es um die Zeichenreihenfolge geht. Der Transparenzeffekt mit dem kommenden Beispiel ist ziemlich gut – er sieht wirklich wie Buntglas aus. Und jetzt versuchen Sie es noch einmal, aber ändern Sie die Richtungsbeleuchtung so, dass Sie aus einer positiven Z-Richtung kommt – entfernen Sie dazu einfach das „-“ aus dem entsprechenden Feld. Es sieht immer noch ziemlich cool aus, aber der realistische Buntglas-Effekt ist verschwunden.
Der Grund dafür ist, dass bei der Originalbeleuchtung die Rückseite des Würfels schwach beleuchtet ist. Das bedeutet, dass seine R-, G- und B-Werte niedrig sind, wenn also die Gleichung:
…berechnet wird, ist die Beleuchtung weniger stark. In anderen Worten ausgedrückt, wir haben eine Beleuchtung, die bedeutet, dass die Dinge, die sich auf der Rückseite befinden weniger sichtbar sind. Wenn wir die Beleuchtung so umschalten, dass die Dinge an der Vorderseite weniger sichtbar sind, dann funktioniert unser Transparenzeffekt weniger gut.
Wie kommt es also zur richtigen Transparenz? Die FAQs von OpenGL deuten darauf hin, dass Sie einen Sourcefaktor von SRC_ALPHA und einen Zielfaktor von ONE_MINUS_SRC_ALPHA verwenden müssen. Aber wir haben immer noch das Problem, dass die Source- und Zielfragmente unterschiedlich behandelt werden und so gibt es eine Abhängigkeit von der Reihenfolge, in der die Dinge gezeichnet werden. Und dieser Punkt bringt uns schließlich zu dem, was ich als das schmutzige Geheimnis der Transparenz in Open-/WebGL bezeichne. Dazu zitieren wir doe OpenGL-FAQs:
„When using depth buffering in an application, you need to be careful about the order in which you render primitives. Fully opaque primitives need to be rendered first, followed by partially opaque primitives in back-to-front order. If you don’t render primitives in this order, the primitives, which would otherwise be visible through a partially opaque primitive, might lose the depth test entirely.“
Transparenz durch Blending ist ein schwieriges Thema und knifflig, aber wenn Sie genügend andere Aspekte der Szene steuern, wie z.B: die Beleuchtung, können Sie den richtigen Effekt erzielen, ohne an der Komplexität zu verzweifeln. Sie können es richtig machen, aber sich müssen vorsichtig sein und Dinge in einer ganz bestimmten Reihenfolge zeichnen, damit Sie einen optischen Mindeststandard erreichen.
Glücklicherweise ist das Blending auch für andere Effekte nützlich, wie Sie in Kürze sehen werden. Aber im Moment wissen Sie alles, was Sie aus diesem Beitrag lernen konnten. Nun haben Sie eine solide Grundlage um den Depth Buffer zu verstehen und Sie wissen, wie Blending verwendet werden kann, um einfache Transparenz zu schaffen.
Dies war sicher der schwierigste Teil der Serie. Wenn Sie soweit alles verstanden haben sollten Sie sich noch die restlichen Beiträge ansehen.