Die Community zu .NET und Classic VB.
Menü

Einfache 3D-Grafik

 von 

Übersicht 

Im zweiten Tutorial aus der Reihe “Managed DirectX mit VB .NET” soll es darum gehen, wie sie einen einfachen dreidimensionalen Würfel auf den Bildschirm zaubern können. Das geht allerdings nicht ohne viel Theorie...

Unsere 3D-Welt  

Der Titel "3D-Welt" ist zwar etwas hochgegriffen, da wir nur einen einfachen Würfel verwenden werden, aber letztendlich gilt das, was wir hier lernen auch für 3D-Landschaften und sonstiges. Bevor wir uns allerdings darum kümmern diesen Würfel auf den Bildschirm zu bringen, müssen wir wissen, wie der Würfel im Speicher abgelegt wird.


Abbildung 1: Verschiedene Formen aus Dreiecken

Wenn Sie sich schon ein wenig mit dem Thema 3D-Grafik (nicht unbedingt Direct3D) beschäftigt haben, so werden Sie wissen, dass sämtliche Objekte in einer dreidimensionalen Welt aus Polygonen, genauer gesagt Dreiecken, bestehen. Der Vorteil von Dreiecken gegenüber Rechtecken, usw. ist, dass es zum einen das einfachste Polygon ist und zum anderen nie nach innen oder außen gewölbt sein kann (konkav / konvex). So wie ein Rechteck z.B. aus zwei gleichen Dreiecken besteht, so kann man auch sämtliche anderen Figuren aus Dreiecken bauen:

Es gibt mehrere Möglichkeiten diese Dreiecke zu speichern.

TriangleList

Die TriangleList ist wohl die einfachste Art eine Form als Dreieck zu speichern. Es werden einfach die Dreiecke mit drei Punkten pro Dreieck gespeichert: 0, 1 & 2; 3, 4 & 5


Abbildung 2: Beispiel für eine Triangle-List

TriangleStrip

Der TriangleStrip hat gegenüber der TriangleList den Vorteil weniger Platz zu verbrauchen, da immer zwei Punkte der vorigen Dreiecks geteilt werden. Der Nachteil liegt daran, dass sie bei weitem nicht so flexibel sind und bei der Verwendung von Lighting zu Darstellungsfehlern führen können. 1, 2 & 3; 2, 3 & 4; 3, 4 & 5; 4, 5 & 6 ...


Abbildung 3: Beispiel für ein TriangleStrip

TriangleFan

TriangleFans sind vor allem für kreisformige Strukturen geeignet. Hier wird allerdings der erste Punkt (Vertex) mit allen Dreiecken geteilt. 0, 1 & 2; 0, 2 & 3; 0, 3 & 4


Abbildung 4: Beispiel für einen TriangleFan

LineList

Die LineList speichert eine Gruppe von Punkten und verbindet jeweils zwei mit einer 1 Pixel schmalen Linie: 0 & 1; 2 & 3; 4 & 5


Abbildung 5: Beispiel für eine Line-List

LineStrip

Der LineStrip verbindet aufeinanderfolgende Vertices miteinander: 0 & 1; 1 & 2; 2 & 3; 3 & 4 ...


Abbildung 6: Beispiel für einen Line-Strip

PointList

Die PointList hat ihren Einsatzbereich vor allem in Bezug mit Partikelsystemen verwendet. Hier werden die einzelnen Vertices als 1 Pixel gezeichnet


Abbildung 7: Beispiel für eine Point-List

Für den Würfel in den Beispielen zu diesem Tutorial habe ich eine TriangleList benutzt. Besser wäre natürlich ein TriangleStrip, dies wäre jedoch für den Anfang ein wenig kompliziert:

' Die obere Seite
cube(0) = New CustomVertex.PositionColored(-1, 1, 1, Color.Blue.ToArgb)
cube(1) = New CustomVertex.PositionColored(1, 1, 1, Color.Red.ToArgb)
cube(2) = New CustomVertex.PositionColored(-1, 1, -1, Color.Blue.ToArgb)
cube(3) = New CustomVertex.PositionColored(1, 1, -1, Color.Red.ToArgb)
cube(4) = New CustomVertex.PositionColored(-1, 1, -1, Color.Blue.ToArgb)
cube(5) = New CustomVertex.PositionColored(1, 1, 1, Color.Red.ToArgb)

