Categories: Sonstiges

WebGL Grundlagen 16 – Rendern in Texturen.

Willkommen zu unserem sechszehnten Teil unserer Serie von WebGL Grundlagen. Wir beginnen mit einer äußerst nützlichen Technik. Wir werden eine 3D-Szene in eine Textur rendern, die wir später als Input für das Rendern einer anderen Szene verwenden können. Dies ist ein netter Trick, nicht nur weil er Szenen innerhalb von Szenen ermöglicht, sondern auch weil er die Grundlage für das Hinzufügen von Picking (Auswahl von 3D-Objekten mit der Maus), Schatten, Reflexionen und vielen anderen 3D-Effekten ist.

Dieses Ergebnis werden Sie nach der Umsetzung erhalten:

For privacy reasons YouTube needs your permission to be loaded. For more details, please see our Datenschutzerklärung.

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 sich den Code genau ansehen.

Sobald Sie eine Kopie des Codes haben, laden Sie index.html in einen Texteditor und schauen Sie sich das an. Die Datei dieses Tutorials hat einige Änderungen gegenüber früheren Beiträgen, also fangen wir ganz unten an und arbeiten uns nach oben. Wir beginnen mit webGLStart. Neues wurde wie immer wieder rot hervorgehoben:

Copy to Clipboard

Also machen wir unser übliches Setup, initialisieren WebGL, laden unsere Shader, erstellen Puffer von Eckpunkten zum Zeichnen, laden die Texturen, welche wir verwenden werden und starten eine Anfrage, um das JSON-Modell des Laptops zu laden. Das Aufregende ist, dass wir einen Frame-Puffer für die Textur erstellen. Bevor ich ihnen den Code zeige, schauen wir uns an, was ein Frame-Puffer ist.

Wenn Sie etwas mit WebGL rendern, benötigen Sie offensichtlich eine Art Speicher auf der Grafikkarte, um die Ergebnisse des Renderings zu erhalten. Sie haben wirklich eine genaue Kontrolle darüber, welche Art von Speicher dafür vorgesehen ist. Sie benötigen ausreichend Platz, um die Farben der verschiedenen Pixel zu speichern, aus denen sich die Ergebnisse ihres Renderings zusammensetzen. Es ist auch ziemlich wichtig einen Depth Puffer zu haben, damit ihr Rendering berücksichtigen kann, wie nahe Objekte in der Szene entfernte Objekte verstecken, so dass auch ein wenig Speicher benötigt wird. Daneben existieren noch andere Arten von Puffern, die ebenfalls nützlich sein können, wie z.B. ein Schablonenpuffer, was wir uns in einer zukünftigen Übung etwas genauer ansehen werden.

Ein Framebuffer ist eine Sache, zu der man eine Szene rendern kann und er besteht aus diesen verschiedenen Bits des Speichers. Es gibt einen Standard-Frame-Puffer, den wir in der Vergangenheit immer gerendert haben und der auf der Website angezeigt wird, aber Sie können auch ihren eigenen Frame-Puffer erstellen und verwenden. In diesem Beitrag werden wir einen Frame-Puffer erstellen und ihm mitteilen, dass er eine Textur als das Bit des Speichers verwenden soll, in dem dieser die Farben speichern soll, wenn er gerendert wird. Außerdem müssen wir ihm ein bisschen Speicher zuweisen, um ihn für seine Tiefenberechnungen zu verwenden.

Nachdem wir das alles erklärt haben, werfen wir einen Blick auf einen Code, der alles erledigt. Die Funktion ist initTextureFrameBuffer und Sie befindet sich eher im oberen Bereich des Codes.

Copy to Clipboard

Bevor die Funktion startet, definieren wir einige globale Variablen, um den Frame-Puffer zu speichern, auf den wir das Material rendern werden, welches auf dem Bildschirm des Laptops erscheinen soll und zudem um die Textur zu speichern, die das Ergebnis des Renderings in diesem Frame-Puffer speichert. Weiter zur Funktion:

Copy to Clipboard

Unser erster Schritt ist es, den Frame-Puffer selbst zu erstellen und nach dem normalen Muster machen wir ihn zu unserem aktuellen, d.h. demjenigen auf dem die nächsten Funktionsaufrufe durchgeführt werden sollen. Wir speichern auch die Breite und Höhe der Szene, welche wir rendern werden. Diese Attribute sind normalerweise nicht Teil eine Frame-Puffers. Hier wurde lediglich der normale JavaScript-Trick benutzt, Sie als neue Eigenschaften zu assozieren, weil Sie an späteren Stellen benötigt werden, wenn wir Dinge mit dem Frame-Puffer machen.

Als nächstes erstellen wir ein Textur-Objekt und stellen die gleichen Parameter wie gewohnt ein:

Copy to Clipboard

Aber es gibt einen kleinen Unterschied. Der Aufruf von gl.texImage2D hat ganz andere Parameter:

Copy to Clipboard

Normalerweise rufen wir gl.texImage2D auf, wenn wir Texturen erstellen, um Bilder anzuzeigen, um die beiden miteinander zu verbinden. Nun gibt es natürlich kein geladenes Bild. Wir müssen eine andere Version von gl.texImage2D benennen und ihr mitteilen, dass wir keine Bilddaten haben und wir möchten, dass Sie einen bestimmten Platz an freiem Speicherplatz auf der Grafikkarte für unsere Textur zur Verfügung stellt. Streng genommen ist der letzte Parameter der Funktion ein Array, das als Ausgangspunkt in den frisch zugewiesenen Speicher kopiert werden soll und durch die Angabe von 0 sagen wir ihm, dass wir nichts zu kopieren haben.

Ok, also haben wir jetzt eine leere Tastatur, die die Farbwerte für unsere gerenderte Szene speichern kann. Als nächstes erstellen wir einen Depth Puffer, um die Depth-Informationen zu speichern:

Copy to Clipboard

Was wir getan haben, ist ein Render-Puffer-Objekt zu erstellen. Dies ist eine generische Art von Objekt, das einen bestimmten Speicherplatz speichert, welches wir mit einem Frame-Puffer assozieren möchten. Wir binden es – genau wie bei Texturen, Frame-Puffern hat WebGL auch einen aktuellen Render-Puffer – und rufen dann gl.renderbufferStorage auf, um WebGL mitzuteilen, dass der aktuell gebundene Render-Puffer genügend Speicherplatz für 16-Bit-Tiefenwerte über einen Puffer mit der angegebenen Höhe und Breite benötigt.

Als nächstes:

Copy to Clipboard

Wir hängen alles an den aktuellen Frame-Puffer an. Wir teilen ihm mit, dass der Raum des Frame-Puffers für das Rendern von Farben (gl.COLOR_ATTACHMENT0) unsere Textur ist und dass der Speicher, den er für die Tiefeninformationen verwenden sollte, der soeben erstellte Depth Puffer ist.

Jetzt haben wir den gesamten Speicher für unseren Frame-Puffer eingerichtet.WebGL weiß, worauf es bei der Verwendung ankommt. Also räumen wir jetzt auf und setzen die aktuelle Textur, den Render-Puffer und den Frame-Puffer wieder auf die Standard-Werte zurück:

Copy to Clipboard

… und wir sind fertig. Unser Frame-Puffer ist richtig eingestellt. Wie können wir es jetzt am besten nutzen? Der Ort, an dem Sie mit der Suche beginnen sollten, ist drawScene, ganz unten in der Datei. Gleich zu Beginn der Funktion, vor dem normalen Code zum Einstellen des Ansichtsfensters und Löschen des Canvas, sehen Sie etwas Neues:

Copy to Clipboard

