Die Community zu .NET und Classic VB.
Menü

Einführung in DirectGraphics

 von 

Einführung  

Willkommen zum ersten Tutorial über DirectX8-Grafiken. Es gibt drei wahrscheinliche Gründe, warum Sie hier sind: Sie sind komplett neu in der Welt von VB und DirectX; Sie kennen DirectX7 und wollen nun mit DirectX8 arbeiten; Sie kennen DirectX7, haben sich schon das DirectX8 SDK angeschaut und sind nun total verwirrt. Egal wie, Sie sind hier...

Die DirectX8-Implementierung ist ziemlich anders, als das was Sie kennen, wenn Sie an DirectX7 gewöhnt sind. Die Hauptsache ist, dass es kein DirectDraw mehr gibt. In DirectX7 war es möglich 2D-Grafiken zu erstellen, indem man Direct3D benutzte(und den Hardwarebeschleuniger). Es war nicht einfach, aber es war möglich. In DirectX8 ist das der einzige Weg 2D-Grafiken zu erstellen. Aber stören Sie sich nicht daran, wenn Sie komplett neu in DirectX sind.

DirectX8 bietet viele sehr nützliche Funktionen, viele sind aber extrem kompliziert, und werden wahrscheinlich nur von Profis benutzt. Sachen wie "mikroprogrammierbare Architekturen" für Per-Pixel-Vertexverarbeitung helfen Spiele mehr lebensecht zu machen. "Mesh skinning" soll Spiele noch realistischer aussehen lassen. Natürlich gibt es noch viele mehr nützliche Funktionen; diese Tutorien versuchen so viele davon zu erklären.

Wenn Sie sich schon mit DirectX auskennen, können Sie gleich zum zweiten Teil übergehen. Für alle, die sich noch nicht so auskennen folgt eine kleine Beschreibung. DirectX ist eine Sammlung von Klassen um Hardware auf einem möglichst tiefen Level anzusprechen. Sie sind nur so schnell, weil sie "dünn" sind. Ein Aufruf an DirectX kommt viel schneller an die Hardware, als bei den traditionellen Windows-API Aufrufen, die bevor sie etwas sichtbares bewirken erstmal durch mehrere Ebenen laufen müssen. DirectX unterstützt auch fast alle vorstellbaren Funktionen der aktuellsten 3D- und Soundkarten, den aktuellsten Internetverbindungen und Eingabegeräten. Aus der Sicht eines Programmierers erstellen wir eine Instanz der DirectX-Klassen, sei es DirectX selbst, Direct3D, DirectSound, DirectMusic... Jetzt können wir anfangen herumzuspielen, normalerweise initialisieren wir die Hardware - für Grafiken setzen wir den Display Modus,Renderoptionen, laden Texturen und Geometrie. Danach starten wir eine Schleife, in der wir alles aktualisiern , was der Aktualisierung bedarf und rendern normalerweise den nächsten Frame - Frames sind die Basis der Framerate; eine Framerate von 70 zeigt, dass die Schleife 70mal in der Sekunde durchlaufen wird.

Den Rest werden Sie lernen, wenn Die weiterlesen.

Der Anfang  

Der erste Teil den wir lernen müssen ist, wie wir einen einfachen Rahmen für DX-Programme erstellen. Sie sollten den Code wirklich lernen, bis Sie ihn im Schlafe können, da er immer wieder und wieder gebraucht wird. Wenn Sie diesen Code nicht wissen, dann werden Sie später irgendwo hängen bleiben. Sie werden den nächsten Code nur verstehen, wenn Sie hier alles lernen.

2a.) DirectX-Projekt erstellen