' Die untere Seite
cube(6) = New CustomVertex.PositionColored(-1, -1, -1, Color.Blue.ToArgb)
cube(7) = New CustomVertex.PositionColored(1, -1, 1, Color.Red.ToArgb)
cube(8) = New CustomVertex.PositionColored(-1, -1, 1, Color.Blue.ToArgb)
cube(9) = New CustomVertex.PositionColored(1, -1, 1, Color.Red.ToArgb)
cube(10) = New CustomVertex.PositionColored(-1, -1, -1, Color.Blue.ToArgb)
cube(11) = New CustomVertex.PositionColored(1, -1, -1, Color.Red.ToArgb)

' Die linke Seite
cube(12) = New CustomVertex.PositionColored(-1, 1, -1, Color.Blue.ToArgb)
cube(13) = New CustomVertex.PositionColored(-1, -1, -1, Color.Blue.ToArgb)
cube(14) = New CustomVertex.PositionColored(-1, 1, 1, Color.Blue.ToArgb)
cube(15) = New CustomVertex.PositionColored(-1, 1, 1, Color.Blue.ToArgb)
cube(16) = New CustomVertex.PositionColored(-1, -1, -1, Color.Blue.ToArgb)
cube(17) = New CustomVertex.PositionColored(-1, -1, 1, Color.Blue.ToArgb)

' Die rechte Seite
cube(18) = New CustomVertex.PositionColored(1, 1, -1, Color.Red.ToArgb)
cube(19) = New CustomVertex.PositionColored(1, 1, 1, Color.Red.ToArgb)
cube(20) = New CustomVertex.PositionColored(1, -1, -1, Color.Red.ToArgb)
cube(21) = New CustomVertex.PositionColored(1, -1, 1, Color.Red.ToArgb)
cube(22) = New CustomVertex.PositionColored(1, -1, -1, Color.Red.ToArgb)
cube(23) = New CustomVertex.PositionColored(1, 1, 1, Color.Red.ToArgb)

' Die Rückseite
cube(24) = New CustomVertex.PositionColored(-1, 1, -1, Color.Blue.ToArgb)
cube(25) = New CustomVertex.PositionColored(1, 1, -1, Color.Red.ToArgb)
cube(26) = New CustomVertex.PositionColored(-1, -1, -1, Color.Blue.ToArgb)
cube(27) = New CustomVertex.PositionColored(1, -1, -1, Color.Red.ToArgb)
cube(28) = New CustomVertex.PositionColored(-1, -1, -1, Color.Blue.ToArgb)
cube(29) = New CustomVertex.PositionColored(1, 1, -1, Color.Red.ToArgb)

' Die Vorderseite
cube(30) = New CustomVertex.PositionColored(1, 1, 1, Color.Red.ToArgb)
cube(31) = New CustomVertex.PositionColored(-1, 1, 1, Color.Blue.ToArgb)
cube(32) = New CustomVertex.PositionColored(-1, -1, 1, Color.Blue.ToArgb)
cube(33) = New CustomVertex.PositionColored(-1, -1, 1, Color.Blue.ToArgb)
cube(34) = New CustomVertex.PositionColored(1, -1, 1, Color.Red.ToArgb)
cube(35) = New CustomVertex.PositionColored(1, 1, 1, Color.Red.ToArgb)

Listing 1: Der Code für den Würfel

Der Code sieht ersteinmal schrecklicher aus, als er wirklich ist. Die Vertices werden alle in ein Array vom Typ CustomVertex.PositionColored gespeichert. Diese Sorte Vertices haben, wie der Name schon sagt, eine Position und eine Farbe.

Allerdings ist diese Methode die Vertices zu erstellen sehr umständlich. Bei einfachen Körpern, wie Kugeln oder Würfeln, die einer einfachen Logik folgen, geht es gerade noch, für Körper und ähnlichem gibt es die Möglichkeit die Körper in einem Programm zu erstellen und dann mit Direct3D zu laden (doch dazu mehr in einem anderen Tutorial).

Culling  

Unser Würfel hat 6 Seiten, allerdings können nur maximal drei dieser Seiten auf einem Bild zu sehen sein. Da diese Seiten aber dennoch gerendert werden müssten, auch wenn sie auf dem finalen Bild nicht mehr zu sehen sind, fügt Direct3D eine kleine Optimierung hinzu, die bewirkt, dass Polygone, die vom Betrachter wegzeigen ausgelassen werden. Die Entscheidung welche Dreiecke weggelassen werden können, wird über die Ordnung der Vertices eines Dreiecks getroffen.


