Studiennotizen zur Computergrafik und OpenGL-C++-Version, Kapitel 13, Geometrie-Shader

In der OpenGL-Pipeline folgt auf die Tessellationsphase die Geometriephase. In dieser Phase hat der Programmierer die Möglichkeit, Geometrie-Shader einzubeziehen. Diese Stufe existierte tatsächlich vor der Tessellationsstufe, die in Version 3.2 (2009) Teil des OpenGL-Kerns wurde.

Wie die Tessellation ermöglichen Geometrie-Shader Programmierern die Manipulation von Scheitelpunktgruppen auf eine Weise, die mit Vertex-Shadern nicht möglich ist. In manchen Fällen kann die gleiche Aufgabe mit einem Tessellations-Shader oder einem Geometrie-Shader gelöst werden, da sich deren Funktionalität in mancher Hinsicht überschneidet.

13.1 Primitiv-für-Primitiv-Verarbeitung in OpenGL

Die Geometrie-Shader-Stufe befindet sich zwischen Tessellation und Rasterisierung, innerhalb des Segments der Pipeline, das für die Grundverarbeitung verwendet wird (siehe Abbildung 2.2). Mit Vertex-Shadern können Sie jeweils einen Scheitelpunkt bearbeiten, während Fragment-Shader jeweils nur ein Fragment (eigentlich ein Pixel) bearbeiten können, Geometrie-Shader jedoch jeweils nur ein Grundelement bearbeiten können.

Denken Sie daran, dass Grundelemente die Grundkomponenten von Zeichenobjekten in OpenGL sind. Es gibt nur wenige Arten von Grundelementen; wir konzentrieren uns hauptsächlich auf Geometrie-Shader, die Dreiecksgrundelemente manipulieren. Wenn wir also sagen, dass ein Geometrie-Shader jeweils nur ein Grundelement bearbeiten kann, meinen wir normalerweise, dass der Shader gleichzeitig auf drei Eckpunkte eines Dreiecks zugreifen kann. Der Geometrie-Shader ermöglicht den gleichzeitigen Zugriff auf alle Scheitelpunkte im Grundelement, dann:

  • Die Ausgabe derselben Grundelemente bleibt unverändert;
  • Ausgabeprimitive desselben Typs mit geänderten Scheitelpunktpositionen;
  • Geben Sie verschiedene Arten von Grundelementen aus.
  • Weitere andere Grundelemente ausgeben;
  • Primitive entfernen (überhaupt nicht exportieren).

Ähnlich wie beim Tessellation Evaluation Shader kann im Geometrie-Shader auf die eingehenden Scheitelpunktattribute als Array zugegriffen werden. Im Geometrie-Shader wird das eingehende Attributarray jedoch nur bis zur Größe des Grundelements indiziert. Wenn das Grundelement beispielsweise ein Dreieck ist, sind die verfügbaren Indizes 0, 1, 2. Greifen Sie mithilfe eines vordefinierten Arraysgl_in auf die Scheitelpunktdaten selbst zu, wie unten gezeigt.

gl_in[2].gl_Position // 第三个顶点的位置

Ähnlich wie beim Tessellation-Evaluierungs-Shader sind die vom Geometrie-Shader ausgegebenen Scheitelpunktattribute alle Skalare. Das heißt, die Ausgabe ist ein Strom der einzelnen Scheitelpunkte, die das Grundelement bilden (ihre Positionen und andere Attributvariablen, falls vorhanden).

verfügt über einen Layoutmodifikator, der den primitiven Eingabe-/Ausgabetyp und die Ausgabegröße festlegt. Der spezielle GLSL-BefehlEmitVertex() gibt an, dass ein Vertex ausgegeben werden soll. Der spezielle GLSL-BefehlEndPrimitive() gibt an, dass ein bestimmtes Grundelement erstellt wird.

Es gibt eine integrierte Variablegl_PrimitiveIDIn, die die ID des aktuellen Grundelements speichert. IDs beginnen bei 0 und zählen, bis die Gesamtzahl der Grundelemente minus 1 beträgt.