Starten Sie ein neues Projekt, Standard-EXE ist sehr gut. Wie immer wird eine neue Form geöffnet - das sollte Sie nicht überraschen, weil es nicht sinnvoll ist, zu versuchen, DirectX mit wenigen VB-Kenntnissen zu lernen. Öffnen Sie das Projekt-Menu und klicken Sie auf Verweise. Scrollen Sie runter und bis Sie einen Eintrag "DirectX8 for Visual Basic Type Library" finden und aktivieren Sie die Checkbox gleich daneben - dann klicken Sie OK. Dieses Projekt ist komplett fähig DirectX zu benutzten. Sollten Sie irgendwelche Probleme damit haben, dann gibt es mehrere Probleme:

  • Haben Sie DirectX8 installiert? Wenn nicht, wird der Eintrag nicht erscheinen.
  • Haben Sie Visual Basic 5 oder 6. DirectX benutzt das Component Object Models (COM) welches erst in VB5 oder höher unterstützt wird.
  • Sie haben die Bedingungen 1.) und 2.) erfüllt, der Eintrag erscheint aber immer noch nicht. Klicken Sie auf "Durchsuchen" und finden Sie die Datei "C:\Windows\System\DX8vb.dll". Das sollte auch funktionieren.

2b.) Die Variablen

Dieser Teil kommt in den Deklarationteil:

'Die benötigten Variablen
Dim DX As DirectX8
 'Das Hauptobjekt, alles kommt von hier.
Dim D3D As Direct3D8
 'Steuert alle 3D Grafiken
Dim D3DDevice As Direct3DDevice8 'Repräsentiert die Hardware,  
        'die in Wirklichkeit rendert
Dim bRunning As Boolean
 'Entscheidet, ob das Programm läuft oder nicht
 'Diese Variablen werden nicht richtig benötigt.
 'Sie existieren nur für die Berechnung der Framerate
Private Declare Function GetTickCount Lib "kernel32.dll" () As Long
Dim LastTimeCheckFPS AsLong 'Letzte Zeit der FPS-Berechnung
Dim FramesDrawn As Long  'Wie viele Frames sind gezeichnet worden.
Dim FrameRate As Long
 'Die wirklich Framerate...

2c.) Die Initialisierung

Nachdem alle Variablen vorhanden sind, müssen wir sie initialisieren. Auch müssen ein paar Parameter gesetzt werden. Nicht schwer bis jetzt, wir hängen aber immer noch im Fenstermodus (nicht Fullscreen, wie die meisten Spiele).

' Initialize: Diese Prozedur initialisiert das Programm.
' Bei Erfolg True, False, wenn ein Fehler aufgetreten ist.
Public Function Initialise() As Boolean
  On Error Goto ErrHandler:
  Dim DispMode As D3DDISPLAYMODE 'Beschreibt den Displaymode
  Dim D3DWindow As D3DPRESENT_PARAMETERS
  'Beschreibt den Viewport

  Set Dx = New DirectX8
    'Erstellt unser Hauptobjekt
  Set D3D = Dx.Direct3DCreate()
    'Befielt dem Hauptobjekt ein Direct3D-Object zu erstellen
  D3D.GetAdapterDisplayMode D3DADAPTER_DEFAULT, DispMode
    'Speichert den aktuellen Displaymode
  D3DWindow.Windowed = 1 'Wir benutzen den Fenstermodus
  D3DWindow.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC
    'Mit dem Monitor aktualisieren
  D3DWindow.BackBufferFormat = DispMode.Format
    'Wir benutzten das eingestellte Format
    'Diese Zeile wird gleich erklärt:
  Set D3DDevice = D3D.CreateDevice(D3DADAPTER_DEFAULT, _
   D3DDEVTYPE_HAL, FrmMain.Hwnd, _
   D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DWindow)
Initialise = True
'Erfolg!
Exit Function
ErrHandler: 'Irgendein Fehler
   Initialise = False

End Function

Diese Funktion sollte, falls alles gut geht, alle Objekte erzeugen - soweit, dass wir sie benutzten können. Trotzdem gibt es hier einige Dinge zu erklären und mehrere Dinge die schief laufen können.

Die wichtigste Sache die schief gehen kann, ist der CreateDevice-Aufruf. Er enthält verschiedene Parameter, die hardwareabhängig sind. Am besten schauen wir uns den Aufruf mal genauer an, hier ist ein Prototyp:

    <center>

