Tutorials - Vertex Shader & Pixel Shader Unterstützung in DirectX 9

Sprachenübersicht/Programmierung/C / C++/ C#/Spieleprogrammierung/DirectX

Vertex Shader & Pixel Shader Unterstützung in DirectX 9

Diese Seite wurde 32732 mal aufgerufen.

Dieser Artikel wurde in einem Wikiweb System geschrieben, das heißt, Sie können die Artikel jederzeit editieren, wenn Sie einen Fehler gefunden haben, oder etwas hinzufügen wollen.

Editieren Versionen Linkpartnerschaft Bottom Printversion

Keywords: C++, Vertex, Pixel, Tutorial, DirectX9, Spieleprogrammierung, OpenGL, Tutorials, Pixel, Hilfe, Unterstützung, Vertex Shader, Effekte, Pixel Shader

Inhaltsverzeichnis



Was sind Shader? Top



Shader sind Assembler Programme die direkt im Grafikprozessor der Grafikkarte ausgeführt werden. Unter bestimmten Umständen können diese aber auch per Software emuliert werden, was aber sehr langsam ist und vermieden werden sollte.

Man unterscheidet Vertexshader und Pixelshader.
Vertexshader arbeiten mit der Geometrie der zu rendernden Modelle, dass heißt sie können die Vertexdaten (Eckpunkte) verändern.
Pixelshader arbeiten mit den zu rendernden Pixeln selber zusammen, dass heißt sie können die Bildpunkte des gerenderten Objektes ändern.
Auch ist es möglich die Tiefenwerte zu manipulieren.

Was kann man mit Shadern machen ? Top



Höhere Effekte wie Normalmapping, Bloom, Softshadows, Bluring, CelShading und andere nur vorstellbare Effekte sind mit Shadern möglich.
Der Fantasie sind keine Grenzen gesetzt.

Warum sollte ich die Effekte in Vertex Shadern oder Pixel-Shader und nicht direkt in C++ schreiben ? Top



Der Grund dafür ist der direkte und unkomplizierte Zugriff auf die Renderdaten und die damit verbundenen Geschwindigkeitsgewinne.
Ausserdem ist es einfacher.

Was soll dieses Tutorial erklären ? Top



Dieses Tutorial soll erklären wie man die benötigte Unterstützung für Pixel Shader und Vertex Shader in DirectX 9 Programme einbaut, um die High Level Shader Language (HLSL) von Microsoft nutzen zu können. Es soll auch klären, wie ein Shader aufgebaut ist.

Welche Klassen und Variablen muss ich in meinem Programm anlegen und wie? Top



Als erstes brauchen wir folgende defines (falls es diese noch nicht gibt):

Vertex/Pixel Shader Code:

 
#define SAFE_RELEASE(p) {if(p) { (p)->Release(); (p)=NULL;}}
#define MAX_NAMELENGTH 255  



Dann wird eine Klasse erstellt, welche die Daten eines geladenen Shaders bereitstellt. Diese Klasse sollte später in einen STL-Vektor gespeichert werden, um Speicherplatz zu sparen, falls ein Shader mehrfach geladen werden sollte.

Code:


#include <vector>



Vertex Shader & Pixel Shader Code:

 
CSshader: 

class CShader
{
public:
      bool bActive;

      LPD3DXEFFECT pEffect;

      LPD3DXBUFFER pBufferErrors;

      char name[MAX_NAMELENGTH];

      CShader(char filename[MAX_NAMELENGTH], LPDIRECT3DDEVICE9 pd3dDevice)
      {
            pBufferErrors = NULL;
            bActive=true;
            strcpy(name,filename);
            HRESULT hr;
            hr = D3DXCreateEffectFromFile(pd3dDevice,filename,NULL,NULL,NULL,NULL,&pEffect,&pBufferErrors);

            if(FAILED(hr))
                  bActive=false;
      }

      ~CShader() 
      {
            SAFE_RELEASE(pBufferErrors);
            SAFE_RELEASE(pEffect);
      }
}; 



Erklärung:

Die Variable bActive gibt Auskunft darüber ob der Shader Fehlerfrei (true) benutzt werden kann oder nicht (false).