Wir werden vier gängige Arten von Operationen untersuchen:

  • Grafikelemente ändern;
  • Elemente löschen;
  • Grafikelemente hinzufügen;
  • Ändern Sie den Elementtyp.

13.2 Grafikelemente ändern

Geometrie-Shader sind praktisch, wenn eine Änderung der Form eines Objekts durch eine einzelne Änderung an einem Grundelement (normalerweise einem Dreieck) beeinflusst werden kann.

Betrachten Sie zum Beispiel den Torus, den wir zuvor in Abbildung 7.12 vorgestellt haben. Angenommen, der Torus stellt den Raum darin dar (z. B. bei der Darstellung eines Reifens) und wir möchten ihn „aufpumpen“. Durch einfaches Anwenden eines Skalierungsfaktors im C++/OpenGL-Code wird dies nicht erreicht, da sich seine Grundform nicht ändert. Um ihm ein „aufgeblasenes“ Aussehen zu verleihen, müssen Sie auch das innere Loch kleiner machen, wenn sich der Torus in den leeren Mittelraum hinein erstreckt.

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, den Oberflächennormalenvektor zu jedem Scheitelpunkt hinzuzufügen. Obwohl dies in einem Vertex-Shader möglich ist, üben wir dies in einem Geometrie-Shader. Programm 13.1 zeigt den Code für den GLSL-Geometrie-Shader. Andere Module sind mit Programm 7.3 identisch, mit einigen geringfügigen Änderungen: Die Namen der Fragment-Shader-Eingaben müssen jetzt die Ausgabe des Geometrie-Shaders widerspiegeln (z. B. „variingNormal“ wird zu „variingNormalG“), und C++/OpenGL-Anwendungen müssen den Geometrie-Shader kompilieren und konvertieren Es wird vor dem Verknüpfen mit dem Shader-Programm verknüpft. Der neue Shader wird wie unten gezeigt als Geometrie-Shader bezeichnet.

GLuint gShader = glCreateShader(GL_GEOMETRY_SHADER);

Programm 13.1 Geometry Shader: Vertices ändern
Fügen Sie hier eine Bildbeschreibung ein

Beachten Sie in Programm 13.1, dass die Eingabevariablen, die den Ausgabevariablen des Vertex-Shaders entsprechen, als Arrays deklariert sind. Dies bietet dem Programmierer einen Mechanismus, um über die Indizes 0, 1 und 2 auf jeden Scheitelpunkt in einem Dreiecksgrundelement und seine Attribute zuzugreifen. Wir wollen diese Eckpunkte entlang ihrer Oberflächennormalenvektoren nach außen verschieben. Im Vertex-Shader wurden sowohl Vertices als auch Normalenvektoren in den Ansichtsraum umgewandelt. Wir fügen jedem eingehenden Scheitelpunktbit (gl_in[i].gl_Position) einen kleinen Teil des Normalenvektors hinzu und wenden dann die Projektionsmatrix auf das Ergebnis an, wodurch jede Ausgabe gl_Position entsteht.

Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.1 „Aufgeblasener“ Torus, durch Geometrie-Shader modifizierte Scheitelpunkte

Es ist erwähnenswert, dass GLSL-AufrufeEmitVertex() verwendet werden, um anzugeben, wann wir mit der Berechnung der Ausgabegl_Position und der zugehörigen Scheitelpunktattribute fertig sind und bereit sind Geben Sie den Scheitelpunkt aus. EndPrimitive()Der Aufruf gibt an, dass wir die Definition der Eckpunkte, aus denen das Grundelement (in diesem Fall das Dreieck) besteht, abgeschlossen haben. Die Ergebnisse sind in Abbildung 13.1 dargestellt.

Der Geometrie-Shader enthält zwei Layout-Qualifizierer. Der erste gibt den primitiven Eingabetyp an und muss mit dem primitiven Typ im C++-seitigen glDrawArrays()- oder glDrawElements()-Aufruf kompatibel sein. Die Optionen sind in Tabelle 13.1 aufgeführt.

