DirectX 8 und DelphiLektion 1: Direct3D Initialisierungvon Jürgen Rathlev |
Grundlage für die nachfolgende Einführung in DirectX8 mit Delphi bilden die
Tutorials von 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: |
procedure TSample3DForm.FormClose(Sender: TObject; var Action: TCloseAction); begin // Lösche die D3D Scene bevor wir D3D beenden D3DKillScene; // Lösche D3D D3DShutdown; 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.
|