Abbildung 8

Mit den Standard-Culling-Einstellungen von Direct3D würde nur das erste Dreieck gezeichnet werden. Der Grund ist, dass die Vertices des ersten Dreiecks im Uhrzeigersinn angeordnet sind, während die Ordnung im zweiten Dreieck gegen den Uhrzeigersinn ist. Würden wir jetzt diese Dreieck um 180-Grad auf der Y-Achse drehen würden, so dass wir sie "von hinten" sehen würden, so würde das erste Dreieck nicht gezeichnet werden. Deshalb sind die Vertices in unserem Würfel alle im Uhrzeigersinn angeordnet, so dass jeweils die hinteren Dreieck nicht gerendert werden.

.RenderState.CullMode = Cull.CounterClockwise

Listing 2: Den Cull-Modus umstellen

Den Culling-Modus können wir über die CullMode-Eigenschaft des RenderState-Objekts unseres Devices setzen. Mit Cull.Clockwise können wir Direct3D übringends auch dazu bringen sämtliche Dreiecke im Uhrzeigersinn zu ignorieren, doch normalerweise sollte die Standardeinstellung CounterClockwise genügen.

Auch Cull.None findet z.B. beim Rendern von (halb-)durchsichtigen Körpern seine Verwendung, sollte aber nicht einfach aus Faulheit die Vertices richtig zu ordnen verwendet werden. Auf diese Weise können Sie gut und gerne 40% der Rechenleistung verschwenden, was bei schnellen Rechnern zwar häufig nicht viel ausmacht, aber langsame Rechner ins Ruckeln treibt...

Vertexbuffer  

Ein Vertexbuffer hat die Funktion Vertices zu speichern (wer hätte das gedacht?). Zwar gibt es auch die Möglichkeit Vertices aus einem Array zu rendern, allerdings sind VertexBuffer schneller, da Direct3D hier kontrollieren kann, wo die Daten gespeichert werden. Im glücklichsten Fall (der gar nicht so selten vorkommt) landen die Vertices im Speicher der Grafikkarte so dass sie nicht jedes mal über den AGP-Bus transferiert werden müssen. Das Erstellen eines VertexBuffers ist relativ einfach:

Dim cube() As CustomVertex.PositionColored
Try
  p_vbCube = New VertexBuffer(GetType(CustomVertex.PositionColored), _
    36, p_D3DDevice, 0, _
    CustomVertex.PositionColored.Format, Pool.Managed)
  cube = DirectCast(p_vbCube.Lock(0, LockFlags.None), _
    CustomVertex.PositionColored())
Catch exp As Exception
  Return False
End Try

' Hier kommt der Code für den Würfel hin...

p_vbCube.Unlock()

Listing 3: Einen Vertexbuffer erstellen

Der Konstruktor des Vertexbuffers verlangt zum einen das Format unserer Vertices (es können nur Vertices mit dem gleichen Typ in einem Vertexbuffer gespeichert werden), welches wir mit GetType abfragen können. Anschließend kommt ein Parameter für die Anzahl der Vertices, einen für unser Device, einen für verschiedene andere Einstellungen (die wir nicht brauchen) und noch einen für das Vertexformat. Mit Pool.Managed geben wir an, dass wir Direct3D überlassen wollen, wo es die Daten nun wirklich hintut. Damit wir den Inhalt des VertexBuffers verändern können, müssen wir ihn zuerst sperren. Dies geht über die Lock-Funktion, die uns ein Array von der Größe des gesperrten Bereichs (bei uns ist das alles) zurückgibt.

Wenn wir mit den Veränderungen fertig sind, müssen wir den Buffer zuerst wieder entsperren, da wir ansonsten beim Rendern einen Fehler erhalten würden. Und schon ist unser Würfel in der Kiste :)

Matrizen  

Matrizen sind ein hochkompliziertes mathematisches Thema, weswegen ich hier nicht genauer darauf eingehe, wie diese funktioniert. Für alle, die etwas damit anfangen können, Direct3D verwendet hauptsächlich 4x4-Matrizen. Was allerdings viel wichtiger ist, als zu wissen wie Matrizen mathematisch funktionieren, ist wie und wofür wir sie einsetzen können: den mathematischen Kram überlassen wir Direct3D.

