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

Direct3D mit Delphi unter DirectX 8

Darstellung und Bewegung von 3D-Objekten

In dieser Lektion wollen wir echte 3D-Objekte (Pyramide und Würfel) sich bewegen lassen. Zunächst müssen wir die Eckpunkte der Dreiecke, aus denen sich unsere Objekte zusammensetzen, definieren. Wir verwenden den gleichen Vertextyp wie in der vorigen Lektion, und es gibt ebenfalls wieder eine gemeinsame Vertexliste für beide Objekte (4 Dreiecke für die Seitenflächen der Pyramide, 2 Dreiecke für den Boden und 6 mal 2 Dreiecke für den Würfel):

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..45] of TMyVertex;

const
// Beschreibung des Vertextyps: 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 = (
    // Pyramide
    (x: 0.0; y: 1.0; z: 0.0; color: $00FF0000 ), // Oben Mitte
    (x: 1.0; y:-1.0; z:-1.0; color: $0000FF00 ), // Rechts vorn
    (x:-1.0; y:-1.0; z:-1.0; color: $000000FF ), // Links vorn
    (x:-1.0; y:-1.0; z: 1.0; color: $0000FF00 ), // Hinten links
    (x: 1.0; y:-1.0; z: 1.0; color: $000000FF ), // Hinten rechts
    // Und noch mal rechts vorn ( benötigt zum schließen )
    (x: 1.0; y:-1.0; z:-1.0; color: $0000FF00 ),

    (x:-1.0; y:-1.0; z:-1.0; color: $000000FF ), // Boden der Pyramide
    (x: 1.0; y:-1.0; z:-1.0; color: $0000FF00 ),
    (x:-1.0; y:-1.0; z: 1.0; color: $0000FF00 ),
    (x: 1.0; y:-1.0; z: 1.0; color: $000000FF ),

  // Würfel
    (x:-1.0; y:-1.0; z:-1.0; color: $0000FF00 ),  // Vorn
    (x:-1.0; y: 1.0; z:-1.0; color: $000000FF ),
    (x: 1.0; y: 1.0; z:-1.0; color: $000000FF ),
    (x: 1.0; y: 1.0; z:-1.0; color: $000000FF ),
    (x: 1.0; y:-1.0; z:-1.0; color: $000000FF ),
    (x:-1.0; y:-1.0; z:-1.0; color: $0000FF00 ),

    (x: 1.0; y:-1.0; z: 1.0; color: $0000FF00 ),  // Hinten
    (x: 1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x:-1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x:-1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x:-1.0; y:-1.0; z: 1.0; color: $0000FF00 ),
    (x: 1.0; y:-1.0; z: 1.0; color: $0000FF00 ),

    (x:-1.0; y: 1.0; z:-1.0; color: $00FF0000 ),  // Oben
    (x:-1.0; y: 1.0; z: 1.0; color: $00FF0000 ),
    (x: 1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x: 1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x: 1.0; y: 1.0; z:-1.0; color: $00FF0000 ),
    (x:-1.0; y: 1.0; z:-1.0; color: $00FF0000 ),

    (x: 1.0; y:-1.0; z:-1.0; color: $00FFFF00 ),  // Unten
    (x: 1.0; y:-1.0; z: 1.0; color: $00FFFF00 ),
    (x:-1.0; y:-1.0; z: 1.0; color: $00FFFF00 ),
    (x:-1.0; y:-1.0; z: 1.0; color: $00FFFF00 ),
    (x:-1.0; y:-1.0; z:-1.0; color: $0000FF00 ),
    (x: 1.0; y:-1.0; z:-1.0; color: $00FFFF00 ),

    (x:-1.0; y:-1.0; z: 1.0; color: $00FF00FF ),  // Links
    (x:-1.0; y: 1.0; z: 1.0; color: $00FF00FF ),
    (x:-1.0; y: 1.0; z:-1.0; color: $00FF00FF ),
    (x:-1.0; y: 1.0; z:-1.0; color: $00FF00FF ),
    (x:-1.0; y:-1.0; z:-1.0; color: $0000FF00 ),
    (x:-1.0; y:-1.0; z: 1.0; color: $00FF00FF ),

    (x: 1.0; y:-1.0; z:-1.0; color: $0000FFFF ),  // Rechts
    (x: 1.0; y: 1.0; z:-1.0; color: $0000FFFF ),
    (x: 1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x: 1.0; y: 1.0; z: 1.0; color: $0000FF00 ),
    (x: 1.0; y:-1.0; z: 1.0; color: $0000FFFF ),
    (x: 1.0; y:-1.0; z:-1.0; color: $0000FFFF ));

