Die Rendering-Pipeline ist die Abfolge von Schritten, die OpenGL beim Rendern von Objekten durchführt. In dem folgenden Beitrag möchten wir ihnen die grundlegenden Schritte in der Rendering Pipeline etwas näher vorstellen.
Pipeline.
Die OpenGL-Rendering-Pipeline wird beim Ausführen eines Rendervorgangs initiiert. Rendering-Operationen erfordern das Vorhandensein eines ordnungsgemäß definierten Vertex-Array-Objekts und eines verknüpften Programm-Pipeline-Objekts, das die Shader für die programmierbaren Pipeline-Phasen bereitstellt.
Nach dem Start arbeitet die Pipeline in der folgenden Reihenfolge:
- Vertex-Verarbeitung:
- Jeder aus den Vertex-Arrays abgerufene Vertex (wie vom VAO definiert) wird von einem Vertex-Shader bearbeitet. Jeder Vertex im Datenstrom wird der Reihe nach zu einem Output-Vertex verarbeitet.
- Optionale primitive Tessellierungsstufen.
- Optionale Verarbeitung primitiver Geometry Shader. Der Output ist eine Sequenz von Primitiven.
- Vertex Post-Processing, der Outputs der letzten Stufe werden angepasst oder an verschiedene Standorte versandt.
-
Feedback über die Transformation
-
Primitives Clipping, die perspektivische Unterteilung und das Ansichtsfenster verwandeln sich in den Window Space.
-
-
Einfache Montage.
-
Scan-Konvertierung und Interpolation primitiver Parameter, die eine Anzahl von Fragmenten erzeugt.
-
Ein Fragment-Shader verarbeitet jedes Fragment. Jedes Fragment erzeugt eine Anzahl von Outputs.
-
Per-Sample-Processing, einschließlich, aber nicht beschränkt auf:
-
Scissor Test
-
Stencil Test
-
Depth Test
-
Blending
-
Logical Operation
-
Write Mask
-
Vertex-Spezifikation.
Bei der Spezifikation von Node Points erstellt die Anwendung eine geordnete Liste von Node Points, die an die Pipeline gesendet werden sollen. Diese Node Points definieren die Grenzen eines Primitivs.
Primitive sind grundlegende Zeichnungsformen, wie Dreiecke, Linien und Punkten.
Dieser Teil der Pipeline befasst sich mit einer Reihe von Objekten wie Vertex Array Objects und Vertex Buffer Objects. Vertex-Array-Objekte definieren, welche Daten jeder Vertex hat, während Vertex-Puffer-Objekte die eigentlichen Vertex-Daten selbst speichern.
Die Daten eines Vertex bestehen aus einer Reihe von Attributen. Jedes Attribut ist ein kleiner Satz von Daten, mit denen die nächste Stufe Berechnungen durchführt. Während ein Satz von Attributen einen Vertex spezifiziert, gibt es nichts, was besagt, dass ein Teil des Attributsatzes eines Vertex eine Position oder ein Normal sein muss. Die Attributdaten sind völlig willkürlich. Die einzige Bedeutung, die ihnen zugewiesen wird, geschieht in der Verarbeitungsphase des Vertex.
Vertex Rendering.
Sobald die Vertex-Daten richtig spezifiziert sind, werden sie anschließend über einen Zeichenbefehl als Primitiv gerendert.
Vertex-Verarbeitung.
Vertices, die aufgrund der vorherigen Vertex-Rendering→Phase geholt wurden, beginnt hier ihre Verarbeitung. Die Stufen der Vertex-Verarbeitung sind fast alle programmierbaren Operationen. Dadurch kann der Benutzercode die Art und Weise der Verarbeitung von Vertices anpassen. Jede Stufe stellt eine andere Art von Shader-Operation dar.
Viele dieser Stufen sind optional.
Vertex Shader.
Vertex-Shader führen eine grundlegende Verarbeitung jedes einzelnen Vertex durch. Vertex-Shader empfangen die Attributeingaben von der Vertex-Rendering-Funktion und konvertieren jeden eingehenden Vertex in einen einzelnen ausgehenden Vertex, basierend auf einem beliebigen, benutzerdefinierten Programm.
Vertex-Shader können benutzerdefinierte Ausgaben haben, aber es gibt auch eine spezielle Ausgabe, die die Endposition des Vertex repräsentiert. Wenn es keine nachfolgenden Verarbeitungsstufen für den Vertex gibt, wird erwartet, dass die Vertex-Shader diese Position für das Rendern mit der Clip-Space-Position des Vertex ausfüllen.
Eine Einschränkung bei der Verarbeitung von Vertices ist, dass jeder Eingangsvertex auf einen bestimmten Ausgangsvertex abgebildet werden muss. Und da die Aufrufe von Vertex-Shadern nicht den gleichen Zustand haben können, sind die Eingangsattribute für die Abbildung der Ausgangsvertextdaten 1:1. Das heißt, wenn Sie die exakt gleichen Attribute an denselben Vertex-Shader im selben Primitiv füttern, erhalten Sie die gleiche Ausgabe-Vertex-Daten. Dies gibt Implementierungen das Recht, die Vertex-Verarbeitung zu optimieren: wenn sie erkennen können, dass sie dabei sind, einen zuvor verarbeiteten Vertex zu verarbeiten, können sie die zuvor verarbeiteten Daten verwenden, die in einem Post-Transformations-Cache gespeichert sind. So müssen sie die Vertex-Verarbeitung nicht erneut auf diesen Daten ausführen.
Vertex-Shader sind nicht optional.
Primitive können mit Hilfe von zwei Shader-Stufen und einem Tessellator mit fester Funktion zwischen ihnen tesseliert werden. Die Tessellation Control Shader (TCS) Stufe steht an erster Stelle und bestimmt die Menge der Tesselierung, die auf ein Primitiv anzuwenden ist und stellt die Verbindung zwischen benachbarten tesselierten Primitiven sicher. Die Tessellation Evaluation Shader (TES)-Stufe kommt als letzte und sie wendet die Interpolation oder andere Operationen an, die zur Berechnung benutzerdefinierter Datenwerte für die durch den Tessellationsprozess mit fester Funktion erzeugten Primitive verwendet werden.
Die Tessellierung als Prozess ist optional. Tessellierung wird als aktiv betrachtet, wenn ein TES aktiv ist. Der TCS ist optional, aber ein TCS kann nur zusammen mit einem TES verwendet werden.
Geometrie-Shader sind benutzerdefinierte Programme, die jedes eingehende Primitiv verarbeiten und null oder mehr Output-Primitive zurückgeben.
Die Input-Primitive für Geometrie-Shader sind die Output-Primitive aus einer Untermenge des Primitivzusammensetzungsprozesses. Wenn Sie also einen Dreiecksstreifen als einzelnes Primitiv senden, sieht der Geometrie-Shader eine Reihe von Dreiecken.
Es gibt jedch eine Reihe von Input-Primitivtypen, die speziell für Geometrie-Shader definiert sind. Diese Adjazenz-Primitive geben den GS eine größere Ansicht der Primitive. Sie bieten Zugang zu Vertices von Primitiven, die an das aktuelle angrenzen.
Der Output eines GS ist null oder einfachere Primitive, ähnlich wie der Output einer primitiven Baugruppe. Der GS ist in der Lage, Primitive zu entfernen oder sie zu tessellieren, indem dieser viele Primitive für einen einzigen Input ausgibt. Der GS kann auch an den Vertex-Werten selbst herumbasteln, indem dieser entweder einen Teil der Arbeit für den Vertex-Shader erledigt oder einfach die Werte bei der Tessellierung interpoliert, Geometrie-Shader können sogar Primitive in verschiedene Typen konvertieren. Input-Point-Primitive können zu Dreiecken oder Linien zu Punkten werden.
Geometrieshader sind optional.
Vertex-Nachbearbeitung.
Nach der Shader-basierten Vertex-Verarbeitung durchlaufen die Vertices eine Reihe von Verarbeitungsschritten mit fester Funktion.
Feedback transformieren.
Die Outputs des Geometrie-Shaders oder der primitiven Baugruppe werden in eine Reihe von Pufferobjekten geschrieben, die für diesen Zweck eingerichtet wurden. Dies wird als Transformations-Feedback-Modus bezeichnet. Dieser ermöglicht es dem Benutzer, Daten über Vertex- und Geometrie-Shader zu transformieren und diese Daten anschließend für die spätere Verwendung zu behalten.
Die Daten, die in den Transformations-Feedback-Puffer ausgegeben werden, sind die Daten von jedem Primitiv, das durch diesen Schritt ausgegeben wird.
Clipping.
Die Primitive werden anschließend beschnitten. Beschneiden bedeutet, dass Primitive, die auf der Grenze zwischen der Innenseite des Betrachtungsvolumens und der Außenseite liegen, in mehrere Primitive aufgeteilt werden, so dass das gesamte Primitiv im Volumen liegt. Außerdem kann die letzte Shader-Stufe der Vertex-Verarbeitung benutzerdefinierte Beschneidungsoperationen auf der Basis einen einzelnen Vertex festlegen.
Die Vertex-Positionen werden mit Hilfe der Perspektiventrennung und der Ansichtsfenstertransformation vom Clip-Space in den Fensterbereich transformiert.
Primitive Assembly.
Primitive Assemblierung ist der Prozess, bei dem eine Reihe von Verteildaten, die von den vorherigen Stufen ausgegeben wurden, gesammelt und zu einer Sequenz von Primitiven zusammengesetzt werden. Der Typ des Primitivs, mit dem der Benutzer gerendert wird, bestimmt, wie dieser Prozess funktioniert.
Der Output dieses Prozesses ist eine geordnete Folge einfacher Primitive (Linien, Punkte oder Dreiecke). Wenn die Eingabe ein Dreiecksstreifen-Primitiv ist, das z.B. 12 Eckpunkte enthält, dann ist der Output dieses Prozesses 10 Dreiecke.
Wenn Tesselierung oder Geometrie-Shader aktiv sind, dann wird vor diesen Vertex-Verarbeitungsstufen eine begrenzte Form der Primitivanordnung ausgeführt. Dies wird verwendet, um diese speziellen Shader-Stufen mit einzelnen Primitiven zu füttern und nicht mit einer Folge von Vertices.
Die Rendering-Pipeline kann in dieser Phase auch abgebrochen werden. Dies ermöglicht die Verwendung von Transform-Feedback-Operationen, ohne dass tatsächlich etwas gerendert werden muss.
Dreiecksprimitive können auf der Grundlage der Ausrichtung des Dreiecks im Fensterraum geculled (d.h. ohne Rendering verworfen) werden. Auf diese Weise können Sie vermeiden, dass Dreiecke gerendert werden, die dem Betrachter abgewandt sind. Bei geschlossenen Oberflächen würden solche Dreiecke natürlich durch dem Benutzer zugewandte Dreiecke überdeckt, so dass sie nie gerendert werden müssen. Face Culling ist eine Möglichkeit, das Rendern solcher Primitive zu vermeiden.
Primitive, die dieses Stadium erreichen, werden anschließend in der Reihenfolge gerastert, in der sie gegeben wurden. Das Ergebnis der Rasterung eines Primitivs ist eine Folge von Fragmenten.
Ein Fragment ist ein Satz von Zuständen, der zur Berechnung der endgültigen Daten für ein Pixel (oder ein Sample, wenn Multisampling aktiviert ist) im Output-Image-Buffer verwendet wird. Der Zustand für ein Fragment umfasst seine Position im Screen-Space, die Abtastabdeckung, wenn Multisampling aktiviert ist und eine Liste beliebiger Daten, die vom vorherigen Vertex oder Geometrie-Shader ausgegeben wurden.
Dieser letzte Datensatz wird durch Interpolation zwischen den Datenwerten in den Vertices für das Fragment berechnet. Die Art der Interpolation wird durch den Shader definiert, der diese Werte ausgegeben hat.
Fragment-Verarbeitung.
Die Daten für jedes Fragment aus der Rasterisierungsphase werden von einem Fragment-Shader verarbeitet. Der Output eines Fragment-Shaders ist eine Liste von Farben für jeden der Color Buffer, in die geschrieben wird, ein Tiefenwert und ein Schablonenwert. Fragment-Shader sind nicht in der Lage, die Schablonendaten für ein Fragment festzulegen, aber sie haben die Kontrolle über die Farb- und Tiefenwerte.
Fragmentshader sind optional. Wenn Sie ohne einen Fragment-Shader rendern, erhalten die Tiefen- und Schablonenwerte des Fragmentes Ihre üblichen Werte. Aber die Werte aller Farben, die ein Fragment haben könnte, sind nicht definiert. Das Rendern ohne Fragment-Shader ist nützlich, wenn nur die Standardtiefeninformationen eines Primitivs in den Depth Buffer gerendert werden, z.B. bei der Durchführung von Occlusion Query Tests.
Operationen pro Stichprobe.
Die vom Fragmentprozessor ausgegebenen Fragmentdaten werden anschließend eine Folge von Schritten durchlaufen.
Der erste Schritt ist eine Sequenz von Culling-Tests. Wenn ein Test aktiv ist und das Fragment nicht den Test besteht, werden die zugrunde liegenden Pixel/Samples (normalerweise) nicht aktualisiert. Viele dieser Tests sind nur aktiv, wenn der Benutzer sie aktiviert. Folgende Tests sind aktiv:
-
Pixel Ownership Test: Schlägt fehl, wenn das Pixel des Fragments nicht im Besitz von OpenGL ist (wenn ein anderes Fenster mit dem GL-Fenster überlappt). Besteht immer, wenn ein Framebuffer-Objekt verwendet wird. Fehlschlag bedeutet, dass das Pixel undefinierte Werte enthält.
-
Scissor Test: Wenn aktiviert, schlägt der Test fehl, wenn das Pixel des Fragments außerhalb eines bestimmten Rechtecks des Bildschirms legt.
-
Stencil Test: Wenn diese Option aktiviert ist, schlägt der Test fehl, wenn der vom Test gelieferte Stencilwert nicht mit dem Stencilwert der zugrunde liegenden Probe im Stencil-Buffer verglichen werden kann, wie vom Benutzer angegeben. Beachten Sie, dass der Stencilwert im Framebuffer auch anschließend noch geändert werden kann, wenn der Stencil-Test fehlschlägt (und selbst dann, wenn der Depth-Test fehlschlägt).
-
Depth Test: Wenn diese Option aktiviert ist, schlägt der Test fehl, wenn die Tiefe des Fragments nicht mit dem Tiefenwert aus der zugrunde liegenden Probe im Depth-Buffer verglichen wird, wie vom Benutzer angegeben.
Hinweis: Obwohl diese nach dem Fragment-Shader spezifiziert sind, können sie unter bestimmten Bedingungen vor dem Fragment-Shader ausgeführt werden. Wenn sie vor dem FS geschehen, dann verhindert jedes Culling des Fragments auch die Ausführung des Fragment-Shaders, was die Leistung spart.
Danach erfolgt das Color Blending. Für jeden Farbwert eines Fragments gibt es eine spezifische Überblendungsoperation zwischen ihm und der Farbe, die sich bereits im Framebuffer an dieser Stelle befindet. Anstelle des Überblendens können auch logische Operationen stattfinden, die bitweise Operationen zwischen den Fragment- und Framebuffer-Farben durchführen.
Schließlich werden die Fragment-Daten in den Framebuffer geschrieben. Maskierungsoperationen ermöglichen es dem Nutzer, das Schreiben auf bestimmte Werte zu verhindern. Color, Depth und Stencil-Schreiben können ein- und ausgeschaltet werden. Einzelne Farbkanäle können ebenfalls maskiert werden.
Vielen Dank für Ihren Besuch.