object.CreateDevice(Adapter As Long, DeviceType As _
CONST_D3DDEVTYPE, hFocusWindow As Long, _
BehaviorFlags As Long, PresentationParameters As _
D3DPRESENT_PARAMETERS) As Direct3DDevice8
</center>
Adapter: Dieser Wert kann entweder Primär oder Sekundär sein. D3ADAPTER_DEFAULT repräsentiert immer den primären Adapter; wir behandeln den Nutzen von Adaptern in einer späteren Lektion.
DeviceType: Dieser Parameter ermöglicht uns zwischen dem HAL(Hardware Acceleration Layer) oder einem "Reference Rasterer"(Einem Debbuging-Tool) zu entscheiden. Es existiert auch noch eine dritte Option: Software Rendering, welche benutzterdefinerte Renderer unterstützt. Sie finden mehr Informationen dazu im DirectX DDK(Driver Development Kit), aber wenn Sie einen 3D-Rasterer schreiben wollen, dann werden Sie dazu wohl kaum Visual Basic benutzten :). Benutzten Sie also immer D3DDEVTYPE_HAL oder D3DDEVTYPE_REF. Bemerkt sei noch, dass der Aufruf schief läuft, wenn der HAL nicht verfügbar ist.
hFocusWindow: Dieser Wert hilft DirectX den Status ihres Programm zu verfolgen. Hier muss nur die hWnd-Eigenschaft der Form angegeben werden; diese Form muss eine normale Form, ohne irgendwelche Merkwürdigkeit sein - also bitte keine verformten oder MDI-Forms.
BehaviorFlags: Diese Eigenschaft bestimmt wie die Direct3D-Engine mit Vertexen, Texturen, Lighting usw. umgeht. Die beste Option hier ist D3DCEATE_PUREDEVICE - allerdings unterstützen nur wenige Grafikkarten diese Option(selbst die relativ neue TnL Geforce 256 nicht) Diese Option heißt, dass die Grafikkarte fasst alles übernimmt - Transformatierung, Schatten, Lighting, Texturen and Rasterung. Wenn ihre Hardware das nicht unterstützt ist das nächstbeste D3DCREATE_HARDWARE_VERTEXPROCESSING - diese Option benutzt Hardware so oft wie möglich; die meisten aktuellen 3D-Karten sollten diese Option unterstützen. Falls auch das schief läuft, können Sie es mit der Option D3DCREATE_MIXED_VERTEXPROCESSING versuchen, welche die Hardware benutzt, wenn es aber nicht geht, dann springen die Software-Komponenten ein. Die letzte Möglichkeit ist dann nur noch der Software-Rasterer und falls die Hardware kein 3D unterstützt, bleibt das die letzte Option. Der Software-Rasterer ist zwar fast immer sehr langsam und ist auch nicht sehr gut zu benutzten. Falls Sie nicht darumkommen: D3DCREATE_SOFTWARE_VERTEXPROCESSING.
PresentationParameters: Dieser Parameter entscheidet, was für einen Displaymodus Sie benutzen wollen. Wenn Sie die aktuelle Einstellung verwenden(wie im Beispiel oben), können Sie den Fenstermodus benutzten; wenn Sie diese Werte verändern, dann kommen Sie in den Fullscreen-Modus. Wie Sie jetzt sehen sollten, existieren hier mehrere Hardware-abhängige Parameter; um dieses Problem zu lösen, benutzen wir einen Prozess namens Enumeration ("Aufzählung"), um herauszubekommen, was unser Computer machen kann. Aber das kommt später; dieses Kapitel ist ja nur der Anfang.

Rendern  

