Pixel- und Vertex-Shader

Programmierbare Shader geben der Spielegrafik eine neue Dimension. Sie ermöglichen es, Transformation, Beleuchtung und das Rendern während der Laufzeit auf Vertex- und Pixel-Ebene zu beeinflussen. Ein Shader ist ein kleines Programm, das für jeden Vertex und jeden, auf dem Bildschirm gerenderden Pixel ausgeführt wird. Es kann Position, Farbe und Normale eines Vertex oder die Texturkoordinaten ebenso steuern wie Pxelfarbe und Helligkeit. Dies geschieht in Abhängigkeit vom Einfluss des Lichtes, der Texturen oder willkürlicher lite-C-Variblen. Dadurch erhält der Entwickler ein neues Maß an Flexibilität über die Weise in welcher Pixel gerendert werden. Mit Vertex- und Pixel-Schader lassen sich realistische Kräuselungen auf Wasser erstellen, dem Spiel ein Cartoon-Look geben, Modelle mit Fell überziehen oder der Lavafluss eines Vulkans steuern (Screenshots von GameStudio-Usern auf dem Gamestudio Shader-Forum):

 
Normalmapping-Shader
Fell-Shader

Shader gibt es in zwei Varianten: Vertex-Shader beeinflussen Mesh-Positionenvertices, Pixel-Shader beeinflussen Texturpixel. Der Shadercode wird in den Speicher der Grafikkarte geladen und direkt in die Grafikverbindung eingespeist. Shadercode ist in Assembly, heutzutage wird allerdings eine höhere Sprache vom Typ `C´, nämlich HLSL (auch bekannt unter Cg) verwendet und dann auf Assembly herunterkompiliert, was das Programmieren von Shadern erheblich vereinfacht. HLSL verwendet dieselbe Syntax wie lite-C. Pixel- und Vertexshader können unter Verwendung der Schlüsselworte VertexShader und PixelShader als Teil des Material-Effektskriptes definiert werden. Um das Shader-Programmieren zu lernen, fangen Sie am besten mit dem Shader Workshop auf der Gamestudio Download-Seite an. Hier beschränken wir uns auf einen schnellen Überblick.

Beispiel eines Vertex-Shaders:

float4x4 matWorldViewProj : WORLDVIEWPROJ;
float4 vecTime;


void vs_flicker_red(
   in float4 iPos : POSITION,
   in float2 iTex0 : TEXCOORD0,
   in float2 iTex1 : TEXCOORD1,
   out float4 oPos : POSITION,
   out float4 oDiffuse: COLOR0,
   out float2 oTex0 : TEXCOORD0,
   out float2 oTex1 : TEXCOORD1)
   {
     oPos = mul(iPos,matWorldViewProj);
     oTex0 = iTex0;
     oTex1 = iTex1;
     oDiffuse.r = fmod(vecTime.w * 0.1,1.0);
     oDiffuse.g = 0.0;
     oDiffuse.b = 0.0;
     oDiffuse.a = 1.0;
   }


technique flicker
{
   pass p0
   {
     VertexShader = compile vs_1_1 vs_flicker_red();

   }
}

Shader-Programmierung ist nicht gerade einfach und erfordert einige Kenntnisse in Vektor- und Matrix-Algebra. Sobald ein Shader aber sauber programmiert ist, kann ihn jeder benutzen, ohne seine interne Funktionsweise zu kennen. Eine gute Einführung ins Schreiben von Shadern für Gamestudio findet sich am Ende der lite-C-Workshops. Die DirectX-Dokumentation von Microsoft enthält eine komplette Beschreibung der Shadersprache. Es gibt einige Bücher übers Shader-Programmieren. Ein Beispiel für das Schreiben wiederverwendbarer Shader finden Sin in der Shaderbibliothek mtlFX.c shader library im Ordner code.

Empfohlene Tutorials und Bücher:

Die mtlFX.c Shaderbiliothek

Eine Bibliothek von Standard Oberflächen- und Postprocessing-Shadern ist in der Datei code\mtlFX.c definiert und kann in jedwedes Projekt aufgenommen werden. Details hierzu finden Sie unter Shader-Bibliothek.

Shader selbst schreiben

Shader sind ein Sonderfall von Material-Effekten. Um Effekte zu erstellen, sollten Sie sich mit den DirectX-Texture-Stages auskennen. Deswegen wird die Entwicklung von Effekten nur fortgeschrittenen Programmierern empfohlen, auch wenn es Spass macht, an den verschiedenen Render-Stages und Texture-Operationen herumzubastlen und zu sehen, was dies auf dem Bildschirm für Änderungen produziert. Ist ein Effekt allerdings einmal anständig entwickelt worden, kann er von jedem verwendet werden. Im Folgenden finden sie einen kurzen Überblick über die Elemente eines Effektes in der Gamestudio-Engine. Bitte beachten Sie, dass dieser Überblick bei weitem nicht ausreicht, um Effekte vollständig zu verstehen - es ist erforderlich, die Microsoft DirectX9 Dokumentation zu lesen.

Effekte sind in drei Hauptbereiche unterteilt:

Variablenangaben (variable declarations) - optionale Werte, die vor dem Rendern gesetzt und dann in dem betreffenden Effekt verwendet werden; beispielsweise Texturen, die Matrix der Welt oder Beleuchtungsparameter.
Techniken und Durchläufe - sie definieren wie etwas gerendert wird und beziehen Statusinformation sowie Vertex- und Shader-Angaben mit ein.
Funktionen - optionaler in Shader-Assembler oder HLSL geschriebener Shader-Code.

Variablen

Variablen werden am Beginn der Effektdatei definiert. Sie müssen einen Typ und einen unverwechselbaren Namen angeben. Dann können Sie konkrete Werte, die von der Art der speziellen Verwendung, anwenderdefinierten Daten und den Eingangswerten abhängen, vorgeben. Beispiel:
float4 diffuse : DIFFUSE = { 0, 0, 0, 1 };

float4 ist der Typ (ein treibender Point-Vektor mit 4 Komponenten), diffuse ist der Name. Nach dem Doppelpunkt steht eine Marke zum Bestimmen der Anwendung. In diesem Fall wird sie als Streufarbe (diffuse) benutzt. Schliesslich wird der Wert auf 0,0,0,1 (rot, grün, blau, alpha) initialisiert.

float4x4 matWorld : WORLD;
float4x4 matView : VIEW;
float4x4 matProj : PROJECTION;

Dies beschreibt drei Matrizen namens matWorld, matView und matProj, die während des Effekts für die Welt, die View- und Projektions-Matrix verwendet werden wird. Diese Matrizen sind bereits von der Engine vordefiniert und werden automatisch auf die Welt-, View- und Projektionsmatrizen des zu rendernden Objekts gesetzt.

A7.6 Bestimmte Variablen können unter Verwendung des gemeinsamen Typenmodifikators von den Effekten gemeinsam benutzt werden. Die Engine stellt einen Effekten-Pool für alle gemeinsamen effekt-Variablen zur Verfügung.

Spezielle Variablennamen

Variablen können irgendeinen Namen haben, einige Variablennamen sind jedoch für den Zugriff auf die Engine, Material- oder Entity-Parameter vordefiniert. Eine Liste sämtlicher vordefinierter Variablennamen finden Sie im Kapitel Effect Variablen.

A7.6 Wenn der Variablenname einer Textur die Buchstabenfolge "_bmap" beinhaltet, ist er von eimem BMAP-Objekt im Voraus gesetzt. In lite-C ist dieses Objekt mit dem Namen definiert, der der "_bmap"-Buchstabenfolge vorangeht. Auf diese Weise kann ein Shader eine beliebigen Anzahl von Texturen verwenden. Beispiel:

// lite-C
BMAP* testimage = "image.tga";
...

// Shader HLSL
texture testimage_bmap; // preset from "image.tga"
...

Techniques

Ein Effekt enthält eine oder mehrere Techniken. Jede Technik besteht aus einem oder mehreren Durchläufen (passes). Jeder Pass besteht aus einer Einstellung einer oder mehrerer Textur-Phasen-Register der 3D Hardware und optional, einem Pixel- oder Vertex-Shader, der das Verhalten der Phase neu definiert. Das Objekt wird einmal pro Pass mit den gegeben Einstellungen gerendet.

Funktioniert eine bestimmte Technik nicht, da die angegeben Phasenzustände der Textur von der 3D Hardware nicht unterstützt werden, wird automatisch zur nächste Technik des Effekts gesprungen. Deswegen sollte jeder Effekt eine einfache Fallback-Technik enthalten, um auch alte Hardware zu unterstützen. Sie wollen zum Beispiel einen Teich mit sich realistisch kräuselndem Wasser, das Licht reflektiert erstellen: Sie beginnen Sie mit der ersten Technik, die in einem einzigen Durchlauf das Wasser rendert, Glanzlichter sowei kaustische Texturen hinzufügt und Licht auf das Wasser wirft. Kann Ihre hardware diese Technik nicht in einem einzigen Durchlauf rendern, könnte eine zweite Technik das Wasser rendern, Glanzlichter oder kaustische Texturen hinzufügen, jedoch kein Licht auf das Wasser bringen.

Eine Technik wird mit dem Schlüsselwort technique gefolgt von einem optionalen, ureigenen Namen definiert; beispielsweise:

technique BumpMapping

Spzielle Technique-Namen

Techniken können willkürliche Namen haben, manche Namen allerdings haben eine spezielle Bedeutung für die Engine.

Durchgänge

Innerhalb der Technik können einer oder mehrere Durchläufe (pass) definiert werden, die die aktuellen Zustandszuweisungen enthalten. Jeder Durchgang ist ein wirklicher Renderprozess. Ein Durchgang wird mit dem Schlüsselwort pass, dem ein optionaler eigenständiger Name folgt, definiert, z. B.:
pass one

Durchgänge werden in der Reihenfolge, in der sie in der Technik erscheinen ausgeführt. Innerhalb eines Durchgangs lassen sich Zustände auf Werte oder Ausdrücke setzen. Sie werden auf sehr ähnliche Weise wie im Skript gesetzt; beispielsweise:

ColorOp[0] = SelectArg1;

LightEnable[0] = true; // turn on light 0
ZEnable = true; // turn on depth tests

Sie finden alle verfügbaren Zustände und Optionen in der Dokumentation zu DirectX. Ein Beispiel zweier Techniken, in dem die zweite zwei Durchgänge hat, könnte also so aussehen:

technique FirstTechnique
{
   pass P0
   {
   // Set states here for pass 0
   }
}

technique SecondTechnique
{
   pass P0
   {
   // Set states here for pass 0
   }

   pass P1
   {
   // Set states here for pass 1
   }
}

Beispiel einer einfachen Texturierungs-Technik:

texture entSkin1; // Variable declaration

technique textureTechnique
{
   pass p0
   {
     Texture[0] = <entSkin1>;
   
     MinFilter[0] = Linear;
     MagFilter[0] = Linear;
     MipFilter[0] = Linear;
   
     ColorOp[0] = Modulate;
     ColorArg1[0] = Texture;
     ColorArg2[0] = Diffuse;
     AlphaOp[0] = SelectArg2;
     AlphaArg1[0] = Texture;
     AlphaArg2[0] = Diffuse;
   }
}

Die Textur wird dem Zustand 0 zugewiesen, dann wird das Filtern auf bilinear gesetzt. Die Textur wird dann durch Setzen der Farboperatoren mit der diffusen Farbe abgestimmt.

Spezielle Pass-Namen

Passes (Durchgänge) können mit Ausnahme der folgenden Ausnahme beliebige Namen haben: A7.6: Enthält der erste Durchgangsname die Zeichenfolge "_repeat", wird der Durchgang für jede Skin des Modells wiederholt. Nach jedem Durchgang nehmen die entSkin1..entSkin3 zugewiesenen Skins um 1 zu. Hat das Modell also 5 Skins, wird der Durchgang 5 mal wiederholt, wobei entSkin1 auf die erste, zweite, dritte, vierte und fünfte Skin in Folge gesetzt wird. Diese spezielle Methode lässt sich zum Rendern einer willkürlichen Anzahl von Skins verwenden, beispielsweise für multitexturiertes Terrain mit 4 oder mehr Texturen. Beispiel:
texture entSkin1; // tiled terrain texture with mask on alpha 

//////////////////////////////////////////////////////////////////////

technique terraintex
{
  pass multitex_repeat11
  {
    AlphaBlendEnable = True;
    SrcBlend = SrcAlpha;
    DestBlend = InvSrcAlpha;
		
    Texture[0] = <entSkin1>;
    TexCoordIndex[0] = 0; // base texture coordinates for alpha 
    AlphaOp[0] = SelectArg1;
    AlphaArg1[0] = Texture;
		
    Texture[1] = <entSkin1>;
    TexCoordIndex[1] = 1; // detail texture coordinates for RGB
    ColorArg1[1] = Texture; 
    ColorArg2[1]= Diffuse;
    ColorOp[1] = Modulate2x;
    AlphaArg1[1] = Current;
    AlphaOp[1] = SelectArg1;

    ColorOp[2] = Disable;
    AlphaOp[2] = Disable;
  }
}

Folgen auf "_repeat" zwei Zahlen wie im obigen Beispiel, ist die erste Zahl die (1..4) Skin zum Starten der Wiederholung und die zweite Zahl (1..4) ist die Skinzunahme pro Durchgang. Beispiel: hat ein Modell 6 Skins, wird ein Durchgang, der auf "_repeat32" endet zwei mal wiederholt. Das erste mal mit der dritten Skin auf entSkin3 und die vierte auf entSkin4, das zweite mal mit der fünften Skin auf entSkin3 und der sechsten auf entSkin4. Die erste und zweite Skin bleiben während beider Durchgänge entSkin1 und entSkin2 zugewiesen. Auf diese Weise können gewöhnliche Skins wie Lightmaps oder Normalen-Maps für alle Durchgänge benutzt werden. Die Nummer der gegenwärtigen Durchgangswiederholung lässt sich durch die Shader-Variable iRepeat auswerten.

Die obigen Beispiele haben keinerlei Shader-Code enthalten - es sind sogenannte Fixed Function Pipeline (FFP) -Effekte. Für detaillierte Informationen über das Schreiben von Shadern legen wir Ihnen ganz besonders die Shader-Workshops ans Herz.

Die default.fx-Shader-Funktionsbibliothek

Die gebräuchlichsten Shader-Funktionen finden sich in der Datei code\default.fx und lassen sich einfach in jedweden verwendeten Shader einfügen. Benutzen Sie diese Funktionen für Ihre Shader! Dadurch wird der Shader-Code nicht nur um einiges kürzer, es werden auch Fehler vermieden und Anpassungen ermöglicht, die sich automatisch auf alle Shader auswirken. Nähere Informationen finden Sie unter Shader includes. Beispiele für das Verwenden von default.fx finden sich in allen Shadern der mtlFX.c-Shaderbibliothek.

Vertex Format

Das ist das, was die Engine an den Vertex-Shader übergibt. Das A7-Vertex-Strukt ist in atypes.h definiert. Sämtliche Geometrie- und Modell-Vertices verwenden drei UV-Koordinatensätze für Textur, lightmap/detailmap und Shadertangenten im folgenden Format:
typedef struct {
   float x,y,z;   // position
float nx,ny,nz; // normal float tu1,tv1; // coordinate set 1, for base texture float tu2,tv2; // coordinate set 2, for light map or detail map float tx3,ty3,tz3,tw3; // coordinate set 3, for tangent or other purposes (A7.06 and above only) } D3DVERTEX; #define D3DFVF_D3DVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX3|D3DFVF_TEXCOORDSIZE2(0)|D3DFVF_TEXCOORDSIZE2(1)|D3DFVF_TEXCOORDSIZE4(2))

 !!  Shader-Koordinaten sind im DirectX-Koordinatenformat mit horizontaler z-Achse und vertikaler y-Achse. Engine- und DirectX-Koordinaten können durch Austauschen der x- und y-Werte konvertiert werden.

Bitte beachten Sie, daß die Koordinate tw3, welche die Ausrichtung der Binormalen enthält, nur in A7.06  und höher zur Verfügung steht. A6 bis A7.4 hatten beim dritten UV-Koordinaten-Set nur 3 Werte. Zur korrekten Handhabung multiplizieren Sie die Binormale mit tangent.w.

Edition

 C   P 

Siehe auch:

MATERIAL, Effect, shader library, shader variables

► Aktuelle Version Online