Tutorials
Hier finden sie Tutorials zum Thema Spieleentwicklung.
Converting Ms3d-animations to DirectX | Datum: 12.5.2010 | Autor: Christoph P. | Schwierigkeitsgrad: Mittel |
Einführung in DirectX Shader | Datum: 15.5.2010 | Autor: Christoph P. | Schwierigkeitsgrad: Einfach |
Dual-Paraboloid-Shadow-Mapping | Datum: 23.5.2010 | Autor: Christoph P. | Schwierigkeitsgrad: Mittel |
Einführung in Milkshape 3D | Datum: 17.6.2010 | Autor: Christoph P. | Schwierigkeitsgrad: Einfach |
Einführung in DirectX Shader
Vorwort
In diesem Tutorial wird ein Einblick in, mit HLSL geschriebene Shader, wie sie in DirectX Verwendung finden, gegeben. Das Einbinden der Shader in ein DirectX Programm ist nicht Teil des Tutorials und sollte bei Bedarf in der Hilfe des DirectX SDK nachgelesen werden. Außerdem werden nur die zwei meist verwendeten Shader, nämlich Vertex-Shader und Pixel-Shader, anhand eines übergeordneten Beispiels erklärt. Es soll das Basiskonzept von DirectX Shader erläutert werden.
Allgemein
Ein Shader ist ein meist kleines Programm, das vom Grafikprozessor ausgeführt wird und deshalb sehr
schnell ist. In DirectX gibt es Shader seit Version 8.0, vorerst aber nur Vertex- und Pixel-Shader,
die in Assembler geschrieben wurden. In DirectX 9 kam dann die High Level Shader Language (HLSL) dazu,
die das Programmieren in einer C-ähnlichen Sprache ermöglichte. In DirectX 10 wurde der Geometry-Shader
eingeführt und DirectX 11 erweiterte die Palette dann zusätzlich noch um den Domain-, Hull- und Compute-Shader.
Seit DirectX 10 stehen Shader im Mittelpunkt von DirectX, da die sogenannte fixe Rendering-Pipeline abgeschafft
wurde. Das heißt, wer ein ansprechendes Ergebnis mithilfe von DirectX haben möchte, der kommt nicht darum herum
sie zu verwenden. Aber ehrlich gesagt, die wenigsten wollen ohne sie auskommen, den mit ihnen kann man
wunderschöne Oberflächeneffekte zaubern, aber auch Animationen realisieren.
Vertex-Shader
Der Vertex-Shader wird einmal pro Vertex aufgerufen, er bekommt einen Vertex als Eingabe, verarbeitet ihn und liefert ihn dann an die DirectX-Pipeline zurück. Der Vertex-Shader ist der erste Shader in der Pipeline und bekommt deshalb die Daten vom Input-Assembler. Hauptsächlich werden die Vertizes im Vertex-Shader transformiert und beleuchtet, aber auch andere Berechnungen für Spezialeffekte sind möglich.
Hier das Gerüst eines einfachen Vertex-Shaders:
Wie ihr seht, sind Shader, Funktionen.
Die Vertexdaten können wie in C in eine Struktur gepackt werden. Allerdings erhalten sie hier Datenzuordnungsbezeichner, sogenannte Semantics, damit DirectX weiß, welchen Variablen, welche Daten zugeordnet werden sollen. Das mit SV beginnende Semantic, ist ein System-Value-Semantic, das für eine Systemdaten-Deklaration, also für die DirectX-Pipeline, vorgesehen ist. Die Ein- und Ausgabedaten können bei einem Vertex-Shader, sowie bei allen Shadern, verschieden sein. In unserem Beispiel werden die Position, Normale und Texturkoordinate an den Shader übergeben und nach der Bearbeitung im Shader werden zusätzlich zu den transformierten Daten (siehe Beispiel unten), die Vertexposition in Weltkoordinaten zurückgegeben. HLSL verwendet eigene Datentypen, ihr könnt diese in der DirectX-Referenz des DirectX SDK nachlesen, sowie auch die im Beispiel verwendeten Funktionen (Intrinsic Functions).
Nun ein komplettes Beispiel eines Vertex-Shaders:
Variablen müssen an einem Shader in Ressource-Objekten übergeben werden. Es gibt mehrere Typen dieser Objekte, die wichtigsten sind: cBuffer, tBuffer, Texture2D und Texture2DArray. Hier wird in einem cBuffer (Constant Buffer) die Welt- und die kombinierte Kamera-Projektions-Matrix übergeben. Mit der Weltmatrix wird im Vertex-Shader dann die Position und die Normale jedes Vertex transformiert. Mit der kombinierten Matrix wird die Position nochmal transformiert, um die Position im Projektionsraum zu erhalten. Der Zweck der Normale und der Position in Weltkoordinaten wird dann erst beim Sehen des Pixel-Shaders klar. Die Daten werden dann zusammen mit den kopierten Texturkoordinaten zurückgegeben. Wie diese Daten dann weiter verarbeitet werden können, sehen wir im nächsten Abschnitt.
Pixel-Shader
Der Pixel-Shader wird einmal pro Pixel aufgerufen, er bekommt die Daten von der davor liegenden Pipeline-Station,
bzw. vom davor liegenden Shader (in unserem Fall der Vertex-Shader). Er berechnet damit die Pixelfarbe (auch Pixeltiefe
oder beliebige Werte sind möglich) und gibt sie an die Pipeline zurück. Der Pixel-Shader ist der letzte Shader
in der Pipeline und gibt die Daten deshalb bereits an den Output-Merger, der die letztendliche Pixelfarbe festlegt.
Pixel-Shader werden hauptsächlich für die Darstellung von Oberflächen verwendet, aber sowie alle Shader, können
auch sie für spezielle Berechnungen herangezogen werden. Die wohl wichtigsten zwei Verwendungsmöglichkeiten aber
sind, Texturierung und Beleuchtung, zu denen ich nach der folgenden Darstellung des Gerüsts eines Pixel-Shaders
ein Beispiel zeigen werde.
Gerüst eines Pixel-Shaders:
Es gibt prinzipiell betrachtet, keinen wirklichen Unterschied zum Vertex-Shader. Nur die Ein- und Ausgabedaten sind andere.
Texturierung und Beleuchtung mit einem Pixel-Shader:
Hier wird zusätzlich noch ein Buffer für die Lichtposition in Weltkoordinaten angelegt und es wird eine Textur und ein
Sampler verwendet. Der Sampler ist notwendig um die Texturfarbe an den entsprechenden Texturkoordinaten auszulesen (samplen).
Die vom Vertex-Shader erhaltenen Eingabedaten für den Pixel-Shader werden von DirectX interpoliert.
Das bedeutet, es werden für das aktuelle Pixel, aus den drei Vertizes, des aktuell zu zeichnenden Dreiecks,
distanz- und modusabhängige "Mittelwerte" berechnet. (Interpolationsmodi können übrigens per Variablenmodifizierer bestimmt werden)
Die Ausgabe unseres Pixel-Shaders ist die berechnete Pixelfarbe. Dafür extra eine Struktur anzulegen, wie bei den anderen
Ein- und Ausgabedaten können wir uns sparen, nicht jedoch das SV_Target-Semantic, das für DirectX die Farbausgabe kennzeichnet.
Nun aber zum Code im Pixel-Shader. Wie schon erwähnt wird zuerst, mit den übergebenen Texturkoordinaten, die Farbe aus
der Textur gelesen. Dann wird der Lichtstrahl von der Lichtposition zum, vom Pixel-Shader repräsentierten Punkt am Dreieck,
(interpolierte Vertexposition in Weltkoordinaten) berechnet und normalisiert, um dann letztendlich die Leuchtstärke über das
Lambert'sche Kosinusgesetz zu berechnen. (Dieses Gesetz besagt, umso flacher der Winkel des reflektierenden Lichtstrahls,
desto geringer die Strahlungsstärke) Die Farbe braucht dann nur mehr mit diesem Wert multipliziert und zurückgegeben werden.
Voila.
Ich hoffe ihr habt nun einen Einblick in die Welt der Shader bekommen und seit wieder etwas schlauer geworden. Das komplette Beispiel gibt es hier zum Downloaden.