DirectX 8 und DelphiLektion 3: Die ersten bewegten Objektevon Jürgen Rathlev |
Die in der Lektion 2 erzeugten Objekte (Dreieck und Quadrat) sollen jetzt in Bewegung
versetzt werden. Sie sollen sich jeweils um eine vertikale Achse drehen. Dazu müssen
wir die ganze Szene in eine echte perspektivische Projektion umsetzen. Zuerst wollen wir die Objekte in etwas geänderter Form definieren: |
type // Unsere Struktur, in der wir die Dreiecke speichern TMyVertex = record x,y,z : single; // Position des Vertex color : dword; // Farbe des Vertex end; TMyVertices = array [0..6] of TMyVertex; |
Die Koordinaten der Eckpunkte werden hier in nicht transformierter Form angegeben. Die Transformation erfolgt erst später beim Rendern der Szene. Die Konstante D3D8T_CUSTOMVERTEX wird daher auf einen anderen Wert gesetzt (D3DFVF_XYZ für die Koordinaten x,y,z und D3DFVF_DIFFUSE für die Farbinformation). Außerdem wollen wir alle Objekte in einem Vertexbuffer unterbringen. Die ersten drei Vertizes gehören zum Dreieck, die restlichen vier zum Quadrat. Daher müssen wir hier auch nur eine Variable für den Vertexbuffer MyVB definieren. Die Initialisierung in FormCreate wird wie in Lektion 2 beschrieben vorgenomme. |
const // Mit D3DFVF_DIFFUSE sagen wir DX, das unsere Struktur eine Farbe hat. // D3DFVF_XYZ bedeutet, das es sich um ein untransformiertes Vertex handelt D3D8T_CUSTOMVERTEX = D3DFVF_XYZ or D3DFVF_DIFFUSE; MyVertices : TMyVertices = ( (x : 0.0; y : 1.0; z : 0.0; color : $FF0000FF ), // x, y, z, color (x : 1.0; y : -1.0; z : 0.0; color : $0000FF00 ), (x : -1.0; y : -1.0; z : 0.0; color : $00FF0000 ), (x : -1.0; y : -1.0; z : 0.0; color : $FF0000FF ), (x : -1.0; y : 1.0; z : 0.0; color : $0000FF00 ), (x : 1.0; y : -1.0; z : 0.0; color : $00FF0000 ), (x : 1.0; y : 1.0; z : 0.0; color : $00FFFFFF )); type TSample3DForm = class(TForm) ... private ... // Buffer, der unsere Vertizes enthält MyVB : IDirect3DVertexBuffer8; ... end; |
Bei der Initialisierung der Szene muss also auch nur ein Vertexbuffer erzeugt werden.
Für das spätere Rendern sind hier allerdings einige weitere Einstellungen vorzunehmen:
D3DRS_CULLMODE legt fest, wie die Rückseiten der Dreicke ermittelt und angezeigt werden.
Mit D3DCULL_NONE sind beide Seiten sichtbar (In der Lektion 4 werden auch andere Möglichkeiten
erläutert). D3DRS_LIGHTING schaltet eine Beleuchtung der Dreiecksflächen ein oder aus. Da
wir noch keine Lichtquelle verwenden, setzen wir diese Option sicherheitshalber auf Aus
(Sie würde hier sowieso nicht funktionieren, da unsere Vertizes nicht mit Normalenvektoren
versehen sind - dazu mehr in einer späteren Lektion). Außerdem müssen wir für die später erforderliche Koordinatentransformation der perspektivischen Ansicht die Grundeinstellungen vornehmen. Dazu sind die Koordinaten der Kamera (Ort des Auges des Betrachters - x = 0, y = 0, z = -6) und eines Zielpunktes (Mitte unserer darzustellenden Objekte - x = 0, y = 0 z = 0), sowie eine Angabe darüber erforderlich, was in unserer Welt oben ist (hier die pos. Y-Achse - x = 0, y = 1, z = 0). Mit D3DXMatrixLookAtLH wird die zugehörige Viewmatrix erstellt. Die erforderliche Projektionsmatrix erzeugen wir mit D3DXMatrixPerspectiveFovLH. Hier sind anzugeben: Der Winkel des Sichtfeldes in rad, das Seitenverhältnis, sowie die Minimal- und Maximalentfernung für den sichtbaren Bereich. |
procedure TSample3DForm.D3DInitScene; var hr : HRESULT; vbVertices : pByte; ViewMatrix, matProj : TD3DXMATRIX; begin if assigned(lpd3ddevice) then with lpd3ddevice do begin // Hier wird der Vertex Buffer erstellt, der groß genug ist um alle Vertizes zu enthalten. hr:=CreateVertexBuffer (sizeof(TMyVertices), D3DUSAGE_WRITEONLY, // Nur Schreibzugriffe D3D8T_CUSTOMVERTEX, // Unser Vertex D3DPOOL_MANAGED, MyVB); // Pointer zu unserem Buffer if FAILED(hr) then FatalError(0,'Fehler beim Erstellen des Vertex Buffers'); // Nun kopieren wir unsere Vertizes in den Buffer // Wir müssen es zuvor mit Lock festhalten, um es bearbeiten zu können with MyVB 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 Vertex-Buffers'); // Hier wird der Vertexbuffer kopiert. Move(MyVertices,vbVertices^,SizeOf(TMyVertices)); // Und wieder loslassen Unlock; end; // Einstellungen für die Dreiecksrückseiten und die Beleuchtung SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE); SetRenderState(D3DRS_LIGHTING,0); // Hier erstellen wir unsere SichtMatrix. Denkt einfach es ist // eure Kamera, von der aus wir sehen. Als erstes setzen wir die Kamera // um 6 Einheiten zurück auf der Z-Achse. D3DXMatrixLookAtLH (ViewMatrix,D3DXVECTOR3(0.0,0.0,-6.0), D3DXVECTOR3(0.0,0.0,0.0), D3DXVECTOR3(0.0,1.0,0.0)); // Da sich unsere Kamera nicht bewegt legen wir sie einfach fest SetTransform(D3DTS_VIEW,ViewMatrix); D3DXMatrixPerspectiveFovLH(matProj, // Resultierende Matrix D3DX_PI/4, // Sichtwinkel 640/480, // Seitenverhätnis 1.0, // Mindeste Nähe 100.0); // Maximal sichtbare Entfernung // Unsere Projektion wird sich niemals bewegen, also setzen wir sie fest SetTransform(D3DTS_PROJECTION,matProj ); end; end; |
Jetzt müssen wir noch unsere Render-Routine erweitern. Da unsere beiden Objekte
rotieren sollen, werden bei jedem Aufruf der Routine die zu den Drehachsen gehörenden
Winkel erhöht. Der nachfolgende Teil entspricht weitgehend dem Beispiel aus Lektion 2.
Vor dem Zeichnen der Objekte müssen allerdings noch die erforderliche Koordinatentransformationen
vorgenommen werden. Wir erzeugen uns jeweils eine Rotationsmatrix um die gewünschten
(sich ändernden) Winkel mit D3DXMatrixRotationYawPitchRoll oder D3DXMatrixRotationY
und multiplizieren sie mit einer Translationsmatrix D3DXMatrixTranslation, um Drehen
und Verschieben zu kombinieren. Die so erzeugte Matrix wird als Weltmatrix für die im
Hintergrund ablaufende Koordinatentransformation beim Zeichnen verwendet. Da beide Objekte in einem Vertexbuffer abgelegt sind, müssen wir beim Zeichnen mit DrawPrimitive den Index des ersten Vertex des jeweiligen Objekts entsprechend anpassen (0 für das Dreieck, 3 für das Quadrat). |
procedure TSample3DForm.D3DRender; var matWorld, rot_matrix, //Our rotation matrix trans_matrix : TD3DXMATRIX; //Our translation matrix begin rot_triangle_X:=rot_triangle_X+0.03; rot_triangle_Y:=rot_triangle_Y+0.02; rot_triangle_Z:=rot_triangle_Z+0.01; rot_square:=rot_square+0.01; if assigned(lpd3ddevice) then 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(0,0,0), //Hintergrundfarbe schwarz 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,MyVB,sizeof(TMyVertex)); // Die Rotation um alle Achsen. D3DXMatrixRotationYawPitchRoll(rot_matrix,rot_triangle_Y,rot_triangle_X,rot_triangle_Z); // Verschiebe das Dreieck um 1.2 nach links D3DXMatrixTranslation(trans_matrix,-1.2,0.0,0.0); // Kombiniere das Teil mit der Welt D3DXMatrixMultiply(matWorld,rot_matrix,trans_matrix); SetTransform(D3DTS_WORLD,matWorld ); // Hier zeichnen wir 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); // Rotation um die Y Achse D3DXMatrixRotationY(rot_matrix,rot_square); D3DXMatrixTranslation(trans_matrix,1.2,0.0,0.0); D3DXMatrixMultiply(matWorld,rot_matrix,trans_matrix); SetTransform(D3DTS_WORLD,matWorld ); // Jetzt zeichnen wir das Viereck. Man beachte, das der Offset // nun auf 3 steht! DrawPrimitive(D3DPT_TRIANGLESTRIP,3,2); EndScene; end; // Zeige Resultate auf dem Bildschirm Present(nil,nil,0,nil); end; end; |
Als letztes müssen wir nun noch Bewegung in unser Programm bringen. Dazu können wir
z. B. ein Timer-Objekt aus den Delphi-Komponeneten in unser Projekt einbauen und
im OnTimer-Ereignis die Render-Routine aufrufen. Eine schnellere Bewegung lässt sich erreichen, wenn unser Programm im Exklusivmodus läuft. Dazu müssen wir für das Application.OnIdle-Ereignis eine kleine Routine schreiben. OnIdle wird vom System immer ausgeführt, wenn auf irgendwelche Eingaben (z. B. von der Tastatur) gewartet wird. Wir fügen in der Deklaration von TSample3DForm unter private folgende Zeilen ein: |
... Animate : boolean; ... procedure MyIdleHandler (Sender: TObject; var Done: Boolean); ... |
Im OnCreate-Ereignis erfolgt die Zuweisung unserer Routine an OnIdle: |
... Animate:=true; Application.OnIdle:= MyIdleHandler; ... |
Mit der Boolean-Variablen Animate können wird unsere Animation über die Tastatur (Leertaste) stoppen und wieder starten. Wenn wir die Variable Done in MyIdleHandler unverändert (true) lassen, übergibt das System nach Verlassen der Routine die Steuerung an den Message-Handler, um andere Anwendungen zum Zuge kommen zu lassen. Wird dieser Wert auf false gesetzt, wird dies nicht gemacht, so dass unser Programm im Exklusivmodus läuft. |
procedure TSample3DForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key=VK_ESCAPE then close; if Key=VK_SPACE then Animate:=not Animate; end; procedure TSample3DForm.MyIdleHandler (Sender: TObject; var Done: Boolean); begin if Animate then D3DRender; Done:=false; 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.
|