DirectX 8 und DelphiLektion 2: Zeichnen einfacher Objektevon Jürgen Rathlev |
Wir wollen jetzt auf der in der Lektion 1 erzeugten Zeichenfläche ein Dreieck und ein Quadrat anzeigen. Die Angaben zu den Eckpunkten aller Objekte werden in DirectX als Vertizes gespeichert. Welche Information in einem Vertex enthalten sein sollen, ist in weiten Grenzen frei wählbar (siehe dazu die DirectX-SDK). Für uns genügen die Angaben zu den Koordinaten und den Farbwerten der Eckpunkte. Außerdem definieren wir zwei Arrays, die die Vertizes unserer beiden Objekte enthalten sollen. |
type TMyVertex = record x,y,z,rhw : single; // Position des Vertex color : dword; // Farbe des Vertex end; TTriangleVertex = array [0..2] of TMyVertex; TQuadratVertex = array [0..3] of TMyVertex; |
Die Koordinaten sollen hier in transformierter Form (d.h. in Pixeln) angegeben werden.
Entsprechend wird die Konstante D3D8T_CUSTOMVERTEX gesetzt (D3DFVF_XYZRHW für
die Koordinaten x,y,z,rwh und D3DFVF_DIFFUSE für die Farbinformation). Wir benötigen
sie weiter unten in CreateVertexBuffer, um das Vertexformat festzulegen.
In den späteren Beispielen werden wir stattdessen untransformierte Koordinaten verwenden,
die erst durch Festlegung eines Sichtpunktes und einer Projektion in Bildschirmkoordinaten
von DirectX umgerechnet werden. Die Größe RHW (reciprocal of homogeneous W) ist in der DirectX-SDK unter den Stichworten "Transformed and Lit Vertices", "What Are Depth Buffers?" und "Eye-Relative vs. Z-Based Depth" näher beschrieben. Sie wird nur bei transformierten Koordinaten benötigt und entspricht einer auf den Ort des Auges bezogenen Tiefeninformation ähnlich der Information im z-Buffer. Wir setzen sie hier einfach auf 1. Bei der Angabe der Raumkoordinaten ist zu beachten, dass anders als in der Mathematik üblich als Koordinatensystem kein Rechtssystem (x nach rechts, y nach oben z nach vorn) sondern ein Linkssystem (x nach rechts, y nach oben, z nach hinten) verwendet wird. DirectX benutzt als Elementarfläche immer das Dreieck, so dass das Quadrat durch zwei Dreiecke dargestellt werden muss. Außerdem müssen in der Deklaration von TSample3DForm unter private zwei Variablen für die Vertexbuffer definiert werden. |
const // Beschreibung des Vertextyps: Mit D3DFVF_DIFFUSE sagen wir DX, das unsere // Struktur eine Farbe hat. D3DFVF_XYZRHW bedeutet, dass es sich um ein transformiertes // Vertex handelt D3D8T_CUSTOMVERTEX =D3DFVF_XYZRHW or D3DFVF_DIFFUSE; // Dreieck TriangleVertex : TTriangleVertex = ( (x : 175.0; y : 50.0; z : 0.5; rhw : 1.0; color : $000000FF), // x, y, z, rhw, Farbe (x : 300.0; y : 300.0; z : 0.5; rhw : 1.0; color : $FFFF0000), (x : 50.0; y : 300.0; z : 0.5; rhw : 1.0; color : $00FFFFFF)); // Quadrat // Leider werden keine Vierecke von D3D unterstützt so wie in OpenGL, also müssen wir // doch alles aus Vertices erstellen QuadratVertex : TQuadratVertex = ( (x : 350.0; y : 300.0; z : 0.5; rhw : 1.0; color : $000000FF), // x, y, z, rhw, Farbe (x : 350.0; y : 50.0; z : 0.5; rhw : 1.0; color : $00FF0000), (x : 590.0; y : 300.0; z : 0.5; rhw : 1.0; color : $0000FFFF), // x, y, z, rhw, Farbe (x : 590.0; y : 50.0; z : 0.5; rhw : 1.0; color : $0000FF00)); type TSample3DForm = class(TForm) ... private ... // Die Buffer, die unsere Vertizes enthalten dxtriangle,dxsquare : IDirect3DVertexBuffer8; ... end; |
Unter FormCreate werden sie initialisiert: |
// Initialisieren aller Variablen procedure TSample3DForm.FormCreate(Sender: TObject); begin lpd3d:=nil; lpd3ddevice:=nil; dxtriangle:=nil; dxsquare:=nil; red:=0; green:=0; blue:=0; end; |
Wir wollen für jedes der beiden Objekte einen eigenen Vertex-Buffer verwenden.
Die Bereitstellung der Buffer erfolgt in der Routine D3DInitScene durch Aufruf
von CreateVertexBuffer.
Wir setzen ihn auf D3DUSAGE_WRITEONLY, weil wir in den Vertexbuffer nur schreiben wollen.
Als nächstes, geben wir unser Vertexformat (siehe oben) an.
Mit dem D3DPOOL geben wir an, wie bzw. wo wir unser Vertex ablegen wollen. Wir legen
fest, dass DirectX den Speicher der Grafikkarte automatisch verwalten soll (D3DPOOL_MANAGED).
Die letzte Variable ist ein Pointer vom Typ Byte-Array. Dieser Pointer zeigt auf den
von DirectX bereitgestellten Vertexbuffer. Zum Kopieren eines Vertex-Arrays in diesen Buffer muss dieser zuerst für andere Zugriffe gesperrt werden (Lock). Das Kopieren geschieht mit der Delphi-Prozedur Move. Anschließend wird der Buffer wieder freigegeben (Unlock). |
procedure TSample3DForm.D3DInitScene; var hr : HRESULT; vbVertices : pByte; begin if assigned(lpd3ddevice) then with lpd3ddevice do begin // Hier wird der Vertex Buffer für das Dreieck erstellt hr:=CreateVertexBuffer (sizeof(TTriangleVertex), D3DUSAGE_WRITEONLY, // Nur Schreibzugriffe D3D8T_CUSTOMVERTEX, // Unser Vertex D3DPOOL_MANAGED, dxtriangle); // Pointer zu unserem Dreieck if FAILED(hr) then FatalError(0,'Fehler beim Erstellen des Vertex Buffers für unser Dreieck'); // Hier wird der Vertex Buffer für das Quadrat erstellt hr:=CreateVertexBuffer (sizeof(TQuadratVertex), D3DUSAGE_WRITEONLY, D3D8T_CUSTOMVERTEX, D3DPOOL_MANAGED, dxsquare); if FAILED(hr) then FatalError(0,'Fehler beim Erstellen des Vertex Buffers für unser Viereck'); // Nun kopieren wir unsere Vertizes in den Buffer // Wir müssen es zuvor mit Lock festhalten, um es bearbeiten zu können with dxtriangle do begin hr:=Lock(0, // Offset, an dem wir beginnen 0, // Größe des locks ( 0 für alles ) vbVertices, // Wenn erfolgreich dann hier ablegen 0); // sonstige Flags if FAILED(hr) then FatalError(0,'Fehler beim Locken des Buffers für die Dreiecke'); // Hier wird der Vertexbuffer kopiert. Move(TriangleVertex,vbVertices^,SizeOf(TTriangleVertex)); // Und wieder loslassen Unlock; end; // Dasselbe Spiel mit dem Viereck with dxsquare do begin hr:=Lock(0,0,vbVertices,0); if FAILED(hr) then FatalError(0,'Fehler beim Locken des Buffers für die Quadrate'); Move(QuadratVertex,vbVertices^,SizeOf(TQuadratVertex)); Unlock; end; end; end; |
Bei Beendigung des Programm müssen die Vertexbuffer wieder freigegeben werden: |
procedure TSample3DForm.D3DKillScene; begin dxtriangle:=nil; dxsquare:=nil; end; |
Das Zeichnen der beiden Objekte erfolgt in der Routine D3DRender (siehe auch
Lektion 1). Mit SetVertexShader wird DirectX mitgeteilt, welches Vertexformat
wir benutzen (siehe oben). Anschießend wird der Stream 0 als Quelle für die nachfolgende
Zeichenoperation DrawPrimitive festgelegt. Er zeigt auf den Vertexbuffer des
Dreiecks. Für das Quadrat geschieht dies entsprechend. In DrawPrimitive
wird als erster Parameter ein Wert (D3DPT_TRIANGLELIST) übergeben, der festlegt,
wie aus der Liste der Eckpunkte die Dreiecke zu erzeugen sind (siehe DirectX-SDK unter
"D3DPRIMITIVETYPE"). D3DPT_TRIANGLELIST : unabhängige Liste von Vertextripeln D3DPT_TRIANGLESTRIP : mit einer Seite in Form eines Streifens aneinanderhängende Dreiecke D3DPT_TRIANGLEFAN : der erste Punkt ist allen Dreiecken gemeinsam (fächerartige Struktur - siehe Lektion 4) Der zweite Parameter ist die Indexnummer des Vertex, mit dem begonnen werden soll, der dritte gibt die Azahl der zu zeichnenden "Primitiven" (Dreiecke) an. |
procedure TSample3DForm.D3DRender; begin with lpd3ddevice do begin Clear(0, // Wieviel Rechtecke löschen? 0 Löscht alle nil, // Pointer zu den Rechtecken. nil = 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 // Vertex Shader sind wirklich komplex, aber es lassen sich damit gute Effekte // erzielen. Genauere Beschreibungen in der SDK, denn alles hier niederschreiben // sprengt den Rahmen eines Tutorials SetVertexShader(D3D8T_CUSTOMVERTEX); // Die D3D Renderfunktionen lesen aus Streams. Hier sagen wir DX welchen Stream // es verwenden soll SetStreamSource(0,dxtriangle,sizeof(TMyVertex)); // Hier zeichnen wir nun endlich unser Dreieck. Wir übergeben DirectX, was wir // zeichen wollen, 0 für den Index des 1. Vertex, 1 für die Anzahl der Vertizes DrawPrimitive(D3DPT_TRIANGLELIST,0,1); // Jetzt setzen wir den Stream auf unser Quadrat SetStreamSource(0,dxsquare,sizeof(TMyVertex)); // Hier werden die 2 Dreiecke gezeichnet DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2); 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.
|