Im Lichte der obigen Beschreibung sollte es ziemlich offensichtlich sein, was dort passiert. Wir wechseln vom Standard-Frame-Puffer zum Render-to-Texture-Framebuffer, den wir in initTextureFramebuffer erstellt haben. Anschließend rufen wir eine Funktion mit der Bezeichnung drawSceneOnLaptopScreen auf, um die Szene zu rendern, welche wir auf dem Bildschirm des Laptops anzeigen möchten. Bevor wir mit drawScene fortfahren, lohnt es sich, einen Blick auf die drawSceneOnLaptopScene-Funktion zu werfen. Ich werde es hier nicht kopieren, weil es eigentlich sehr einfach ist – es ist nur eine abgespeckte Version der drawScene Funktion aus Übung 13. Das liegt daran, dass unser Rendering-Code bisher keine Vermutungen darüber angestellt hat, wohin er gerendert werden soll. Er wird nur in den aktuellen Frame-Puffer gerendert. Die einzigen Änderungen, die wir hier vorgenommen haben, waren die Vereinfachungen, die durch das Entfernen der beweglichen Lichtquelle möglich wurden, die für dieses Tutorial nicht notwendig waren.

Sobald die ersten drei Zeilen von drawScene ausgeführt wurden, haben wir also einen Rahmen aus Übung 13, welcher in eine Textur gerendert wurde. Der Rest von drawScene zeichnet einfach den Laptop und verwendet diese Textur für seinen Bildschirm. Wir beginnen mit etwas normalem Code, um die Model-View-Matrix einzurichten und den Laptop um einen Betrag zu drehen, welcher von laptopAngle bestimmt wird:

Copy to Clipboard

Die Werte, die die Farben und Positionen unserer Lichtquellen definieren, senden wir wie gewohnt an die Grafikkarte:

Copy to Clipboard

Als nächstes geben wir die Grafikkarteninformationen über die beleuchtungsrelevanten Parameter des Laptops weiter, was das erste ist, was wir zeichnen werden. Es gibt hier etwas Neues, das nicht direkt mit dem Rendern von Texturen zu tun hat. Sie erinnern sich vielleicht daran, dass bei der Beschreibung des Phong-Beleuchtungsmodell erwähnt wurde, dass für jede Art von Licht unterschiedliche Farben hatten – eine Umgebungsfarbe, eine diffuse Farbe und eine spiegelnde Farbe. Damals hatten wir die vereinfachende Annahme getroffen, dass diese Farben immer weiß waren oder die Farbe der Textur, je nachdem, ob Texturen aus- oder eingeschaltet waren. Aus Gründen, die wir uns gleich anschauen werden, reicht das für dieses Tutorial nicht aus – wir müssen die Farben für den Laptop-Bildschirm etwas detaillierter spezifizieren und wir müssen eine neue Art von Farbe verwenden, die emmisive Farbe. Für den Body des Laptops brauchen wir uns jedoch keine Sorgen zu machen. Die Material-Farb-Parameter sind einfach, der Laptop nur weiß.

Copy to Clipboard

Der nächste Schritt besteht darin, wenn die verschiedenen Vertex-Koordinaten des Laptops schon geladen sind, um es zu zeichnen. Dieser Code sollte schon recht vertraut sein:

Copy to Clipboard

Sobald wir das alles getan haben, wurde der Laptopkörper gezeichnet. Als nächstes müssen wir den Bildschirm zeichnen. Die Beleuchtungseinstellungen werden zuerst vorgenommen, und diesmal stellen wir eine emittierende Farbe ein:

Copy to Clipboard