Die drei wichtigsten Matrizen sind die World-, View- und die Projection-Matrix, die hier im Kurzen vorgestellt werden sollen.

Die World-Matrix:

Die World-Matrix verändert, wie man aus dem Namen vielleicht deuten kann, wie unsere 3D-Welt angezeigt wird. Mit der World-Matrix können wir z.B. unseren Würfel verschieben, drehen, vergrößern und manches mehr. Hier ein Stück aus dem Beispiel-Code zu diesem Tutorial:

cubeangle += CSng((Environment.TickCount - lastFrameUpdate) / 1000)
If cubeangle >= Math.PI * 2 Then cubeangle = 0

p_MatCube1 = Matrix.Multiply(Matrix.RotationY(cubeangle), _
                            Matrix.RotationX(cubeangle))

Listing 4

Die letzten zwei Zeilen sind denke ich mal die Interessantesten: hier wird eine neue Matrix aus zwei Matrizen erstellt. Die erste rotiert um einen bestimmten Winkel auf der Y-Achse, die zweite rotiert um den selben Winkel auf der X-Achse. Hier befinden sich schon zwei Fallen. Zum ersten ist es im Gegensatz zur normalen Mathematik nicht egal, in welcher Reihenfolge wir multiplizieren d.h. wenn wir die Rotations-Matrix für die X-Achse mit der der Y-Achse multiplizieren würden, würden wir ein komplett anderes Ergebnis als im oberen Beispiel erhalten. Zweitens werden Winkel in Direct3D im Bogenmaß und nicht in Grad angegeben. Wer damit nichts anzufangen weiß, der kann die einfache Formal Winkel in Grad * (Math.PI / 180) verwenden.

Die ersten beiden Zeilen im obigen Code sorgen nur dafür, dass der Würfel sich richtig dreht. Wenn wir hier einen beliebigen konstanten Wert verwendet hätten, so würde sich der Würfel auf verschieden schnellen Rechnern unterschiedlich schnell drehen. Deswegen wird hier dafür gesorgt, dass sich der Würfel in der Sekunde und einen bestimmten Wert (1) dreht. Falls wir bei 360° (PI * 2) angekommen sind, wird der Würfel zurück auf 0° gesetzt.

Hier noch eine Reihe weiterer Funktionen für die Verwendung mit der World-Matrix:

Name der Funktion Verwendungsmöglichkeit
Multiply Kombiniert zwei Matrizen miteinander. Wie schon gesagt ist die Reihenfolge nicht beliebig.
RotateAxis Erstellt eine Matrix für eine beliebige Drehung, die über einen Richtungsvektor angegeben werden muss.
RotateX Rotiert um die X-Achse
RotateY Rotiert um die Y-Achse
RotateZ Rotiert um die Z-Achse
Scale Vergrößert alles um einen bestimmten Faktor
Translate Verschiebt Objekte beliebig im Raum.
Identity Beinhaltet eine Matrix die keinerlei Veränderungen vornimmt (also praktisch leer ist, mathematisch stimmt dies mal wieder nicht :)

Das man mit dem Kombinieren von Matrizen nette Ergebnisse erreichen kann zeigt sich beim zweiten Würfel in Beispiel zu diesem Projekt. Er dreht sich sowohl um sich selbst aber auch um einen anderen Würfel in der Mitte. Dieser Effekt wird nicht durch das Erstellen eines zweiten Würfels erreicht, sondern einfach dadurch, dass der Würfel mit einer anderen World-Matrix gerendert wird. Der Code zum Erstellen dieser Matrix sieht so aus:

p_MatCube2 = Matrix.Multiply(Matrix.RotationY(cubeangle), _
                            Matrix.Translation(-5, 0, 0))
p_MatCube2 = Matrix.Multiply(p_MatCube2, Matrix.RotationY(cubeangle))

Listing 5: Der zweite Würfel

Die View-Matrix:

Die View-Matrix definiert praktisch eine Kamera mit der wir in unsere 3D-Welt hineinschauen können. Diese Kamera können wir beliebig durch die 3D-Welt bewegen. Hier der Code aus dem Beispiel der die View-Matrix erstellt:

.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, -15), _
                                  New Vector3(0, 0, 0), _
                                  New Vector3(0, 1, 0))

Listing 6: Die View-Matrix einrichten