In der Initalisierung der 3D-Umgebung sind nur kleine Ergänzungen nötig, um den Z-Buffer (Tiefenbuffer) einzuschalten. Die Routine ist deshalb (ebenso wie die nachfolgenden) hier nur in den Ausschnitten wiedergegeben, an denen etwas geändert werden muss. Der herunterladbare Quelltext enthält die vollständigen Beispiel.

// Mit dieser Funktion initialisieren wir D3D
procedure TSample3DForm.D3DInit;
  ...
  with d3dpp do begin
    ...
    // Wir brauche einen Z-Buffer, also schalten wir ihn ein
    EnableAutoDepthStencil := TRUE;
    AutoDepthStencilFormat := D3DFMT_D16;
    ...
  end;
  ...

Bei den Einstellungen für das Rendern ist entsprechend der Z-Buffer einzuschaklten (D3DRS_ZENABLE).
Mit dem Parameter D3DRS_CULLMODE kann man ein wenig experimentieren. Bislang hatten wir D3DCULL_NONE verwendet, was Vorder- und Rückseite der Dreiecke sichtbar macht. In diesem Beispiel ist es besser, den Wert auf D3DCULL_CCW zu setzen, um richtige Körper mit Außenflächen dargestellt zu bekommen. Entscheidend dafür, was Außen- oder Innenseite eines Dreiecks ist, ist die Reihenfolge der Eckpunkte (Vertizes) der Dreiecke im Vertexbuffer. Bei D3DCULL_CCW sind die Innen(oder Rück)seiten im Gegenuhrzeigersinn (CCW = counter clockwise) zu lesen. Setzen wir diesen Wert auf D3DCULL_CW (CW = clockwise), so wären die Innenseiten der Körper sichtbar.
Die Position des Betrachters wird etwas verändert (y = 4), um von einer erhöhten Position auf die beiden Körper blicken zu können.

procedure TSample3DForm.D3DInitScene;
  ...
  if assigned(lpd3ddevice) then with lpd3ddevice do begin
    ...
    SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
    SetRenderState(D3DRS_LIGHTING, 0);
    SetRenderState(D3DRS_ZENABLE, 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 und um 4 Einheiten nach oben.
    D3DXMatrixLookAtLH(ViewMatrix, D3DXVECTOR3(0.0, 4.0, -6.0),
                                   D3DXVECTOR3(0.0, 0.0, 0.0),
                                   D3DXVECTOR3(0.0, 1.0, 0.0));
    ...

Die Render-Rotuine muss gegenüber Lektion 3 etwas geändert werden, da wir andere Objekte darstellen wollen. Die Pyramide wird in zwei Schritten gezeichnet: Zuerst die 4 Seitenflächen, dann der Boden. Die Seitenflächer sind im Vertexbuffer in Fächerform (D3DPT_TRIANGLEFAN) abgelegt, d.h. der erst Punkt ist allen Dreicken gemeinsam (Spitze der Pyramide), bei den weiteren Punkten wird reihum jeweils um eins weitergezählt (siehe auch DirectX-SDK unter „D3DPRIMITIVETYPE“). Beim Boden werden die Punkte im Vertexbuffer als Streifen interpretiert (D3DPT_TRIANGLESTRIP). Wichtig ist es, darauf zu achten, dass bei jedem DrawPrimitive immer der richtige Index zum ersten Vertex und die zum Objekt zugehörige Anzhl von Dreiecken (Primitives) angegeben wird.

procedure TSample3DForm.D3DRender;
  ...
  if assigned(lpd3ddevice) then with lpd3ddevice do begin
    ...
    if SUCCEEDED(BeginScene) then begin
      ...
      // Die Rotation um alle Achsen.
      D3DXMatrixRotationYawPitchRoll(rot_matrix, rot_triangle_Y,
        rot_triangle_X, rot_triangle_Z);
      // Verschiebe die Pyramide nach links
      D3DXMatrixTranslation(trans_matrix, -1.5, 0.0, 0.0);
      // Kombiniere das Teil mit der Welt
      D3DXMatrixMultiply(matWorld, rot_matrix, trans_matrix);
      SetTransform(D3DTS_WORLD, matWorld);

      // Nun zeichnen wir unsere Pyramide
      DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 4);
      // Den Boden
      DrawPrimitive(D3DPT_TRIANGLESTRIP, 6, 2);

      // Rotation um die Y Achse
      D3DXMatrixRotationY(rot_matrix, rot_square);
      D3DXMatrixTranslation(trans_matrix, 1.7, 0.0, 0.0);
      D3DXMatrixMultiply(matWorld, rot_matrix, trans_matrix);
      SetTransform(D3DTS_WORLD, matWorld );

      // Und der Würfel
      DrawPrimitive(D3DPT_TRIANGLELIST, 10, 12);
      ...

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.