OpenGL hat eine spezielle Textur für Cubes, welche es uns erlaubt, insgesamt 6 Texturen einzupacken. Es hat einen passenden Sampler in GLSL, der eine 3D-Texturkoordinate nimmt – mit R-, S- und T-Komponenten. Der Trick dabei ist, dass wir einen 3D-Richtungsvektor für die Texturkoordinaten verwenden. Die Komponente mit der größten Größe gibt dem GL an, von welcher der 6 Seiten die Probe entnommen werden soll und das Gleichgewicht zwischen den verbleibenden 2 Komponenten gibt dem GL an, wo auf dieser 2D-Textur ein Texel entnommen werden soll.
Zwei beliebte Techniken, die Cube Maps verwenden, sind Skyboxen, die die Illusion eines 3D-Hintergrunds hinter der Kulisse bieten und Environment Mapping, die eine sehr glänzende Oberfläche als Spiegel der Umgebung erscheinen lassen. Wir können es auch verwenden, um die Approximate Refraction für transzulente Materialien wie z.B. Wasser zu approximieren.
Sky Box.
Diese Technik ersetzt effektiv die GL-Hintergrundfarbe durch eine interessantere 3D „gemalte Kulisse“. Die Idee bei der Sky-Box-Technik ist es, die Kamera während der Bewegung in einer großen Box einzuschließen. Wenn die Box auch bei Bewegung der Kamera den gleichen Abstand hat, dann entsteht ein Parallaxeneffekt – die llusion, dass sie relativ weit weg ist. Wir drehen sie aber nicht, wenn sich die Kamera dreht – das erlaubt uns, herumzuschwenken. Wenn die Textur gut konstruiert ist, werden wir die Texturen nicht bemerken und sie wird auf die Box projiziert, so dass es so aussieht, als ob wir uns innerhalb einer Kugel befinden, wenn wir sie mit unserem View-3D-Vektor abtasten. Wir können tatsächlich eine Kuppel oder eine Kugel anstelle einer Box verwenden, aber Boxen sind einfacher zu texturieren.
Die Größe der Box hat keinen Einfluss darauf, wie groß sie aussieht. Denken Sie darüber nach – wenn es sich mit der Kamera bewegt, sieht es genau so nah aus wie in der Ferne. Die Größe spielt nur eine Rolle, wenn sie sich mit den Clip-Ebenen überschneidet – in diesem Fall sehen Sie eine Ebene, die einen Teil ihrer Box abschneidet. Wir werden die Box vor allem anderen zeichnen und die Tiefenmaskierung abschalten. Denken Sie daran, dass dies bedeutet, dass es nichts in den Tiefenpuffer schreibt – alle nachfolgenden Zeichnungen auf den gleichen Pixel werden darauf gezeichnet. Dann wird es nie vor die Kulisse gezeichnet, auch wenn es näher an der Kamera ist.
Machen Sie einen Big Cube.
Wir müssen einen großen Cube machen und ihn in einen Scheitelpuffer legen. Wir müssen keine Texturkoordinaten hinzufügen. Wir werden im Inneren des Cubes sein, also stellen Sie sicher, dass Sie die Wicklungsreihenfolge der Eckpunkte enthalten, so dass die „Vorderseiten“ auf der Innenseite liegen. Sie können einen Cube aus einer Mesh-Datei laden, aber das wäre nicht ganz korrekt.
Erstellen einer Cube-Map Textur.
Nun müssen wir einen geeigneten Satz Texturen für die Skybox erstellen oder finden. Möglicherweise müssen Sie die Texturen „vorne“ und „hinten“ vertauschen, wenn sie für das gegnerische Koordinatensystem bestimmt sind. Cube Maps werden manchmal in einer einzigen Texturdatei gespeichert, aber meistens als 6 separate Texturen. Es ist davon auszugehen, dass hier 6 verschiedene Texturen geladen werden. Beachten Sie, dass keine Mip-Maps für Skyboxen erzeugt wurden, da es immer der gleiche Abstand zur Kamera sein wird und wir können einfach eine entsprechend große Textur wählen. Um den Cube zu testen, wurde auf jede strukturierte Fläche ein farbiger Rand gezeichnet, um zu zeigen, wo die Texturen waren und ob die Vorder- und Rückseite vertauscht werden mussten.
Ich habe eine create_cube_map() Funktion, die Dateinamen für die 6 separaten Bilddateien nimmt und die OpenGL Textur erzeugt. Dadurch wird eine weitere Funktion aufgerufen, um das separate Bild für jede der 6 Seiten zu laden. Beachten Sie, dass jede Seite an die gleiche Textur gebunden wird, die generiert wurden – wir werden dies als Parameter übergeben. Abschließend wenden wir eine Texturfilterung auf die Textur an und der Textur-Handle wird als Funktionsparameter zurückgegeben.
Die Parameter der Texturfilterung sind interessant. Achten Sie darauf, dass Sie für alle drei Komponenten „Clamp to Edge“ einstellen. Wenn Sie nicht an der Kante klemmen, erhalten Sie möglicherweise ein sichtbares Seam an den Rändern ihrer Texturen. Wir haben ein paar bi-lineare Filter,um kleine Aliase zu entfernen, wenn sich die Kamera dreht.
Der Code ist fast identisch mit der Erstellung einer einzelnen 2D-Textur in OpenGL, außer dass wir eine Textur vom Typ GL_TEXTURE_CUBE_MAP anstelle von GL_TEXTURE_2D binden. Dann wird glTextImage2D() 6 mal aufgerufen, mit dem Makro für jede Seite als erstem Parameter, anstatt GL_TEXTURE_2D.
Einfache Cube-Map Shader.
Unser Vertex-Shader benötigt lediglich die Positionierung des Cubes und die Ausgabe der Texturkoordinaten. In diesem Fall eine 3D-Koordinate als vec3. Dies sollte eine Richtung sein, aber es können die tatsächlichen lokalen Scheitelpunkte als Richtung verwendet werden, weil es ein Cube ist. Wenn Sie darüber nachdenken, werden die Punkte entlang der Vorderseite mit unterschiedlichen Werten von x und y interpoliert, aber alle haben den z-Wert -10.0. Sie müssen diese nicht erst normalisieren – es wird trotzdem funktionieren. Das einzige Problem ist, dass die Punkte genau an den Ecken keine „größte“ Komponente haben werden und Pixel können schwarz erscheinen, da die Koordinate ungültig ist Daher wurde Clamp-to-Edge ermöglicht.
Hinweis: Es sind andere Tutorials bekannt, die die Z- und W-Komponenten von gl_Position so manipulieren, dass nach der perspektivischen Teilung der Z-Abstand immer 1.0 ist. Maximaler Abstand im normalisierten Gerätebereich und damit immer im Hintergrund. Das ist eine schlechte Idee. Es wird kollidieren und möglicherweise sichtbar flackern oder ganz schwarz zeichnen, wenn die Tiefenprüfung aktiviert ist. Es ist besser, die Tiefenmaskierung beim Zeichnen zu deaktivieren, wie wir es in Kürze tun werden und damit sicherstellen, dass es das erste ist, was Sie in ihrer Zeichenschleife zeichnen.
Die P- und V-Matrizen sind die Projektion meiner Kamera bzw. View-Matrizen. Die Ansichtsmatrix hier ist eine spezielle Version der Ansichtsmatrix der Kamera, welche die Kameraübersetzung nicht enthält. In der Hauptschleife wird geprüft, ob sich die Kamera bewegt hat. Anschließend wird eine sehr einfache Ansichtsmatrix erstellt und deren Uniform für den Cube Map Shader aktualisiert. Diese Kamera kann nur auf der Y-Achse rotieren, aber Sie können ein Quaternion verwenden, um eine Matrix für eine Freiluftkamera zu erzeugen. Denken Sie daran, dass die Ansichtsmatrizen negierte Orientierungen verwenden, so dass die Szene um die Kamera gedreht wird. Wenn Sie eine andere Mathematik-Bibliothek verwenden, dann haben Sie hier natürlich andere Matrix-Funktionen.
Der Fragmentshader verwendet einen speziellen Sampler (SamplerCube), welcher die 3D-Texturkoordinate akzeptiert. Ansonsten gibt es nichts Besonderes am Fragement-Shader. Verwenden Sie die Funktion texture() haben Sie möglicherweise das Problem, dass ältere GL-Implementierungen möglicherweise textureCube() bevorzugen.
Rendering.
Verwenden Sie ihre Shader und binden Sie ihren VAO wie Sie es gewohnt sind. Denken Sie daran, dass die Textur vor dem Rendern in einen aktiven Slot eingebunden werden sollte. Diesmal hat es mit GL_TEXTURE_CUBE_MAP einen anderen Texturtyp. Wenn die Tiefenmaskierung deaktiviert ist, wird nichts in den Tiefenpuffer geschrieben, so dass sich alle nachträglich gezeichneten Szenen vor der Skybox befinden.
Reflexion und Refraktion mit statischen Environment Maps.
Bisher haben wir nur spiegelnde Highlights verwendet, um zu zeigen, ob eine Oberfläche stark reflektierend ist, was als leuchtend farbiger Punkt erscheint. Angenommen, wir wollen ein detailliertes Bild der Skybox oder eines Teils der Umgebung auf die Oberfläche reflektieren. Wenn wir eine Cube-Map mit dem Bild eines bunten quadratischen Fensters darauf haben, dann können wir damit eine viel interessantere Reflexion des Lichts auf der Oberfläche erzeugen.
Reflexion der Skybox.
Die gebräuchlichste Art der Environmental Map spiegelt nur die Skybox einer Szene auf einer Oberfläche wieder. Wir können sagen, dass dies eine „statische“ Environmental Map ist, da sie keine beweglichen Teile der Szene wiederspiegelt. Es kann einen ziemlich überzeugenden Effekt erzeugen, da Sie sehen können, dass die Skybox mit der Reflexion übereinstimmt, wenn Sie die Ansicht drehen. Sie müssen keine Skybox anzeigen lassen, damit dies funktioniert – eine Indoor-Szene hat vielleicht keinen sichtbaren Himmel, aber sie haben vielleicht eine Cube-Map, die nicht angezeigt wird, aber eine Indoor-ähnliche Cube-Map enthält, die Sie nur für Reflexionen verwenden.
Das Prinzip ist sehr einfach. Wir erarbeiten einen Richtungsvektor vom Blickpunkt (Kameraposition) zur Oberfläche. Wir reflektieren dann diese Richtung von der Oberfläche ausgehend von dem Oberflächennormal. Wir verwenden den reflektierten Richtungsvektor, um die Cube-Map zu vermessen. GLSL hat eine eingebaute reflect()-Funktion, die einen Eingangsvektor und einen Normalwert annimmt und den Ausgangsvektor erzeugt. Es kommt häufig vor, dass Probleme beim Kompilieren auf allen Treibern entstehen. Deshalb erfolgt in der Praxis meistens eine manuelle Programmierung.
Es wurde ein Mesh des Suzanne Affenkopfes geladen und dazu wurde ein einfacher Vertex-Shader erstellt. Dazu müssen die Scheitelpositionen und Normals im Eye Space berechnet werden.
Der Fragment-Shader berechnet den Richtungsvektor von der Kamera zur Oberfläche als incident_eye: die Einfallsrichtung im Eye Space. Beachten Sie, dass dies normalerweise vec3 dir = normalize (to=from) ist, aber das „from“ hier ist die Kameraposition (0,0,0,0) im Eye Space. Die eingebaute Reflexionsfunktion enthält dann den reflektierten Vektor, der in den World Space konvertiert und als Texturkoordinaten verwendet wird.
Sie können die Anzahl der Befehle im Shader reduzieren, wenn Sie die Reflexion im World Space berechnen, aber dann brauchen Sie eine einheitliche Kameraweltposition.
Häufige Fehler
- Die Reflexionen sind verkehrt herum und auf der falschen Seite – Überprüfen Sie unbedingt die Richtung und die normalen Vektoren. Normalisieren Sie sie.
- Die Mesh-Reflexionen sind nicht so glatt wie der Affe – denken Sie daran, interpolierte Flächennormals zu verwenden (setzen Sie alle Flächen auf glätte Flächen in Blender).
Refraktionen.
Transzulente Objekte sollten das Licht brechen. Das bedeutet, dass er nicht gerade durchläuft – der Strahl biegt sich, wenn er auf die Oberfläche trifft und das Material wechselt. Ein Blick in ein Schwimmbad zum Beispiel. Dies funktioniert ähnlich wie bei der Reflexion, außer dass der Eye-to-Surface-Vektor über den Oberflächennormals stört, statt sie zu reflektieren. Der tatsächliche Richtungswechsel richtet sich nach dem Snell`schen Gesetz. GLSL hat auch eine eingebaute refract ()-Funktion mit einer vektorbasierten Version von Shell`s Gesetz. Sie können den vorherigen Fragment-Shader ändern, um diesen anstelle von vec3 zu verwenden.
Das Verhältnis ist das Verhältnis zwischen den Refractive Index des ersten Materials (Luft, 1,0) und dem zweiten Material (Wasser, 1,3333). Auf Wikipedia finden Sie einige Indizes für gängige Materialien. Beachten Sie, dass der Affenkopf einseitig ist; wir ignorieren die Wirkung des Lichts, dass durch den Hinterkopf wandert, wo es ein zweites Mal brechen sollte. Man kann also sagen, dass es nicht sehr realistisch ist, aber gut für einfache Oberflächen.
Man kann sich vorstellen, dass man mit der richtigen Kombination aus Phong Vertex Displacement und Refraction Environment Map eine ziemlich überzeugende Wasser-Visualisierung machen möchte.
Häufige Fehler:
- Das gebrochene Bild sieht vergrößert oder blockig aus. Versuchen Sie es mit höher auflösenden Cube-Map-Texturen.
- Das Bild steht auf dem Kopf, das ist wahrscheinlich richtig. Schauen Sie sich Fotos von Refraktionen realer, ähnlicher Oberflächen (z.B. Kugel, Pool) an. Überprüfen Sie, ob ihr Indexverhältnis korrekt ist.
Und für Experten… dynamische Environment Maps.
Es ist möglich, die gesamte Szene, einschließlich aller sich bewegenden Objekte, in einer Environment Map zu verwenden. Wenn Sie bereits mit dem Binden von Texturen an Framebuffer vertraut sind, dann können Sie 6 Texturen als Zielausgaben von 6 Framebuffern binden. Sie rendern die Szene 6 mal aus der Sicht des Objekts, das umgebungstechnisch abgebildet werden soll. Einmal nach oben, einmal nach unten, einmal nach links etc. Die Ausgabentexturen werden in der Cube-Map zusammengefasst. Die perspektivische Matrix sollte ein Sichtfeld von 90 Grad haben, so dass sich die 6 Stützen exakt in einem Cube ausrichten.
- Erstellen von 6 Farbtexturen
- Erstellen von 6 Tiefentexturen
- 6 Framebuffer erstellen
- Fügen Sie jedem Framebuffer eine Farbtextur und eine Tiefenkarte hinzu
- Binden Sie beim Rendern jeden Framebuffer und zeichnen Sie diese in eine von 6 Richtungen
- Erstellen einer Cube-Map aus 6 Texturen
Wir haben uns angesehen, wie man manuell eine Cube-Map am Anfang des Artikels erstellt. Es ist auch möglich, alle 6 Tiefen- und Farb-Maps an einen einzigen Framebuffer anzuhängen und alle 6 Ansichten in einem Durchgang zu rendern. Dazu müssen Sie einen Geometrie-Shader verwenden, der die Vertex-Positionen in separate gl_Layers umleitet. Dies könnte ihnen eine kleine rechnerische Verbesserung bringen, wenn Sie dynamische Environmental Maps in Echtzeit berechnen, aber Sie sollten beachten, dass Geometrie-Shader im gesamten Treiberbereich instabil und damit nicht zu empfehlen sind.