Die Variable pEffect stellt den Kern der Shaderklasse dar. Über diese Variable gehen die ganzen Informationen, die wir zum Rendern mit Shaden brauchen. Die Funktionsweise wird später etwas genauer erklärt.

Die Variable pBufferErrors enthält im Falle eines Fehlers im Shader die Art des Fehlers und die Position des Fehlers fest. Mit (char*)pBufferErrors->GetBufferPointer(); kann man diese Variable auswerten.

Die Variable name enthält den Ort und Dateinamen des Shaders.

In der Klasse eurer Objekte die Shader unterstützen sollen, muss dann nur noch folgende Struktur eingebaut werden:

Shader Klasse:

 
TShader:

typedef struct
{
      char cTechnology[MAX_NAMELENGTH];
      CShader *shader;
} TShader; 



Die Variable cTechnology enthält den Namen der beim Rendern zu nutzenden Technik. Diese Technik wird im Shader selbst angegeben. Sie stellt sozusagen einen Funktionskopf dar.

Die Variable shader enthält die Adresse zu einem Shader. Dieser Shader sollte sich in einem STL-Vektor befinden und seperat über einen RessourcenManager verwaltet werden.

Diese Struktur sollte nun in eure Objektklasse eingebaut werden (die Objektklasse/Struktur wird im Tutorial TObject genannt):

Code:


{
      ...
      TShader ShaderInfo;
      ...
}



Nun haben wir unsere benötigten Klassen und Strukture. Jetzt sollten wir diese Klassen und Strukturen auch verwenden:

In eureren RessourcenManager (oder global wenns jemand möchte) sollten nun folgende Zeilen geschrieben werden:

Code:

 
std::vector<CShader*> ShaderList; //der schon oft erwähnte STL-Vektor 

CShader*   CreateShader(char *filename); //Funktion zum laden eines Shaders

CShader* SetShader(TObject *obj,bool bActive,char *tech,char *filename=NULL); //Funktion zum setzen eines Shaders auf ein Objekt

bool SetUpShader(TObject *obj); //Funktion zum bereitstellen wichtiger Daten

CShader* GetShader(char name[MAX_NAMELENGTH]); //Funktion zum suchen eines bereits erstellten Shaders



Die Struktur TObject stellt dabei euer Objekt dar, welches den Shader zugewiesen bekommt zum Beispiel TObject *newBattleShip=new TObject();

In eurer CPP Datei (egal wo, Hauptsache die Datei kann eure wichtigen Funktionen bereitstellen) sollet nun folgendes eingetragen werden:

Code:


CShader* CEngine::GetShader(char name[MAX_NAMELENGTH])
{
      for(int i=0; i<RessourceManager->ShaderList.size(); i++)
            if(strcmp(RessourceManager->ShaderList->name,name)==0)
                  return RessourceManager->ShaderList[i];

      return NULL;
}



In dieser Funktion wird der STL-Vektor nach einem bereits erstellten Shader durchsucht.

Code:


CShader*  CEngine::CreateShader(char *filename)
{
      CShader *pShader=NULL;

      //suche ob es den Shader schon gibt
      if((pShader=GetShader(filename))!=NULL)
            return pShader;

      pShader=new CShader(filename,m_pd3dDevice);

      if(pShader && pShader->pBufferErrors)
            return NULL; //am besten noch eine Fehlermeldung ausgeben

      if(!pShader)
      {
            //am besten noch eine Fehlermeldung ausgeben
            return NULL;
      }

      RessourceManager->ShaderList.push_back(pShader);

      return pShader;
}



Diese Funktion prüft, ob der gewünschte Shader schon erstellt wurde ([i]GetShader). Wenn der Shader schon mal erstellt wurde, wird dieser zurückgegeben. Wenn er aber noch nicht erstellt wurde, wird versucht dieses nachzuholen. Hat alles geklappt, wird der neu erstellte Shader in die ShaderList vom RessourcenManager eingetragen, um zu zeigen, dass der Shader erstellt wurde.

Code:


CShader* CEngine::SetShader(TObject *obj,bool bActive,char *tech,char *filename)
{
      if(bActive)
      {
            obj->ShaderInfo.shader=NULL;
            obj->ShaderInfo.shader=CreateShader(filename);
            strcpy(obj->ShaderInfo.cTechnology,tech);
            return obj->ShaderInfo.shader;
      }
      else
      {
            obj->ShaderInfo.shader=NULL;
            return NULL;
      }     

      return NULL;
}



Diese Funktion aktiviert oder deaktiviert einen Shader für das Objekt obj. Zum aktivieren wird der gewünschte Shader gesucht und gegebenenfalls erstellt. Die Funktion setzt auch den Technologie Namen (den Technologie Namen kann man benutzen, um ein Fallbacksystem zu generieren, das dafür sorgt, dass auch Benutzer ohne Shader Unterstützung ein angenehmes Bild bekommen, es bleibt aber jedem selbst überlassen dies zu integrieren).

Die nächste Funktion sollte von jedem selbst erstellt werden, da Sie nur dazu dient, wichtige Shaderelemente die oft gebraucht werden zu definieren. Trotzdem mache ich einen Vorschlag, um zu zeigen wie dies funktioniert:

Shader setzen:


bool CEngine::SetUpShader(TObject *obj)
{
      //die braucht nur ausgeführt werden wenn ein Shader benutzt werden soll
      if(!obj->ShaderInfo.shader || !obj->ShaderInfo.shader->pEffect)
            return false;

      //Dies ist auch sehr wichtig da ansonsten der Shader möglicherweise nicht funktioniert
      obj->ShaderInfo.shader->pEffect->SetTechnique( obj->material.ShaderInfo.cTechnology );

      D3DXMATRIXA16 matViewProject;
      D3DXMatrixMultiply( &matViewProject, &camera->movement.matrix, &matProj ); 
      D3DXMATRIXA16 matViewInv;
      D3DXMatrixInverse( &matViewInv, NULL, &camera->movement.matrix );
      D3DXVECTOR4 vecPosition( matViewInv._41, matViewInv._42, matViewInv._43, 1.0f );
 
      //Hier wird die Transformationsmatrix des zu rendernden Objektes übergeben
      obj->ShaderInfo.shader->pEffect->SetMatrix( "matWorld", &obj->movement.matrix );

      //Und hier die der Kamera (man kann ja nie wissen wann man das braucht)
      obj->ShaderInfo.shader->pEffect->SetMatrix( "matView",  &camera->movement.matrix );

      //und so weiter
      obj->ShaderInfo.shader->pEffect->SetMatrix( "matViewProject", &matViewProject );
      obj->ShaderInfo.shader->pEffect->SetMatrix( "matProject", &matProj );
      obj->ShaderInfo.shader->pEffect->SetVector( "vecPosition", &vecPosition );
      obj->ShaderInfo.shader->pEffect->SetVector( "lightDir", &lightDir );

      for(int i=0; i<MAX_TEXTURESTAGES; i++)
      {
            char texture[MAX_NAMELENGTH];
            sprintf(texture,"texture%d",i);
            obj->ShaderInfo.shader->pEffect->SetTexture( texture,obj->texture);
      }

      return true;



In dieser Funktion werden (sollten) alle wichtigen Daten, die ein Shader brauchen könnte, direkt an [i]pEffect übergeben (oben wurde erwähnt, dass dies der Kern eines jeden Shader ist).

Etwas noch sehr wichtiges sind die Matritzen die übergeben werden, diese helfen ein Objekt korrekt darzustellen. Weitere Set Befehle kann man aus der DirectX Hilfe herauslesen.

Beim beenden des Programms sollte die ShaderList im RessourcenManager noch korrekt gelöscht werden.

Code:


for(int i=0; i<RessourceManager->ShaderList.size(); i++)
            delete ShaderList;

ShaderList.clear();



Damit wären die wichtigsten Elemente eingebaut.

Und wie benutze ich die Shader beim Rendern? Top



Beim Zeichnen eines Objektes müssen nur die folgenden Elemente genutzt werden:

Shader Rendern:


... //als erstes sollten alle Transformationen und andere wichtige Dinge gemacht werden die auch beim rendern ohne Shader gebraucht werden
 
//das hier sollte direkt vor dem eigentlichen Rendern (DrawIndexedPrimitive,...) stehen
//damit wird wenn irgendein Fehler auftritt bei zugewiesenem Shader der Shader deaktiviert
//das überprüfen auf Fehler geschieht in SetUpShader
 
if(!SetUpShader(obj))
{
      if(obj->ShaderInfo.shader)
      {
            delete obj->ShaderInfo.shader;
            obj->ShaderInfo.shader=NULL;
      }

      ... //hier sollte nun gerendert werden (DrawIndexedPrimitive,...)

      return;
}
                  
UINT uPasses;
obj->ShaderInfo.shader->pEffect->Begin( &uPasses, 0 );

for( UINT uPass = 0; uPass < uPasses; ++uPass )
{
      obj->ShaderInfo.shader->pEffect->Pass( uPass );
      ... //hier sollte nun gerendert werden (DrawIndexedPrimitive,...)
}

obj->ShaderInfo.shader->pEffect->End();




Das funktioniert noch nicht so richtig. Warum? Top



Es müssen erst noch einige *.lib und *.h Dateien eingebunden werden, wenn es noch nicht getan wurde. Wenn es immer noch Probleme gibt einfach fragen: FallenAngel84@web.de

Außerdem werden noch funktionierende Shader gebraucht.

Kann ich mal so einen simplen Shader sehen? Top



Hier, dieser Shader müsste wenn alles funktioniert das zu rendernde Objekt komplett rot färben (Technologiename: "Simple")

Vertex Shader & Pixel Shader:


SimpleShader.fx:
 

// transformations
float4x4 matWorld       : WORLD;
float4x4 matView        : VIEW;
float4x4 matProject     : PROJECTION;
float4 roteFarbe    = { 1.0, 0.0, 0.0, 1.0 };

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
    float4 Diff : COLOR0;
};
 
