Teil 1: Einführung in die Vertex Shader
Einleitung
Seit dem Erscheinen der ersten 3dfx Vodoo Karten, stieg die Performance immer höher an. Mehr Polygone, höhere Aulösungen. Die Grafik, besser gesagt die grafischen Fähigkeiten, bleiben aber fast immer gleich. Der Grund war/ist, das Grafikkartenentwickler Funktionen und Algorithmen, wie zB: zum Rendern von Polygonen, direkt in den Grafikchip gebrannt haben. Um diese Performance zu nutzen, musste man eben diese hard-coded Funktionen verwenden und konnte keinen eigenen Algorithmen verwenden.
Die Verwendung von Vertex Shader ist eigentlich nichts Neues. Pixar verwendet diese schon seit Jahren in ihrem Programm RenderMan, mit dem, zum Beispiel, Filme wie "Toy Story", oder "A Bug's Life" gemacht wurden. Der Vorteil von RenderMan ist, dass die Programmierer fast keine Grenzen, für die Darstellung, haben und dadurch sehr flexiblel und kreativ waren. Leider waren das aber alles nur Software-Implementationen.
Mit eben den neuen Grafikkarten sind wir jetzt in der Lage noch schönere und schnellere Grafik zu programmieren, eben indem die Funktionen nicht mehr in den Silizium-Kern gebrannt werden, sondern durch den Programmierer selbst geschrieben werden können.
Bevor wir aber jetzt in Freuden-Schreien ausbrechen: Um einen Film wie "A Bug's Life" in Real-Time zu rendern dauert es noch Jahrzehnte. Einen einzigen Frame auf einem heutigen Computer zu rendern würde ca. drei Stunden dauern und über ein Gigabyte an Geometrie-Daten benötigen.
Aufbau der Grafikpipeline
Der Aufbau der Pipeline für DirectX 8.0 hat sich um zwei Dinge geändert. Erstens die Vertex Shader und zweitens die Pixel Shader. Uns aber interessiert nur der Abschnitt, der Transformation & Beleuchtung (TnL) ersetzt. Diese TnL-Engine war dazu da, Vertices (Vektoren) noch schneller zu transformieren. Bei noch älteren Grafikkarten als solche die eine GeForce 256 GPU hatten, wurde eben dieser Teil noch von der CPU übernommen. Mit dem Aufkommen der TnL in Hardware, hatte man den Prozessor ziemlich entlastet. Die Grafikkarte transformierte und beleuchtete die Vertices von selbst. Dadurch konnte man noch mehr Vertices an die Grafikkarte schicken, als es mit dem Prozessor möglich wäre. Noch dazu war jetzt mehr Prozessor-Zeit frei für andere Dinge, wie eben KI, Physik,...
Doch eben alle Funktionen der TnL-Engine sind in den Kern gebrannt und dadurch nicht zu verändern. Vor allem der Beleuchtungs-Abschnitt gefiel nicht vielen Entwickler und so verwendeten die meisten eben nur den T-Teil.
Und eben diese Teil, der TnL Teil, ist jetzt frei programmierbar durch die Entwickler, also durch euch alle da draussen.
Was ist ein Vertex Shader?
Ein Vertex Shader ist eigentlich ein kleines Programm, das in einer Sprache geschrieben wird die Assembler ähnlich schaut. Dieses kleine, selbstgeschriebene Programm wird für jeden Vertex, den man an die Grafikkarte übergibt, ausgeführt. Ein Vertex Shader kann daher nur an einem Vertex zu einer bestimmten Zeit arbeiten. Auch kann es keine zusätzlichen Vertices erzeugen. Ein Vertex Shader kann alle Eigenschaften eines Vertex verändern, die man auch der Grafikkarte/DirectX übergibt. Jedoch kann ein VS (Vertex Shader) folgendes nicht: Polygon-Operationen, Culling, Clipping gegen den Frustum oder anderen Clipping-Planes,, andere Vertices verändern, Vertices erzeugen.
Um all diese Dinge zu verwirklichen, verwendet der VS vier verschiedene Speicherstellen. Jedes dieser Speicherstellen hat eine Breite von 4 Floats/ 128 Bit und eine bestimmte Länge. Jedes Breite (4 Floats) ist ein Eintrag. In einem Eintrag kann man also Positionsdaten (xyzw), Texturkoordinaten (uvwq), Farben (rgba) oder einfach vier Werte speichern.
Ein weiteres Ding der Unmöglichkeit ist Daten zu speichern, die länger existieren sollen als der VS ausgeführt wird. Nach jedem Vertex werden alle Register wieder auf Initial-Werte zurückgesetzt und der nächste Vertex wird durchgeschickt durch das VS-Programm.
In den nächsten Tabellen sind alle Register und deren Möglichkeiten und Eigenschaften aufgelistet. Die erste Tabelle zeigt die VS Input Register, die zweite die Output Register und die letzte alle anderen. Die "Format" Spalte zeigt an, ob dieser Speicher ein 4-dimensionaler Vektor oder ein Skalar ist. Sollte es ein Skalar sein, ist nur die erste Stelle (zB. oFog.x) gültig. "Zugriff" beschreibt die Möglichkeiten des Zugriffs und "Zugriff durch", wer darauf zugreifen kann. Die interessanteste Spalte ist jedoch "Max. Refs/Instr". Eine einzige Instruktion eines VS darf nur an eine limitierte Anzahl von verschiedenen Speicherstellen zugreifen. Zum Beispiel kann eine Instruktion nicht auf zwei unterschiedliche Constant Memory Stellen zeigen. Daher ist add r0, c[0].x, c[1].y illegal. Da auf c0 und c1 zugegriffen wird. Dagegen ist add r0, c[0].x, c[0].y legal. Und eben dies gibt die Anzahl von maximalen Refs/Instr an.
Name | Format | Zugriff | Zugriff durch | Max. Refs/Instr | Verwendung |
v0-v15 | Vektor | Nur Lesen | Anwendung/VB | 1 | Vertex Daten |
c[0]-c[95] | Vektor | Nur Lesen | Anwendung | 1 | Konstante Daten geladen von der Anwendung |
Name | Format | Zugriff | Zugriff durch | Verwendung |
oPos | Vektor | Nur Schreiben | Vertex Shader | Transformierte Clip-Space Koord. |
oD0 | Vektor | Nur Schreiben | Vertex Shader | Diffuse Farbe des Vertex |
oD1 | Vektor | Nur Schreiben | Vertex Shader | Speculäre Farbe des V. |
oT0-oT3 | Vektor | Nur Schreiben | Vertex Shader | Texturkoordinaten des V. für Textur Stage 0-3 |
oFog.x | Skalar | Nur Schreiben | Vertex Shader | Nebel Wert f.V. |
oPts.x | Skalar | Nur Schreiben | Vertex Shader | Sprite-Größe f.V. |
Name | Format | Zugriff | Zugriff durch | Max. Refs/Instr | Verwendung |
r0-r11 | Vektor | Lesen/Schreiben | Vertex Shader | 3 | Temporäre Register |
a0.x | Skalar | Verwenden/Schreiben | Vertex Shader | 1 | Indirektes Addressierung des Konstanten Speichers |
Wie man schon in der Grafik erkennt, kann ein VS maximal 128 Instruktionen lang sein. Insgesamt gibt es 17 verschiedene Instruktionen die man verwenden kann. Diese Zahlen sehen war klein aus, jedoch wird der VS für jeden übergebenen Vertex ausgeführt.
Unmöglich sind Sprünge und Schleifen in VS. Auch kann man den VS nicht vorzeitig verlassen. Jede Instruktion wird für jeden Vertex ausgeführt. Hier nun eine Auflistung der möglichen Instruktionen und deren mathematische Bedeutung:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Jede Instruktion benötigt einen Tick zum Ausführen. Das heißt, dass die VS Performance direkt proportional zu der Anzahl von verwendeten Instruktionen ist. Ein VS der nur die Hälfte eines anderen ist, wird in der Hälfte der Zeit ausgeführt. Daher sollte man unnötige Berechnungen nicht wirklich ausführen und so den VS verkürzen.
Mit Destinatin-Modifier und Source-Modifier kann man den Code noch weiter flexibel machen. Mit dem Destination-Modifier ist eigentlich eine Schreib-Maske. Dies dient dazu um nur bestimmte Komponenten eines Registers zu schreiben. Zum Beispiel mit der Instruktion mul r0.xy, v0, s0. Werden nur die xy Komponeten des Dest-Registers geschrieben. Die beiden anderen (zw) bleiben unverändert. Bei keiner Angabe eines Dest-Modifier ist gleichzusetzen mit der Verwendung von allen vier Komponeten .xyzw. Man sollte diese Möglichkeit schon verwenden, da erstens der Code lesbarer wird und zweitens mehr Optimierungen durch den Treiber durchgeführt werden könnten. Der Source-Modifier ist ein Komponent-Swizzel und optional eine Negierung. Wie auch beim Dest-Register, ist bei keiner Angabe eines Source-Modifiers dies gleichzusetzen mit .xyzw. Gibt man nur .x an, so wird dies eigentlich in ein .xxxx umgesetzt. Das heißt, beim Lesen des Source-Register der y Komponente wird dann eigentlich der x Wert genommen und nicht eben y. Ein anderes Beispiel wäre .yzwx. Hier wird als x Wert in Wirklichkeit dann y genommen. Weiters können alle vier Komponenten negiert werden, indem man vor den Registernamen ein Minus setzt. mul r0, v0.yzwx, -s0.ywxz.
Was man noch benötigt um einen VS zu programmieren ist eine VS Deklaration. Sie wird verwendet um jeden Vertex Input Register die richtigen Werte zuzuweisen. Diese wird aber erst im nächsten Kapitel genauer erklärt.
Warum sollte man Vertex Shader verwenden?
Wie schon gesagt ersetzen die Vertex Shader die fixe TnL-Pipeline. Aber warum macht man dies? Der Grund ist, dass die TnL-Pipe nicht alle mögliche Vertex-Attribute unterstützt und daher manche Berechnungen noch mit dem Prozessor erledigt werden müssen. Die Vertex Shader können die Tnl-Pipe locker ersetzten und dabei noch viel großartigere Effekte erzielen. Und das alles mit Hardware-Unterstützung und nicht mehr unter der Verwendung des Prozessors. Da die Grafikhardware dies viel schneller berechnen kann, als ein Prozessor dies könnte, ist dies erstens ein riesen Zeitgewinn, zweitens wird der Prozessor nicht mehr mit Grafik-Schnick-Schnack belastet und hat noch mehr Zeit andere Dinge zu erledigen (KI, Physik,...). Noch dazu kann man unnötige Teile einfach nicht in den VS einbauen und sich so noch einen zusätzlichen Zeitvorsprung erarbeiten.
Beispiele für erzielbare Effekte
Hier nun einige Beispiele von Effekten die mit den VS möglich sind.
FisheyeLens Effect
Im unteren Bild sieht man, wie die Landschaft mit einer normalen TnL Pipeline darstellbar wäre. Im oberen Bild wurden alle Vertices normal transformiert und dann mit einer Deformation versehen. Dieser Effekt wäre mit einer Hardware TnL Pipeline eben nicht darstellbar.
Sine Wave Pertubation
Von diesem Bild sollte man die Animation sehen, da diese Fläche zeitabhängig deformiert wird. Dieser Effekt ist daher so interessant, da alles was man dem VS über einen Stream übergibt, eine x,y Position ist. Durch diese Position berrechnet der VS eine Vertex Model-Space Position, die Normale, den Eye-Direction Vektor und den Reflection Vektor
Tools
Was wäre die Welt ohne zusätzliche Programme.
nVidia Effects Browser
Mit Hilfe des Effects Browsers kann man einfach und schnell eigene Vertex/Pixel Shader Beispiele programmieren und testen. nVidia bietet für diesen schon genug Beispiele, bei denen man sich satt sehen kann. Jedoch unterstützen viele Grafikkarten nicht die benötigten Ressourcen. VS können ja noch, dank ihrer guten Software-Implementierung, emuliert werden. Bei Pixel Shader ist aber Schluß. Diese können einfach nicht durch eine Software-Implementierung ersetzt werden und daher auf Grafikkarten ohne Pixel Shader Unterstützung nicht betrachtet werden.
nVidia Shader Debugger
Mit Hilfe des Shader Debugger bekommt man einen Überblick über alle Register in einem Debugging-Fenster. Während man sich durch einen Shader durcharbeitet, veränderen sich auch die Daten des Debuggers interaktiv. Es gibt auch die Möglichkeit Haltepunkte zu setzen. Leider kann der Shader Debugger nur ab Windows 2000 mit Service Pack 1 gestartet werden.
Vertex Shader Assembler
Um einen Vertex Shader in eine Binary zu kompilieren, benötigt man einen Assembler. Insgesamt gibt es derzeit zwei Assembler. Einen von Microsoft, der dem DirectX 8.0 SDK (Microsoft Vertex Shader Assembler) beiliegt und einen von nVidia (nVidia Vertex and Pixel Shader Macro Assembler). In den nächsten Tutorials verwenden wir den Assembler von nVidia, da er mehr Funktionalität besitzt als sein Gegenstück von Microsoft. Möglicherweise widme ich beiden ein eigenes Tutorial (E-Mails *g*).
3D Studio MAX 4.x
Ab der Version 4.x können Vertex/Pixel Shader direkt während der Entwicklung von Modellen und Animationen erarbeitetet werden. 3D Studio stellt die Shader in WYSIWYG dar (What You See Is What You Get).
Dies war das erste Tutorial in hoffentlich ein langen Serie von Tutorials. *g* Ich freue mich auf jede E-Mail sei es ein Lob oder auch eine Beschwerde: SCHREIBT.
(c) by Kongo
Bei Fragen, Beschwerden oder Wünschen E-Mail an: kongo@gmx.at
Letztes Update: 03.02.2002