Home » Tutorials » Grafik und Spiele » Direct3D mit Delphi unter DirectX 8

Direct3D mit Delphi unter DirectX 8

Direct3D Initialisierung

Grundlage für die nachfolgende Einführung in DirectX8 mit Delphi bilden die Tutorials von http://www.snorre-dev.com. Die dort beschriebenen Beispiele wurden von mir nach Delphi 5 umgesetzt. Sie sollten aber auch mit anderen Delphi-Versionen funktionieren. Nützlich ist es außerdem, sich die DirectX-SDK von Microsoft herunterzuladen. Neben einigen Beispielen (in C++ und VB) enthält dieses Paket eine umfangreiche Dokumentation zu DirectX. Einige der darin enthaltenen Beispiele habe ich nach Delphi umgesetzt.
Um DirectX unter Delphi einzusetzen, benötigt man entsprechende Interface-Units. Zum Glück haben sich die Leute des Jedi Projects die Arbeit gemacht, diese zu erstellen und allen Interessierten zur Verfügung zu stellen. Man kann sich die Units und die erforderliche DLL dort bei Jedi Graphix herunterladen. Da leider mehrere leicht unterschiedliche Versionen im Umlauf sind, empfehle ich die auf dieser Seite bereitgestellte Version zu verwenden. Sie wurde mit den nachfolgenden Beispielen zusammen getestet.
Wie wir in den nachfolgenden Beispielen sehen, bringt meiner Meinung nach der Einsatz von Delphi gerade für den Einsteiger einige Vorteile, da er sich hier im Gegensatz zu VisualC++ nicht mit der am Anfang etwas unübersichtlichen Verwaltung von Fenster-Handles und Botschaftsroutinen herumschlagen muss. Ein wenig Erfahrung bei der Programmentwicklung unter Delphi ist allerdings schon erforderlich.
Fangen wir also an! Als erstes erzeugen wir eine neue Delphianwendung. Der Form geben wir den Namen „Sample3DForm“. Der uses-Zeile fügen wir die benötigten DirectX-Units hinzu:

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 Direct3D8, d3dx8;

In der Deklaration von TSample3DForm benötigen wir unter private einige Variablen und Methoden:

type
 TSample3DForm = class(TForm)
 ...   // hier werden von Delphi Komponeneten und Ereignisse eingetragen
 private
 // Das Direct3D Interface, es wird zum Initialisieren und Schließen von D3D benötigt
 lpd3d            : IDIRECT3D8;

 // Das D3DDevice wird zum Rendern benutzt und spiegelt den Bildschirm mit allen
 // Funktionen wieder. Wenn wir das D3DRender Interface erstellen, verändern oder
 // das Bild rendern, so machen wir das über dieses Interface
 lpd3ddevice      : IDirect3DDevice8;

 red,green,blue   : byte;   // Hintergrundfarbe

 procedure FatalError(hr : HResult; FehlerMsg : string);
 function D3DFind16BitMode : TD3DFORMAT;
 procedure D3DInit;
 procedure D3DShutdown;
 procedure D3DInitScene;
 procedure D3DKillScene;
 procedure D3DRender;
 end;

Bevor wir die D3D-Methoden mit Leben füllen, müssen wir uns um die Initialisierung und Deinitialisierung unserer Objekte kümmern. Durch Doppelklick auf OnCreate im Objektinspektor erzeugen wir den Rumpf für die Initialisierung und fügen nachfolgende Zeilen ein:

// Initialisieren aller Methoden und Variablen
procedure TSample3DForm.FormCreate(Sender: TObject);
begin
 lpd3d:=nil;
 lpd3ddevice:=nil;
 red:=0; green:=0; blue:=0;
end;

Entsprechend verfahren wir für die Deinitialisierung mit OnClose:

// Initialisieren aller Methoden und Variablen
procedure TSample3DForm.FormCreate(Sender: TObject);
begin
 lpd3d:=nil;
 lpd3ddevice:=nil;
 red:=0; green:=0; blue:=0;
end;

Die Initialisierung unserer 3D-Objekte wird im OnShow-Ereignis vorgenommen:

procedure TSample3DForm.FormShow(Sender: TObject);
begin
 D3DInit;     // Initialisieren von D3D
 D3DInitScene;
 D3DRender;   // Zeichne unsere Grafiken
end;

Damit das Programm auf Tastatureingaben (ESC für Programmende) reagiert, fügen wir noch ein OnKeyDown-Ereignis ein:

procedure TSample3DForm.FormKeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
begin
 if Key=VK_ESCAPE then close;
end;

Jetzt können wir daran gehen, die 3D-Umgebung zu initialisieren. Mit der Funktion Direct3DCreate8 wird ein Direct3D-Interfaceobjekt erzeugt. Alle DirectX Strukturen werden immer mit ZeroMemory(…) überschrieben, um unangenehme Nebeneffekte durch ältere Aufrufe zu vermeiden.
Im Record d3dpp werden alle wichtigen Parameter, die zur Erstellung des Devices nötig sind, gesetzt. Um ein geeinetes Backbufferformat zu ermiiteln, wird die weiter unten beschriebene Funktion D3DFind16BitMode aufgerufen.
Anschließend erstellen wir mit CreateDevice endgültig unsere DirectX Schnittstelle. Der Parameter D3DCREATE_SOFTWARE_VERTEXPROCESSING ist für ältere Karten bestimmt, die noch keine Harwareunterstützung für Vertexprocessing haben. Das lässt sich mit D3DCREATE_HARDWARE_VERTEXPROCESSING auch abstellen:

// Mit dieser Funktion initialisieren wir D3D
procedure TSample3DForm.D3DInit;
var
 hr    : HRESULT;
 d3dpp : TD3DPRESENTPARAMETERS;
begin
 //Erstelle Direct3D! Muß immer als erstes erstellt werden
 //Immer D3D_SDK_VERSION als Version setzen
 lpd3d:=Direct3DCreate8(D3D_SDK_VERSION);
 if(lpd3d=nil) then FatalError(0,'Fehler beim Erstellen von Direct3D!');

 // Setze D3DPRESENT_PARAMETERS auf 0, sonst könnten wir probleme mit älteren
 // Eintragungen bzw. unkontrollierbaren Ergebnissen bekommen!
 // Sollten wir bei allen DirectX Strukturen machen
 ZeroMemory(@d3dpp, SizeOf(d3dpp));

 with d3dpp do begin
 // Hiermit werden alte Frames gelöscht, denn wir brauchen sie nicht
 SwapEffect:=D3DSWAPEFFECT_DISCARD;
 hDeviceWindow:=Handle;      // Dies ist unser HWND von TForm
 BackBufferCount:=1;         // 1 Backbuffer

 Windowed          := FALSE;
 BackBufferWidth   := 640;
 BackBufferHeight  := 480;
 BackBufferFormat  := D3DFind16BitMode;
 end;

 //Nachdem wir die D3DPRESENT_PARAMETERS Struktur ausgefüllt haben, sind wir
 // endlich so weit unser D3D Device zu erstellen
 hr:=lpd3d.CreateDevice(D3DADAPTER_DEFAULT,
 D3DDEVTYPE_HAL,
 Handle,
 D3DCREATE_SOFTWARE_VERTEXPROCESSING,
 d3dpp,
 lpd3ddevice);
 if FAILED(hr) then FatalError(hr,'Fehler beim Erzeugen des 3D-Device');
end;

Beim Programmende müssen alle Ressourcen auch wieder freigegeben werden:

// *** D3DShutdown hier werden die Resourcen von D3D wieder freigegeben
procedure TSample3DForm.D3DShutdown;
begin
 if assigned(lpd3ddevice) then lpd3ddevice:=nil;
 if assigned(lpd3d) then lpd3d:=nil;
end;

Die nachfolgende beiden Routinen werden erst in der nächsten Lektion mit Leben erfüllt und bleiben hier zunächst leer:

procedure TSample3DForm.D3DInitScene;
begin
end;

procedure TSample3DForm.D3DKillScene;
begin
end;