VS_OUTPUT VS(float3 Pos  : POSITION)
{
    VS_OUTPUT Out = (VS_OUTPUT)0; 

    float4x4 WorldView = mul(matWorld, matView); 

    float3 P = mul(float4(Pos, 1), (float4x3)WorldView);  // position (view space) 

    Out.Pos  = mul(float4(P,1), matProject);             // position (projected)

    Out.Diff = roteFarbe; 

    return Out;



technique Simple
{
    pass P0
    {
        VertexShader = compile vs_1_1 VS();
    }



Wie man hier sehen kann, muss man eine etwas umständliche Rechnung nutzen, um die Vertices der Welt auf die Vertices des Bildschirms umzurechnen. Deshalb wurde auch in der Funktion SetUpShader einige Matrizen übergeben. Diese Funktion sollte daher unbedingt angepasst werden, da ich nicht wissen kann, wo und wie die Matrizen bei euch gespeichert wurden.

Was muss man noch beachten ? Top



Wenn man Shader benutzt, wird die Lichtberechnung von DirectX nicht genutzt, da muss man sich also selber raushelfen, indem man eine Lichtberechnung in den Shader einbaut. Und wie schon mal erwähnt wurde, muss die Berechnung der endgültigen Positionskoordinaten auch übernommen werden (wird im Shaderbeispiel gezeigt).

In den Funktionen wo die Objekte erstellt werden oder nach dem erstellen, sollte die Funktion [i]SetShader genutzt werden, um Shader für dieses Objekt nutzen zu können.

Man sollte auch überprüfen, ob Vertex Shader und/oder Pixel Shader auch von der eingebauten Grafikkarte unterstützt werden. Deshalb sollte man Alternativen bereithalten.

Fragen, Kritik und andere Dinge an FallenAngel84@web.de![code]

Gibt es noch irgendwelche Fragen, oder wollen Sie über den Artikel diskutieren?

Editieren Versionen Linkpartnerschaft Top Printversion

Haben Sie einen Fehler gefunden? Dann klicken Sie doch auf Editieren, und beheben den Fehler, keine Angst, Sie können nichts zerstören, das Tutorial kann wiederhergestellt werden

Sprachenübersicht/Programmierung/C / C++/ C#/Spieleprogrammierung/DirectX/Vertex Shader & Pixel Shader Unterstützung in DirectX 9