Tabelle 13.1 Optionen für primitive Eingabetypen

Fügen Sie hier eine Bildbeschreibung ein
Die verschiedenen primitiven OpenGL-Typen (einschließlich der Typen „strip“ und „fan“) werden in Kapitel 4 behandelt. Der Typ „adjacent“ wird in OpenGL für die Verwendung mit Geometrie-Shadern verwendet und sie können auf Scheitelpunkte neben Grundelementen zugreifen. Wir verwenden sie in diesem Buch nicht, sie werden jedoch der Vollständigkeit halber aufgeführt.

Der Ausgabeprimitivtyp muss „points“, „line_strip“ oder „triangle_strip“ sein. Beachten Sie, dass der Ausgabelayout-Qualifizierer auch die maximale Anzahl von Scheitelpunkten angibt, die der Shader pro Aufruf ausgibt.

Diese spezifische Änderung am Torus kann im Vertex-Shader vereinfacht werden. Nehmen wir jedoch an, dass Sie, anstatt jeden Scheitelpunkt entlang seines eigenen Oberflächennormalenvektors nach außen zu verschieben, jedes Dreieck entlang seines Oberflächennormalenvektors nach außen bewegen möchten, wodurch die konstituierenden Dreiecke des Torus effektiv nach außen „explodiert“ werden. Der Vertex-Shader kann dies nicht tun, da der Normalenvektor des Dreiecks berechnet wird

Die Scheitelpunktnormalenvektoren der drei Dreiecksscheitelpunkte müssen gemittelt werden, und der Scheitelpunkt-Shader kann jeweils nur auf die Scheitelpunktattribute eines Scheitelpunkts im Dreieck zugreifen. Wir können dies jedoch im Geometrie-Shader tun, da der Geometrie-Shader Zugriff auf alle drei Eckpunkte in jedem Dreieck hat. Wir berechnen den Oberflächennormalenvektor des Dreiecks, indem wir deren Normalenvektoren mitteln, und fügen dann diesen durchschnittlichen Normalenvektor zu jedem Scheitelpunkt im Dreiecksgrundelement hinzu. Abbildung 13.2, Abbildung 13.3 und Abbildung 13.4 zeigen den durchschnittlichen Oberflächennormalenvektor, den modifizierten Geometrie-Shader-Main()-Code bzw. die Ausgabeergebnisse.
Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.2 Anwenden durchschnittlicher dreieckiger Oberflächennormalenvektoren auf Dreieckseckpunkte

Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.3 Modifizierter Geometrie-Shader für „explodierenden“ Torus

Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.4 „Explodierter“ Torus

Das Erscheinungsbild eines „explodierten“ Torus kann verbessert werden, indem sichergestellt wird, dass auch das Innere des Torus sichtbar ist (normalerweise würden diese Dreiecke von OpenGL ausgesondert, da sie die „Rückseite“ bilden). Eine Lösung besteht darin, den Torus zweimal zu rendern, einmal auf normale Weise und einmal mit umgekehrter Umhüllungsreihenfolge (durch die Umkehrung der Umhüllungsreihenfolge wird effektiv vertauscht, welche nach vorne und welche nach hinten zeigen). Wir senden auch ein Flag an den Shader (über die Uniform-Variable), um diffuses und spiegelndes Licht auf den nach hinten gerichteten Dreiecken zu deaktivieren, damit sie weniger hervorstechen. Die Codeänderungen sind wie folgt.

Änderungen an der display()-Funktion:

Fügen Sie hier eine Bildbeschreibung ein

Änderungen am Fragment-Shader:

Fügen Sie hier eine Bildbeschreibung ein
Der resultierende „explodierte“ Torus, einschließlich der Rückseite, ist in Abbildung 13.5 dargestellt.

Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.5 „Explodierter“ Torus, einschließlich Rückseite

13.3 Grundelemente löschen