Der erste übergebene Vektor gibt die Position an, der zweite den Punkte auf den die Kamera schaut. Mit dem dritten lässt sich viel Mist anstellen: z.B. die 3D-Welt auf dem Kopf rendern. Der Vektor gibt an, wo sich im Bild oben befindet. Der beste Wert ist (0, 1, 0) beidem keinerlei Veränderungen vorgenommen werden.

Die Projection-Matrix:

Die Projection-Matrix verändert noch einige "Eigenschaften" der Kamera: den Sichtwinkel und die Entfernung bis in die wir sehen können:

.Transform.Projection = Matrix.PerspectiveFovLH(Math.PI / 4, 4 / 3, 1, 100)

Listing 7: Die Projection-Matrix einstellen

Die ersten beiden Parameter kümmern sich um den Sichtwinkel, die letzten beiden ab welchen Entfernungen von der Kamera Vertices gerendert werden sollen (hier von 1 bis 100).

Der Tiefenbuffer  

Schon im letzten Teil haben wir gesehen, wie wir einen Tiefenbuffer initialisieren können. Dessen Bedeutung blieb jedoch im Dunkeln. Der Depth-Buffer hat die Aufgabe zu verhinden, dass Körper, die eigentlich hinter einem anderen liegen, trotzdem davor gezeichnet werden. So lustig das auch klingt, so problematisch ist es auch. Ohne der Tiefenbuffer müssten wir die Körper alle von hinten nach vorne zeichnen, was bei großen Welten ein nicht zu unterschätzendes organisatorisches Problem ergeben würde.


Abbildung 9: Ein Grafikfehler, der ohne Z-Buffer auftreten kann

Der Tiefenbuffer speichert für jeden Pixel die Tiefe des Pixels auf der Z-Achse. Wenn wir jetzt ein weiterentferntes Objekt zeichnen, überprüft der Tiefenbuffer zuerst, ob sich an dieser Stelle schon ein Pixel befindet. Nur wenn das Objekt sich auch wirklich davor befindet wird der Pixel überzeichnet. Hier ein Bild des Beispielprojekts, bei dem der Z-Buffer ausgeschaltet ist:

Obwohl sich der zweite Würfel hinter dem anderen befindet, wird er davor gezeichnet. Bei angeschaltetem Z-Buffer wäre das nicht passiert!

Vor dem Rendern  

Bevor wir zu dem Code kommen, der unseren Würfel auf den Bildschirm bringt, will ich noch kurz zwei weitere Änderungen erklären:

With p_D3DDevice
  .RenderState.Lighting = False
  .RenderState.ZBufferEnable = True

  .Transform.View = Matrix.LookAtLH(New Vector3(0, 0, -15), _
                                    New Vector3(0, 0, 0), _
                                    New Vector3(0, 1, 0))
  .Transform.Projection = Matrix.PerspectiveFovLH(Math.PI / 4, 4 / 3, 1, 100)
  .Transform.World = Matrix.Identity
End With

Listing 8

Dieser Teil folgt direkt hinter dem Erstellen des Device. Zuerst wird Lighting ausgeschaltet, da wir im Moment kein Lighting verwenden. Danach folgt das Anschalten des Z-Buffers, damit wir die weiter oben genannten Probleme umgehen können. Anschließend werden die View- und die Projectionmatrix eingestellt (und von dort an unverändert gelassen), die Worldmatrix erhält nur einen Standardwert.

Ein weiterer neuer Teil sind die Funktionen UpdateFrame und RenderFrame. UpdateFrame stellt die verschiedenen Matrizen für die beiden Würfel ein und zählt nebenbei die Geschwindigkeit unseres Programms in Frames per Second (FPS), also Bildern in Sekunden. RenderFrame zeichnet die beiden Würfel auf den Bildschirm. Diese beiden Funktionen werden dann in einer Endlosschleife immer wieder aufgerufen. Dabei wird jedesmal der Würfel etwas weitergedreht und das Bild neu gezeichnet. Die FPS geben in diesem Sinn also an, wie oft die Render-Funktion in einer Sekunde aufgerufen wird.