Also, was ist die Emmisionsfarbe? Nun, Bildschirme auf Dingen wie Laptops reflektieren nicht nur das Licht, sondern geben es auch wieder ab. Wir möchten, dass die Farbe des Bildschirm wesentlich stärker von der Farbe der Textur bestimmt wird als von den Lichteffekten. Wir können das tun, in dem wir die Uniforms ändern, welche die Beleuchtung regeln, indem wir das Point Lighting ausschalten und die Umgebungsbeleuchtung bis zu 100% erhöhen, bevor wir den Bildschirm zeichnen und dann die alten Werte wiederherstellen. Allerdings ist diese Vorgehensweise schon recht merkwürdig, schließlich wird der Emissionsgrad des Bildschirms über den Bildschirm geregelt und nicht über das Licht. In diesem speziellen Beispiel könnten wir das auch allein mit der Umgebungsbeleuchtung machen, denn das Umgebungslicht ist weiß, so dass die Einstellung der Hintergrundfarbe des Bildschirms mit 1,5, 1,5, 1,5, 1,5 den richtigen Effekt hätte. Aber wenn dann jemand die Umgebungsbeleuchtung variiert, ändert sich auch die Farbe des Bildschirms, was merkwürdig wäre. Denn wenn Sie ihren Laptop in einem rot-beleuchteten Raum aufstellen, wird der Bildschirm nicht rot. Also verwenden wir eine neue emmisive-Farbuniform, die der Shader mit einem einfachen Code handhabt, zu dem wir später noch kommen werden.

Eine Randbemerkung: Es lohnt sich, daran zu erinnern, dass die Emissionsfarbe eines Objekts in diesem Sinne keine anderen Objekte in seiner Umgebung beeinflusst – das heisst, es lässt das Objekt nicht zu einer Lichtquelle werden und andere Dinge beleuchten. Es ist nur eine Möglichkeit, ein Objekt mit einer Farbe zu versehen, die unabhängig von der Beleuchtung der Szene ist.

Die Anforderung an die Emissionsfarbe erklärt auch, warum wir für dieses Tutorial die anderen Materialfarben-Parameter trennen mussten. Unser Laptop-Bildschirm hat eine Emissionsfarbe, die durch seine Textur bestimmt wird, aber seine Spiegelfarbe sollte davon unberührt bleiben – schließlich ändert das, was auf dem Bildschirm ihren Laptops zu sehen ist, nicht die Farbe der Reflexion des Fensters dahinter. Diese Farbe ist also immer noch weiß.
Anschließend binden wir die Puffer, welche die Vertex-Attribute des Laptop-Bildschirms spezifizieren:

Copy to Clipboard

Als nächstes geben wir an, dass wir die Textur verwenden wollen, die wir vorher gerendert haben:

Copy to Clipboard

Dann zeichnen wir den Bildschirm und wir sind fertig:

Copy to Clipboard

Das ist der gesamte Code, welcher nötig war, um eine Szene in eine Textur zu übertragen und diese dann in einer anderen Szene zu verwenden.

Das ist ziemlich viel für dieses Tutorial, aber lassen Sie uns einfach schnell durch die anderen Änderungen von den vorhergehenden Übungen laufen. Es gibt ein Paar von Funktionen, die loadLaptop und handleLoadedLaptop genannt werden, um die JSON-Daten zu laden, die den Laptop bilden. Sie sind im Allgemeinen die selben wie der Code, um die Teekanne im vierzehnten Teil unserer Serie zu laden. Es gibt auch ein bisschen Code am Ende von initBuffers, um den Vertex-Puffer für den Laptop-Bildschirm zu initialisieren. Das ist aktuell noch ein wenig hässlich, wird aber in einer späteren Beitrag noch verbessert präsentiert werden.

Schließlich gibt es noch den neuen Fragment-Shader, der als Alternative zur Texturfarbe die Materialfarben pro Lichtart handhaben muss. Neu ist lediglich die emissive Beleuchtung. Hier wird lediglich die endgültige Fragmentfarbe direkt am Ende hinzugefügt. Hier der Code:

Copy to Clipboard

Und das wars zu dieser Thematik. In diesem Tutorial haben wir uns damit beschäftigt, wie man eine Szene in eine Textur rendert und in einer anderen Szene verwendet und wie man Materialfarben und deren Funktionsweise berührt. Im nächsten Tutorial werden wir aufzeigen, wie man damit etwas wirklich Nützliches machen kann. Mit GPU-Picking kann man 3D-Szenen schreiben, mit denen man interagieren kann, indem man auf die Objekte klickt.

3DMaster