Dieser Teil wird wahrscheinlich der größte Teil in ihrem Programm werden. Dieser Code wird so oft wie möglich in einer Schleife ausgeführt; unsauberer Code endet in schlechter Performance; sauberer und schneller Code führt zu unglaublichen Frameraten und schönen Grafiken. Nachdem Sie durch den Initialisierungsprozess sind und alles was Sie brauchen eingestellt haben, wird sich ihre Arbeit nur noch um diesen Teil drehen. Normalerweise wird der gesammte Bildschirm in einer Prozedur gerendert, andere Prozeduren können zwar auch benutzt werden, eine Prozedur ist aber schneller. Dieser Prozess läuft fast immer nach dem selben Muster ab:

  1. Verticen, Kameras, Texturen, usw. werden aktualisiert.
  2. Bildschirm säubern
  3. Den neuen Frame zeichnen - das ist der schwere Teil.
  4. Finale Variablen aktualisieren
  5. Den gerenderten Frame auf den Bildschirm kopieren

In dieser Lektion erstellen wir nur einen einfachen Rahmen für diese Prozedur:

Public Sub Render()
   '1.) Zuerst müssen wir den Bildschrim leeren.
   'Das muss immer passieren
 D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, &HCCCCFF, 1#, 0
   'Der Wert in der Mitte ist Hexadezimal,
   'wenn Sie das von HTML her kennen.
   '2.) Normalerweise würden wir hier jetzt alles rendern
   ', vorerst bleibt dieses Feld ersteinmal leer.
 D3DDevice.BeginScene
   'Alle Aufrufe zum Rendern kommen zwischen diesen Aufrufen
 D3DDevice.EndScene
   '//3. Den Frame auf den Bildschirm kopieren
   'Dieser Aufruf ist ungefähr das selbe wie
   'Primary.Flip() in DirectX7
   'Diese Werte solten für fast alles arbeiten.
 D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
End Sub

Dieser Code ist ziemlich einfach, er zeichnet nichts, er aktualisiert keine Variablen. Aber Sie werden sehen, dass sich das bald ändert.

Erwähnenswert ist hier auch noch die Benutzung von "ByVal 0" in manchen Parametern. Wenn Sie D3DDevice.Present eingeben und der Tooltip erscheint, werden Sie sehen, dass SourceRect, DestRect und DirtyRegion alle als "As Any" definiert sind. Das heißt, dass wir wirklich alles übergeben könnten. Wenn wir jetzt nur 0 als Parameter übergeben würden, wird Visual Basic es als "Nothing" interpretieren - was wir absolut nicht wollen; wir wollen wirklich eine 0 übergeben. Deshalb übergeben wir die 0 mit ByVal und VB macht sicher, dass wirklich eine 0 ankommt, nicht "Nothing".

Die Hauptschleife  

Die Hauptschleife ist eine kurze Schleife; in jeder Schleife aktualisieren wir die Grafik, KI, Sound und halt alles, was ein Spiel machen muss. Je schneller diese Schleife, desto höher die Framerate. Natürlich können wir hier am Anfang auch den Initialisierungsprozess einbauen. Das sollte dann so aussehen:

Private Sub Form_Load()
 Me.Show 'Versichern, dass das Fenster sichtbar ist.
 bRunning = Initialise()
 Debug.Print "Device Creation Return Code : ", bRunning
   'Damit jeder sieht, was passiert
 Do While bRunning
   Render 'Frame aktualisieren
   DoEvents ' Gibt dem Fenster Zeit zum "Denken",
            ' damit es auf Ereignisse reagieren kann
       'Framerate ausrechenen, es ist nicht größer
       'wichtig zu wissen wie...
     'Also brauchen wir uns keine Sorgen zu machen
   If GetTickCount - LastTimeCheckFPS >= 1000 Then
     LastTimeCheckFPS = GetTickCount
     FrameRate = FramesDrawn 'Framerate unterbringen
     FramesDrawn = 0 'Zähler zurücksetzten
     Me.Caption = "DirectX-Graphics: Lesson 01 " & _
       "{" & FrameRate & "fps}"
       'Framerate ausgeben
   End If
   FramesDrawn = FramesDrawn + 1
 Loop
 'Wenn wir hierhin gekommen sind, müssen wir alles aufräumen
 'Also müssen wir alles aufräumen, Es ist also nicht
 'unbedingt nötig ist aber eine gute Programmierübung

 On Error Resume Next 'Falls die Objekte nie erstellt wurden
   '(die Initialisierung ist schief gelaufen) könnten wir
   'bekommen wenn wir versuchen sie zu löschen. Daran müssen
   'Fehler wir denken, aber wenn wir das
   'Programm sowieso beenden...
 Set D3DDevice = Nothing
 Set D3D = Nothing
 Set Dx = Nothing
 Debug.Print "All Objects Destroyed"