Eine häufige Verwendung von Geometrie-Shadern besteht darin, aus einfachen Objekten reichhaltige dekorative Objekte zu erstellen, indem einige Grundelemente rational entfernt werden. Wenn wir beispielsweise einige Dreiecke aus unserem Torus entfernen, kann dieser in eine komplexe Gitterstruktur umgewandelt werden, die von Grund auf viel schwieriger zu modellieren wäre. Der Geometrie-Shader, der dies tut, wird in Programm 13.2 gezeigt, und die Ausgabe ist in Abbildung 13.6 dargestellt.
Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.6 Geometrie-Shader: Grundelemente löschen

Programm 13.2 Geometry Shader: Primitive entfernen
Fügen Sie hier eine Bildbeschreibung ein
Es sind keine weiteren Änderungen am Code erforderlich. Bitte beachten Sie, dass hier die Mod-Funktion verwendet wird – alle Scheitelpunkte werden übergeben, mit Ausnahme des ersten Scheitelpunkts von jeweils drei Grundelementen, der ignoriert wird. Auch hier kann das Rendern nach hinten gerichteter Dreiecke den Realismus verbessern, wie in Abbildung 13.7 dargestellt.
Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.7: Elementlöschung auf der Rückseite

13.4 Grafikelemente hinzufügen

Die vielleicht interessanteste und nützlichste Verwendung von Geometrie-Shadern besteht darin, dem gerenderten Modell zusätzliche Scheitelpunkte und/oder Grundelemente hinzuzufügen. Dadurch ist es möglich, beispielsweise die Details des Objekts zu erhöhen, um die Höhenkarte zu verbessern, oder die Form des Objekts vollständig zu ändern.

Betrachten Sie das folgende Beispiel, in dem wir jedes Dreieck im Torus in eine winzige Dreieckspyramide umwandeln.

Unsere Strategie ähnelt unserem vorherigen Beispiel eines „explodierenden“ Torus, dargestellt in Abbildung 13.8. Die Eckpunkte des übergebenen Dreiecksprimitivs werden verwendet, um die Basis der Pyramide zu definieren. Die Wände der Pyramide bestehen aus diesen Eckpunkten und neuen Punkten (sogenannte „Spitzenpunkte“), die durch Mittelung der Normalenvektoren der ursprünglichen Eckpunkte berechnet werden. Der neue Normalenvektor für jede der drei „Seiten“ der Pyramide wird dann durch das Kreuzprodukt der beiden Vektoren vom Spitzenpunkt zur Basis berechnet.
Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.8 Umwandeln eines Dreiecks in eine Pyramide

Der Geometrie-Shader in Programm 13.3 führt dies für jedes Dreiecksgrundelement im Torus durch. Für jedes Eingabedreieck werden drei Dreiecksprimitive ausgegeben, also insgesamt neun Eckpunkte. Jedes neue Dreieck wird in der Funktion makeNewTriangle() konstruiert, die dreimal aufgerufen wird. Es berechnet den Normalenvektor des angegebenen Dreiecks und ruft dann die Funktion aufsetOutputValues(), um jedem ausgegebenen Scheitelpunkt das entsprechende Ausgabescheitelpunktattribut zuzuweisen. Nachdem alle drei Scheitelpunkte ausgegeben wurden, wird EndPrimitive() aufgerufen. Um sicherzustellen, dass die Beleuchtung genau erfolgt, wird für jeden neu erstellten Scheitelpunkt ein neuer Wert für den Beleuchtungsrichtungsvektor berechnet.

Programm 13.3 Geometry Shader: Primitive hinzufügen
Fügen Sie hier eine Bildbeschreibung ein

Die resultierende Ausgabe ist in Abbildung 13.9 dargestellt. Wenn die Spikelängenvariable (sLen) erhöht wird, wird die hinzugefügte Oberflächenpyramide höher. Ohne Schatten sehen sie jedoch möglicherweise nicht realistisch aus. Das Hinzufügen von Schattenkarten zu Programm 13.3 bleibt als Übung übrig.

Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.9 Geometrie-Shader: Grundelemente hinzufügen