Folgende kleine Routine ist für die Fehlerbehandlung zuständig. Sie gibt eine Fehlermeldung auf dem Bildschirm aus und beendet dann das Programm:

// Fataler Fehler. Meldung und Programmende
procedure TSample3DForm.FatalError(hr: HResult; FehlerMsg: string);
var
 s: string;
begin
 if hr<>0 then s := D3DXErrorString(hr) + #13 + FehlerMsg
 else s := FehlerMsg;
 D3DKillScene;
 D3DShutdown;
 MessageDlg(s, mtError, [mbOK], 0);
 close;
end;

Jetzt müssen wir noch prüfen, ob unsere Grafikhardware überhaupt den von uns gewünschten Grafikmodus unterstützt. Mit dem Parameter D3DADAPTER_DEFAULT wählen wir die primäre Grafikkarte aus. Mit D3DDEVTYPE_HAL stellen wir ein, dass wir die Hardwarebeschleunigung (hardware application layer) einsetzen wollen. Die nächsten zwei Parameter beschreiben den Farbmodus des Bildschirms und des Backbuffers. Hier wählen wir den 16-bit Modus (rot 5 Bits, grün 6 oder 5 Bits, blau 5 Bits und ohne oder mit 1 Bit Alphakanal). Man kann auch mit anderen Farbmodi experimentieren (siehe dazu die DirectX-SDK):

function TSample3DForm.D3DFind16BitMode: TD3DFORMAT;
var
 hr: HRESULT;
begin
 hr := lpd3d.CheckDeviceType (D3DADAPTER_DEFAULT,
 D3DDEVTYPE_HAL,
 D3DFMT_R5G6B5,      // Format Primary
 D3DFMT_R5G6B5,      // Format Buffer
 false);
 if (SUCCEEDED(hr)) then begin
 result := D3DFMT_R5G6B5;
 exit;
 end;

 hr := lpd3d.CheckDeviceType (D3DADAPTER_DEFAULT,
 D3DDEVTYPE_HAL,
 D3DFMT_X1R5G5B5,
 D3DFMT_X1R5G5B5,
 false);
 if (SUCCEEDED(hr)) then begin
 result := D3DFMT_X1R5G5B5;
 exit;
 end;
 FatalError(0, 'Der erforderliche D3D Modus wird von Ihrer Karte nicht unterstützt');
 result := 0;
end;

Jetzt können wir endlich beginnen etwas auf den Bildschirm zu schreiben. Die Render-Routine wird in den nachfolgenden Lektionen noch ergänzt. Hier erzeugen wir zunächst nur einen schwarzen Bildschirm.
Die nachfolgenden Routinen sind alle Methoden von lpd3ddevice (IDirect3DDevice8). Clear wird benutzt, um den Buffer zu löschen. Mit BeginScene starten wir den eigentlichen Code, in dem das Bild aufgebaut wird (siehe nächste Lektion). Mit EndScene schließen wir den Bildaufbau ab. Zum Schluß bringen wir den Backbuffer mit Present auf den Bildschirm.

procedure TSample3DForm.D3DRender;
begin
 if assigned(lpd3ddevice) then with lpd3ddevice do begin
 Clear(0,           // Wieviel Rechtecke löschen? 0 Löscht alle
 nil,         //Pointer zu den Rechtecken. NULL = Ganzer Bildschirm
 D3DCLEAR_TARGET,
 D3DCOLOR_XRGB(red,green,blue), //Hintergrundfarbe
 1,           // Lösche ZBuffer ( Wir haben momentan noch keinen )
 0 );

 if SUCCEEDED(BeginScene) then begin
 // Hier wird später alles stehen, was gerendert werden soll

 EndScene;
 end;

 // Zeige Resultate auf dem Bildschirm
 Present(nil, nil, 0, nil);
 end;
end;

Die Quelltexte der Beispiele stehen zum Download zur Verfügung. Die Zip-Datei enthält alle Lektionen. Zum Ausführen einer der Lektionen muss in den Projekt-Optionen von Delphi als Bedingung einer der Werte Lesson1, Lesson2, … definiert werden.