'Programmende

 Unload Me
 End
End Sub

Sie werden bemerkt haben, dass dieser Code in der Form_Load-Prozedur steht; das heißt, dass DirectX beim Programmladen gestartet wird und dann gleich in die Hauptschleife übergeht. Eine wichtige Sache ist der Me.Show() Aufruf in den ersten Zeilen. In einem normalen VB-Programm, wird die Form erst gezeigt, wenn die Form_Load-Prozedur durchlaufen ist - in unserem Fall würde die Form dann nach dem Beenden kurz erscheinen. Eine "DoEvents"-Zeile würde eventuell auch helfen, ist aber auch nicht sehr sauber.

Die ganze Schleife basiert auf einer Variable, bRunning, welche auf True gesetzt wird, wenn das Programm gestartet wird. Sobald Sie auf False gesetzt wird, verlassen wir die Schleife in Form_Load. Sie können sich die Variable als An/Aus-Schalter vorstellen; das Programm sollte, bei einer Framerate von 60, in 1/60 Sekunden reagieren sollte.

Es gibt noch zwei Dinge die Sie sich überlegen sollten. Das erste ist die Aufrufreihenfolge ihrer Prozeduren - was hier noch nicht so wichtig ist, da wir ja nur eine haben. In einem normalen Spiel haben Sie aber mehrere Prozeduren. Ein gutes Beispiel ist es, wenn Sie ene Graphic-Engine haben, die von der Tastatureingabe abhängig ist - wenn Sie die Grafik-Routine zuerst aufrufen und erst danach die Eingabeüberprüfung, dann ist ihre Grafik-Engine immer einen Frame zurück. Wenn Sie allerdings beide in der umgekehrter Reihenfolge aufrufen, dann ist alles synchronisiert. Das zweite ist die Framerate-Berechnung. Die Framerate ist sehr praktisch, weil Sie immer wissen, in welcher Geschwindigkeit ihr Programm gerade läuft. Eigentlich erhöht es pro Schleifendurchgang einen Zähler um eins. Nach einer Sekunde wird das dann in die Framerate-Variable kopiert.

Die letzte Sache ist die "DoEvents"-Zeile. Ohne diesen einfachen Aufruf würde alles nach kurzer Zeit auseinanderlaufen. Der DoEvents-Befehl erlaut dem Fenster zu "atmen", wenn man es so nennen kann; ohne ihn würde fast alles stoppen - Forms tauchen nicht auf, Maus-/Tastatureingaben werden ignoriert und das Setzen von Eigenschaften überhört(Wie z.B. das Setzen der Caption-Eigenschaft). Das ist besonders schlecht, wenn Sie Tastatur-/Mauseingabe brauchen - wenn keine Maus und Tastaturereignisse registriert werden, dann kann die Schleife nicht beendet werden, wenn die Schleife sich nicht terminieren lässt, läuft sie weiter und weiter - bis sie irgendwann abstürzt(was sicherlich passieren wird). Zu Ihrer Sicherheit lassen Sie die Zeile am Besten dort und denken nicht drüber nach.

Aufräumen  

Das Letzte was wir machen sollten ist Aufräumen, obwohl es nicht das Ende der Welt bedeutet, wenn Sie es vergessen. Am Ende ihres Programms sollten Sie immer alle Objektvariablen, die Sie erstellt haben zerstören. Sie haben ja oben schon den Code zum Aufräumen gesehen:

On Error Resume Next
Set D3DDevice = Nothing
Set D3D = Nothing
Set Dx = Nothing

Wirklich einfach, um ein Objekt zu zerstören schreiben Sie einfach "Set = Nothing" und es läuft gut. Ich habe hier noch ein "On Error Resume Next" eingebaut, weil wir Fehler bekommen, falls die Objekte nie erstellt wurden - was passieren wird, wenn die Initialisierung nicht läuft.

Das Beispiel im Fullscreen-Modus  

Wenn Sie das Beispielprogramm(bis jetzt) starten, dann werden Sie merken, dass es im Fenster-Modus läuft(Ich habe es Ihnen ja gesagt). Obwohl eine Menge Spiele eine Option für Fenster-Modus haben, benutzten sie doch meistens einen Fullscreen-Modus. Es gibt viele Gründe dafür, aber es ist hauptsächlich das "Look and feel" - ein Spiel sieht viel realistischer aus, wenn Sie nicht den Desktop und die Startleiste sehen.

Den Fullscreen-Modus zu benutzen ist nicht sehr schwer, wir müssen nur ein paar kleine Änderungen in unserer Strukur vornehmen.

DispMode.Format = D3DFMT_X8R8G8B8
DispMode.Width = 640
DispMode.Height = 480
D3DWindow.SwapEffect = D3DSWAPEFFECT_FLIP
D3DWindow.BackBufferCount = 1 'Nur ein Backbuffer
D3DWindow.BackBufferFormat = DispMode.Format
   'Wir benutzten das eingestellte Format
D3DWindow.BackBufferHeight = 480
D3DWindow.BackBufferWidth = 640
D3DWindow.hDeviceWindow = frmMain.hWnd

Nicht allzu schwer; dieser Code ersetzt den "D3DWindow" Initialisierungs-Code, den wir früher benutzt haben. Ich setzte voraus, dass Sie schon über Auflösungen(640x480, 1024x768, usw...), so dass ich das hier nicht besprechen muss. Es existiert aber noch etwas worauf Sie aufpassen müssen, wenn Sie den obenstehenden Code benutzten: Das Display-Mode-Format - DispMode.Format - wie Sie oben sehen können auf "D3DFMT_X8R8G8B8" gesetzt. Aber was soll das bedeuten?

Also, alle Texturen und Surfaces(Backbuffers, Primary Buffers, Tiefenpuffer, ...) sind in einem bestimmten Format im Speicher gespeichert. Sie kennen wahrscheinlich auch die Farbtiefen, wenn Sie mit Auflösungen vertraut sind - 8 Bit, 16 Bit, 24 Bit, 32 Bit. Dieser Wert bestimmt wie viel Bit Speicher für ein Pixel verbraucht werden, 8 Bits sind ein Byte, also braucht eine Farbtiefe von 32 Bit 4 Bytes pro Pixel. Je höher die Farbtiefe, desto mehr Speicher wird verbraucht (und desto schöner sieht es aus). Aber was hat diese Information für einen Wert für uns? Der D3DFMT_X8R8G8B8-Flag bestimmt, wie dieser Speicher benutzt wird - in diesem Fall 8888 Format=32 Bit Farben. Es gibt noch eine Handvoll mehr Formate, die wir später vielleicht noch sehen werden.

Mit dem Code oben, brauchen Sie einen Computer, der 640x480 Pixel in einer Farbtiefe von 32 Bit Farben unterstützt, wenn nicht wird es nicht laufen. Also behalten Sie ihre aktuelle Auflösung im Auge - wenn die Hardware die eingestellte Auflösung nicht unterstützt bekommen Sie einen Fehler. Das nächste Kapitel erklärt einen Prozess namens Enumeration(Aufzählung), mit dem Sie herausfinde, was ihre Hardware unterstützt.

Beispielprojekt zu diesem Tutorial [16300 Bytes]

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.