Durch sorgfältige Anwendung dieser Technik können Spitzen, Dornen und andere feine Oberflächenvorsprünge oder umgekehrte Vertiefungen, Grübchen (Referenzen [DV14, TR13, KS16]) usw. simuliert werden.

13.5 Elementtyp ändern

OpenGL ermöglicht das Ändern primitiver Typen in Geometrie-Shadern. Eine häufige Verwendung dieser Funktion besteht darin, ein Eingabedreieck in ein oder mehrere Ausgabeliniensegmente umzuwandeln, um Fell oder Haare zu simulieren. Während die Generierung überzeugender Haare nach wie vor eines der schwierigeren Projekte in der realen Welt ist, können Geometrie-Shader in vielen Situationen dabei helfen, ein Echtzeit-Rendering zu erreichen.

Programm 13.4 zeigt einen Geometrie-Shader, der jedes Eingabedreieck mit 3 Scheitelpunkten in ein nach außen gerichtetes Liniensegment mit 2 Scheitelpunkten umwandelt. Zunächst wird der Startpunkt des Haarbündels berechnet, indem die Eckpunkte des Dreiecks gemittelt werden, um den Schwerpunkt des Dreiecks zu generieren. Als Endpunkt des Haares wird dann derselbe „Spitzenpunkt“ wie in Programm 13.3 verwendet. Das Ausgabegrundelement wird als Liniensegment mit zwei Scheitelpunkten angegeben, wobei der erste Scheitelpunkt der Startpunkt und der zweite Scheitelpunkt der Endpunkt ist. Die Ergebnisse für die Instanziierung eines Torus mit der Dimension 72 Scheiben sind in Abbildung 13.10 dargestellt.

Fügen Sie hier eine Bildbeschreibung ein

Abbildung 13.10 Dreiecksgrundelemente in Liniengrundelemente ändern

Dies ist natürlich nur der Ausgangspunkt für die Herstellung völlig realistischer Haare. Um das Haar zu biegen oder zu bewegen, sind mehrere Modifikationen erforderlich, z. B. das Generieren weiterer Eckpunkte für die Linie und das Berechnen ihrer Position entlang der Kurve und/oder das Einbeziehen von Zufälligkeiten. Da das Liniensegment keine offensichtliche Oberflächennormale hat, kann die Beleuchtung kompliziert sein; in diesem Beispiel geben wir einfach an, dass der Normalenvektor mit der Oberflächennormalen des ursprünglichen Dreiecks übereinstimmt.

Programm 13.4 Geometrie-Shader: Primitive-Typ ändern

Fügen Sie hier eine Bildbeschreibung ein

Weitere Informationen

Einer der Vorteile von Geometrie-Shadern besteht darin, dass sie relativ einfach zu verwenden sind. Während viele Anwendungen von Geometrie-Shadern mithilfe von Tessellation implementiert werden können, sind sie aufgrund der Mechanik von Geometrie-Shadern im Allgemeinen einfacher zu implementieren und zu debuggen. Natürlich hängt die relative Eignung von Geometrie gegenüber Tessellation von der jeweiligen Anwendung ab.

Die Erzeugung überzeugend realistischer Haare oder Felle ist eine Herausforderung und erfordert je nach Anwendungsszenario unterschiedliche Techniken. In einigen Fällen reicht eine einfache Textur aus, oder es können Tessellations- oder Geometrie-Shader verwendet werden, wie beispielsweise die in diesem Kapitel gezeigten Grundtechniken. Bewegung (Animation) und Beleuchtung werden schwierig, wenn realistischere Effekte erforderlich sind. Zwei spezialisierte Tools zur Haar- und Fellerzeugung sind HairWorks und TressFX. HairWorks ist Teil der NVIDIA GameWorks-Suite [GW18], während TressFX von AMD entwickelt wird [TR18]. Ersteres funktioniert sowohl mit OpenGL als auch mit DirectX, während letzteres nur mit DirectX funktioniert. Beispiele für die Verwendung von TressFX finden Sie in [GP14].

Supongo que te gusta

Origin blog.csdn.net/weixin_44848751/article/details/131198434
Recomendado
Clasificación