Die Rendering-Pipeline oder Grafikpipeline beschreibt beim Echtzeitrendering den Weg von der vektoriellen, mathematischen Beschreibung einer 3D-Szene zum gerasterten Bild auf dem Monitor. Sie übernimmt dabei Dinge wie die Umrechnung von Bildschirmkoordinaten in Gerätekoordinaten, Texturierung, das Clipping oder auch das Antialiasing.
Die 3D-Daten liegen normalerweise in Form von so genannten Vertex-Streams vor. Ein Vertex definiert einen „Eckpunkt“ eines beliebigen Objektes. Diese sind meist Meshes von 3D-Modellen oder Primitive. Je nach Format kann es sich dabei um Listen von Dreiecken versch. Formatierung oder auch Linienlisten oder einfache Punktlisten handeln. Die Vertizes werden in verschiedenen Einzelschritten transformiert, wobei das Ergebnis jeder Transformation der Ausgangspunkt der nächsten Transformation ist, bis ein fertiges 3D-Bild auf dem Bildschirm zu sehen ist. Die Gesamtheit dieser hintereinander geschalteten Transformationen nennt man Pipeline. Das Abbilden der Weltkoordinaten auf Bildschirmkoordinaten wird Screen Mapping genannt.
Aufgrund der hohen Parallelisierbarkeit der Aufgaben ist es möglich mehrere Pipelines auf einem Chip zu realisieren. So finden sich auf der NVidia Geforce 7800 GTX 8 Vertex- und 24 Pixelpipelines, die jeweils vollständig unabhängig voneinander arbeiten können ( siehe auch SIMD ). Ein weiterer Grund für Pipelining ist, dass die Aufgaben auf verschiedene Einheiten aufgeteilt werden können und diese somit gleichzeitig arbeiten können. Da die durchzuführenden Berechnungen stets die gleichen sind, wird die Pipeline komplett in Hardware realisiert, was einen enormen Geschwindigkeitsvorteil mit sich bringt. Eine Ausnahme bilden Vertex- und Pixel-Shader, da diese frei programmierbar sind. Die Rechenleistung moderner GPUs übersteigt die von CPUs mittlerweile deutlich, was Fließkommaoperationen betrifft. Beispielsweise erledigt die Geforce 7800 GTX in einer Sekunde bis zu 280 Mrd. Gleitkommaberechnungen. Ein Pentium 4 mit SSE2 erzielt maximal 12 GigaFLOPS.
Zum Parametrisieren solcher Grafikpipelines werden sogenannte Grafik-APIs wie zum Beispiel DirectX oder OpenGL verwendet. Diese nehmen dem Programmierer viele Aufgaben ab, so dass dieser nicht mehr auf die Hardware direkt zugreifen muss und damit unabhängiger vom System wird. Man sagt, sie abstrahieren die darunterliegende Hardware.
Zunächst wird versucht, die Anzahl der zu verarbeitenden Vertizes so klein wie möglich zu bekommen. Um dies zu erreichen, wird zunächst mit Hidden Surface Removal-Verfahren wie Binary Space Partitioning, Octrees oder Portal-Based Rendering ein Großteil der Vertizes, die von der Kamera aus nicht gesehen werden können, von der Rendering-Pipeline ausgeschlossen. Dieser Schritt wird in Software ausgeführt und ist meist als Teil von Grafik-Engines implementiert.
Geometry Processing
Die Renderpipeline selbst beginnt mit dem Vertex Processing. Hier werden alle notwendingen Transformationen, die Vertices betreffen (Rotation, Skalierung, Translation, Lichtberechnungen, etc.), vorgenommen. Alle folgenden Schritte finden fest verdrahtet auf der GPU statt. Alternativ dazu kann auch der frei programmierbare Vertexshader verwendet werden. Zur besseren Verarbeitung werden alle Koordinaten homogenisiert. Dazu wird ein mit dem Wert 1 initialisierter Wert ( meist “w” genannt ) zu jeder Vertex-Koordinate hinzugefügt.
Um eine möglichst effiziente Abarbeitung der 3D-Informationen gewährleisten zu können, müssen alle Eingangsdaten in ein einheitliches Format überführt werden. Dieser Vorgang wird Tesselation genannt. Hier werden die Primitive (höherer Ordnung) bzw. Meshes in ihre eigentlichen Bestandteile, Dreiecke, Linien und Punkte reduziert, die durch ihre Eckpunkte (Vertices) definiert werden, zerlegt.
Diese Daten können optional mit Hilfe von diversen Mapping-Verfahren nachbearbeitet werden. So ist es beispielsweise möglich ein Höhenrelief durch das Anwenden einer Displacement Map auf eine planare Oberfläche zu übertragen und dieser damit eine rauhe Struktur zu verleihen. Dazu werden anhand von Texturinformationen die Vertices eines Objektes manipuliert. Dieser Vorgang wird Displacement Mapping genannt. Bei Bump Mapping werden hingegen zwar ebenfalls Höheninformationen aus einer Textur gelesen, jedoch wird die Geometrie nicht verändert, sondern gaukelt die Oberflächen-Unebenheiten nur vor. Diese Technik ist deutlich performanter als Displacement-Mapping, liefert jedoch kein perfektes Ergebnis. Beispielsweise bleibt die Silhouette eines Objekts dadurch unverändert. Alternativ dazu bietet das Normal Mapping die Möglichkeit, mehrere verschiedene Normalenvektoren für ein Dreieck zu verwenden. Die benötigten Vektor-Informationen werden aus einer Textur gelesen. Das RGB-Farbtripel eines jeden Texels wird dazu in die XYZ-Koordinaten des Vektors umgewandelt. Die Textur kann von 3D-Modellierungssoftware automatisch erzeugt werden. Voraussetzung ist natürlich ein entsprechend hochwertiges Modell. Diese Methode wird bei neu entwickelten Anwendungen und Spielen bevorzugt verwendet, da sie äußerst realistische Effekte erzeugt. Die Silhouette eines Objekts verändern kann sie jedoch ebenfalls nicht.
Zum Zeitpunkt der Übergabe an der Rendering-Pipeline befinden sich alle Vertex-Koordinaten im verschiedenen lokalen Koordinatensystemen, die sich relativ zu den vorhandenen Objekt befinden. Diese Ausrichtung wird auch "model space" genannt. So orientieren sich beispielsweise alle Vertices eines 3D-Modells an dessen Ursprung. Daher werden daher alle lokalen Koordinaten umgewandelt und in ein gemeinsam genutztes eingetragen. Dieses neue, globale Koordinatensystem wird auch "world space" und die dazugehörige Berechnung "world transformation" genannt.
Die View-Matrix legt Standort und Blickrichtung fest, mit der die Szene aufgenommen wird. Man kann sich darunter auch die "virtuelle" Kamera vorstellen. Manche APIs, wie z. B. OpenGL vereinen View- und Worldtransformation zu einer ModelView Transformation, da sie den selben Vorgang, nur in entgegengesetzter Richtung beschreiben. Ob sich die Kamera von der Szene entfernt, oder die Szene von einer fix-positionierten Kamera, ist gleichgültig. Gleiches gilt auch für Rotation und Skalierung.
Im nächsten Schritt werden mit Hilfe des Beleuchtungsmodells und des gewählten Shading-Verfahrens alle nötigen Informationen über die Art des Lichts und dessen Verteilung berechnet. Beim Beleuchtungsmodell handelt es sich um eine Bidirektionale Reflektanzverteilungsfunktion. Sie gibt an, wie sich die Intensität und Farbe für einen Objektpunkt mit gegebener Normale berechnet. Das gängige Modell nach Phong hat sich als einigermaßen realistisch profiliert, ist jedoch vollständig empirisch, d.h. es basiert auf keiner wissenschaftlichen Grundlage. Nur so ist zu erklären, dass es nicht einmal den Energieerhaltungssatz erfüllt. In mittlerer Zukunft könnte das Phong-Beleuchtungsmodell durch das Schlick-Beleuchtungsmodell abgelöst werden. Auch das Per-Pixel-Lighting gewinnt immer mehr an Bedeutung. Das Shading bestimmt dann, für welche Normalen die Berechnungen des Beleuchtungsmodelles durchführt werden. Der bekannteste Vertreter dieser Art ( benannt nach seinem Entwickler Henri Gouraud, der es erstmals 1971 vorstellte ) ist das Gouraud Shading. Dabei werden die Farbwerte an den Eckpunkten ( Vertices ) des Polygons berechnet und jeder Punkt der Oberfläche wird aus diesen Werten linear interpoliert. Um Farbverläufe möglichst weich darzustellen, werden stets auch benachbarte Polygone in die Berechnung miteinbezogen, so dass an den Kanten jeweils gleiche Farbwerte berechnet werden. Alternativ dazu interpoliert das Phong Shading ( nicht zu verwechseln mit dem Beleuchtungsmodell ) die Normalen der Vertices (zuerst linear über die Kanten, dann linear auch ins Innere des Polygons) statt der Farbwerte. Diese Normalen werden dann bei der Beleuchtung zur Berechnung der Farbe genutzt. Das ist zwar deutlich aufwändiger, allerdings werden Glanzlichter, die beim Gouraud-Shading oft zu schwach oder gar nicht dargestellt werden, deutlich sichtbar. Objekte, die mit Phong-Shading berechnet werden, haben daher meist eine Plastik-ähnliche Oberfläche.
Sämtliche Koordinaten befinden sich immer noch in einem dreidimensionalen Raum, relativ zur Kamera-Position und Drehung. Im letzten Schritt der Transformation werden diese nun auf eine zwei-dimensionale Ebene projiziert. Dabei unterscheidet man zwischen perspektivischer und orthogonaler Projektion. Bei Ersterer spricht man auch von Fluchtpunkt-Projektion, da sich der Sichtbarkeitsbereich wie eine Pyramide verhält. Die “Spitze” dieser Pyramide befindet sich an der Position der virtuellen Kamera. Bei der orthogonalen Projektion verhält sich der Sichtbarkeitsbereich wie ein Quader. Das bedeutet unter Anderem, dass auch entfernte Objekte nicht kleiner dargestellt werden als nahe.
Im nächsten Schritt werden normalerweise alle Vertices aus der Verarbeitung ausgenommen, die zu Oberflächen gehören deren Vorderseite vom Betrachter wegzeigt. Welche Seite zur Kamera zeigt, lässt sich entweder über die vorgegebenen Normalen oder die Reihenfolge der Vertices bestimmen. Diese Methode bezeichnet man als Back Face Culling und wird nur optional verwendet.
Anschließend wird die Menge der Vertices auf diejenigen reduziert, die überhaupt im Sichtbereich liegen. Dazu prüft man jeden Vertex ob er sich zwischen "Near" und "Far" Clipping Plane befindet. Dabei handelt es sich um künstliche Entfernungsbegrenzer. Vertices, die zu weit weg oder zu nah sind, werden nicht gerendert. Als nächstes wird überprüft, ob sich der Vertex auch zwischen den vier Ebenen befindet, die die Ränder des Sichtbereichs bilden. Gehört ein abgeschnittener Vertex zu einer Oberfläche, die zum Teil innerhalb des Sichtbereichs liegt, so werden für die Oberfläche neue Vertices generiert, die direkt auf der Sichtgrenze liegen und dadurch auf den Sichtbereich zurecht geschnitten. Diesen Vorgang bezeichnet man als Frustum Culling.
Die letzten beiden Schritte des Geometry Processing sind trivial : Bei der Perspective Division werden die homogenen Koordinaten wieder in dreidimensionale zurückgerechnet, indem man den gesamten Vektor mit (1 / w) multipliziert, so dass dieser wieder den Wert 1 erhält. Abschließend skaliert die Viewport Transformation-Matrix alle vorhanden Werte, so dass sie auf “Pixel-Koordinaten” übertragen werden können. Dazu gehört auch das Höhen- und Seitenverhältnis des Monitors miteinzubeziehen. Der jetzt nicht mehr benötigte Z-Wert der Koordinate entspricht dem Abstand eines Vertexes zur Bildebene und wird im sogenannten Z-Buffer gespeichert und bei der Rasterisierung weiterverwendet. Dieser Puffer hat deshalb auch exakt dieselbe Größe des zu rendernden Bildes.
Damit ist der erste große Teil der Rendering-Pipeline abgeschlossen. Die Scan Conversion bildet den Übergang zwischen Vertex- und Pixelprocessing und kann deswegen nicht zugeordnet werden und kann auch von keiner Shader-Einheit programmiert werden. Man unterscheidet zwischen zwei Verfahren : der Linearen Interpolation und der selten verwendeten linearen Kanteninterpolation. Bei letzterem Verfahren wird das Dreieck als drei lineare Funktionen interpretiert, die sich in den Eckpunkten schneiden. Ausgehend vom Mittelpunkt wird dann jeder Pixel berechnet bis alle drei Kanten erreicht werden. Beim der gebräuchlicheren linearen Interpolation wird das Dreieck durch einen horizontalen Schnitt so in zwei Kleinere zerlegt, dass beide eine horizontable Grundlinie haben. Von dieser ausgehend wird die “Steigung” der Kanten berechnet und anschließend in Zeilen zerlegt. Diese können dann problemlos in einzele Pixel aufgespalten werden.
Rasterization
Die mittlerweile aufgespaltenen, transformatierten Primitive werden nun an den zweiten Teil der Render-Pipeline, der Rasterization Unit, übergeben, die das Pixel Processing übernimmt. Hier werden die Texturen umgewandelt, modifiziert, beleuchtet und auf die Primitive übertragen.
Um unnötige Berechnungen zu vermeiden, kontrolliert der Depth-Test, ob ein Pixel überhaupt sichtbar ist. Diese Fragestellung wird auch Sichtbarkeitsproblem genannt. Dieser Test verhindert beispielsweise auch, dass Objekte im Hintergrund über Objekte im Vordergrund gerendert werden. indem er dessen Tiefenwerte vergleicht. Dies könnte passieren, falls das hintere Objekt nach dem vorderen die Renderpipeline durchläuft. Die Bedingung nach der die Auswahl getroffen wird, kann zwar geändert werden, was jedoch nur selten Sinn macht. Ein Beispielt hierfür bildet die Kombination mit dem später erwähnten Alpha Blending, das Transparenz-Effekte ermöglicht. Offensichtlich werden dabei auch hinter dem transparenten Objekt liegende Punkte zur Berechnung benötigt.
Das Texture Mapping bringt nun jeden Pixel mit einem Texel ( Textur Pixel ) in Verbindung. Dabei werden die übergebenen Texturkoordinaten, die meist mit “u” und “v”, manchmal auch “s” und “t” deklariert werden auf Bildschirmkoordinaten umgerechnet. Hierbei muss wieder die Entfernung zum Betrachter miteinbezogen werden.
Mit Hilfe von verschiedenen Filtern ist es möglich, Texturen auf die gewünschte Größe zu skalieren. Im schnellsten Verfahren, dem Nearest-Neighbor-Filtering wird für jeden benötigten Pixel wird, wie der Name schon sagt, der am nächsten liegende Pixel einfach kopiert. Das Resultat wirkt sehr “pixelig”. Beim bilinearem Filtering geht man etwas geschickter vor : es wird die gewichtete Mischfarbe der vier nächsten Pixel berechnet. Bei Vergrößerung ist das Ergebnis recht passabel, jedoch leiden bei zu starker Minifikation feine Strukturen sehr. Das liegt daran, dass stets nur vier Pixel betrachtet werden. Das deutlich aufwändigere bikubische Filtering behebt dieses Manko, findet aber nur in Bildbearbeitunssoftware Verwendung. Stattdessen greift man auf MIP-Mapping zurück. Hier werden vorab eine Reihe von verkleinerten Bilder der Textur gespeichert, was einen, um ein Drittel höheren, Speicherbedarf verursacht. Problematisch bei diesem Vorgang ist der Übergang zwischen zwei MIP-Mapping Stufen, da die kleinere Textur aufgrund ihrer Größe nur ein Viertel der Textur-Informationen besitzt. Abhilfe schafft hier das trilineare Filterung, das zusätzlich zur bilinearen Filterung innerhalb der einzelnen MIP-Maps auch zwischen zwei MIP-Mapping-Stufen interpoliert. Bei stark verzerrter Projektion allerdings kann der sogenannte Moiré-Effekt auftreten. Daher wird oft ein etwas rechenaufwändigerer Anisotropischer Filter ( meist Footprint Assembly ) verwendet. Dabei wird ein Parallelogramm anstatt eines Rechtecks zur Interpolation zu Grunde gelegt.
Für eine noch lebensnähere Darstellung einer dreidimensionalen Szene wird oft das rechenintensive Anti-Aliasing verwendet, das auch oft als “Kantenglättung” bezeichnet wird. Streng genommen handelt es sich dabei um ein Oversampling-Verfahren mit anschließender Tiefpass-Filterung. Oversampling bedeutet dass die Informationen in höherer Auflösung berechnet werden, als eigentlich notwendig wäre. Diese werden dann mit einem relativ einfachen Filter wieder heruntergerechnet. Als Ergebnis erhält man ein deutlich weicher gezeichnetes Bild, das jedoch auch an Schärfe einbüßt. Vor Allem bei kleinen, scharf umrissenen Details, wie zB. Schriftarten kann dieser Effekt auch unerwünscht sein. Auch hier unterscheidet man wieder zwischen mehreren Verfahren : Das Full Screen Anti-Aliasing ( FSAA ) berechnet eine komplette Szene inklusive aller Texturen. Im Gegensatz dazu steht das deutlich schnellere Multisampling Anti-Aliasing ( MSAA ), das nur Kanten behandelt.
Den Abschluss der Rasterisierung bildet das schon erwähnte Alpha Blending, das den zu schreibenden Farbwert mit dem eventuell bereits im Bild vorhandenen Pixel zu kombinieren. Ein Alpha-Wert von eins überschreibt alte Daten vollständig. Wird der Bildinhalt nach dem Rendern nicht gelöscht, lassen sich mit Alpha-Blending verschiedene Schmier-Effekte erzeugen. Auch transparente Objekte sind damit möglich, indem Objekte im Hintergrund nur teilweise übermalt werden. Dazu muss jedoch der Tiefenpuffer deaktivert sein.