Public Sub UpdateFrame()
  Static cubeangle As Single
  Static lastFrameUpdate As Integer

  Try
    cubeangle += CSng((Environment.TickCount - lastFrameUpdate) / 1000)
    If cubeangle >= Math.PI * 2 Then cubeangle = 0
    p_MatCube1 = Matrix.Multiply(Matrix.RotationY(cubeangle), _
                                Matrix.RotationX(cubeangle))

    p_MatCube2 = Matrix.Multiply(Matrix.RotationY(cubeangle), _
                                Matrix.Translation(-5, 0, 0))
    p_MatCube2 = Matrix.Multiply(p_MatCube2, Matrix.RotationY(cubeangle))

    If (Environment.TickCount() - p_LastFPSCheck >= 1000) Then
      p_LastFPSCheck = Environment.TickCount()
      p_FPS = p_FrameCount
      Console.WriteLine(p_FPS)
      p_FrameCount = 0
    End If
    p_FrameCount += 1
  Catch
  Finally
    lastFrameUpdate = Environment.TickCount
  End Try
End Sub

Listing 9

Die FPS-Zählroutine ist einfach zu verstehen. Bei jedem Update wird ein Zähler um eins nach oben gesetzt und kontrolliert wieviel Zeit seit der letzten Ausgabe vergangen ist. Nach einer Sekunde wir der Counter ausgegeben und p_LastFPSCheck die aktuelle Zeit gesetzt.

Die Render-Funktion  

Die RenderFrame-Funktion zeichnet unsere beiden Würfel mit dem Device-Objekt auf den Bildschirm.

Public Sub RenderFrame()
  Try
    If Not Init Then Return
    With p_D3DDevice
      .Clear(ClearFlags.ZBuffer Or ClearFlags.Target, Color.Black, 1, 0)
      .BeginScene()

      .SetStreamSource(0, p_vbCube, 0)
      .VertexFormat = CustomVertex.PositionColored.Format
      .Transform.World = p_MatCube1
      .DrawPrimitives(PrimitiveType.TriangleList, 0, 12)

      .Transform.World = p_MatCube2
      .DrawPrimitives(PrimitiveType.TriangleList, 0, 12)

      .EndScene()
      .Present()
    End With
  Catch
  End Try
End Sub

Listing 10

Die Render-Funktion hat eigentlich immer den gleichen Rahmen: zuerst muss die Clear-Funktion aufgerufen werden, die das alte Bild und den Z-Buffer löscht und mit einer Hintergrundfarbe füllt (hier einfach nur Schwarz). Mit BeginScene wird dann das Rendern des Bilds begonnen, EndScene schließt dieses ab. Present hat dann schließlich nur die Aufgabe, das (im Speicher) gezeichnete Bild auf den Bildschirm zu bekommen.

Der restliche Code ist auch nicht besonderns kompliziert: mit SetStreamSource machen wir unserem Device bekannt, dass wir gerne den Vertexbuffer als Quelle für unseren Würfel verwenden würden. Die VertexFormat-Eigenschaften zeigt dem Device-Objekt, welches Format unsere Vertices haben. Dann kommt der spannende Teil: über Transform.World setzen wir unsere Worldmatrix und rendern dann den Würfel. Die Parameter der DrawPrimitives-Funktion sollten klar sein: TriangleList erzählt dem Device, dass wir eine Dreieckliste verwenden wollen, startVertex bestimmt von wo aus im Vertexbuffer Direct3D die Vertices nehmen soll und primitiveCount gibt an wieviel Polygone gezeichnet werden sollen. Die selbe Nummer ziehen wir dann nocheinmal für den zweiten Würfel ab, so dass diese Grafik entsteht:


Abbildung 10: Der angezeigt Würfel

Verwendung der Engine  

Das Verwenden der Engine ist eigentlich ganz einfach. Wir müssen nur UpdateFrame und RenderFrame in einer Endlosschleife aufrufen. Um ganz auf der Höhe der Zeit zu sein, habe ich die Endlosschleife noch in einen anderen Thread verpackt (so können wir uns das "hässliche" DoEvents sparen). So kann sich die Form im Hauptthread um Clicks und sonstiges kümmern, während das Rendern von einem anderen übernommn wird.

' ...
  If engine.Init Then running = True Else Me.Close()

  Dim t As New Threading.Thread(AddressOf Render)
  t.Start()
End Sub

Private Sub Render()
  Do While running
    Try

      engine.UpdateFrame()
      engine.RenderFrame()
    Catch stopexp As Threading.ThreadAbortException
      Return
    End Try
  Loop
End Sub

Listing 11

Das Beispielprojekt zu diesem Tutorial herunterladen [11243 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.