Die Community zu .NET und Classic VB.
Menü

OpenGL in Visual Basic - Dynamische 3D-Welten

 von 

Grundlagen 

Willkommen in den unendlichen Weiten des 3D-Raumes. Was wir jetzt angehen gehört bereits zu den Grundlagen echter 3D-Spiele. Was benötigt so ein Spiel eigentlich? Ganz klar: Eine Welt, in der unser Spieler frei umherlaufen kann!
Diese Aufgabe teilen wir in zwei Teile:

  • Erstellen eines Datenformates, mit dem wir unsere Welt beschreiben und das wir zur Laufzeit einlesen können.
  • Bewegen des Spielers in dieser Welt.


Abbildung 1: Es wird interessant: Eine 3D-Welt und wir sind mitten drin.

Erstellen einer externen Datei mit Weltdaten  

Schon haben wir ein Problem. Wie soll eine solche Datei aussehen? Es gibt in den Weiten der 3D-Programmierung dazu keinen Standard. Jeder Entwickler eines 3D-Modellers kreiert da munter sein eigenes Format, möglichst absolut nicht kompatibel mit den Produkten der Konkurrenz. Davon lassen wir uns aber nicht beirren, ganz im Gegenteil: Wir erzeugen ebenfalls unser eigenes Format. Dazu habe ich eine Textdatei ausgewählt, da diese leicht zu editieren ist. Da unsere 3D-Welt im Prinzip nur aus Dreiecken (Vertizen) besteht, enthält unsere Datei auch nur Dreiecke. Die Datei hat daher einen recht einfachen Aufbau den Sie später nach Belieben verkomplizieren dürfen. In der ersten Zeile steht die Anzahl der definierten Dreiecke. Sodann folgen Blöcke von 3 Zeilen mit jeweils 5 Werten. Das sind die X-, Y- und Z-Koordinaten eines Punktes vom Dreieck sowie die beiden Koordinaten der Textur. Anschließend folgt noch eine Zeile mit der Indexnummer der verwendeten Textur. Das Laden der Texturen habe ich der Einfachheit halber noch fest im Quellcode gelassen. Da Sie das schlichte Prinzip solcher Weltdateien sicherlich sofort verstehen, wird es Ihnen keine Mühe machen Texturen, Lichter etc. selbst in die Beschreibungen einzufügen.

Zur Ablage der Daten im Speicher definieren wir unsere eigenen Datentypen. Ein Vertex-Typ mit den fünf Werten eines Punktes. Ein Dreieck (Triangel-Typ) der ein Array mit je drei Punkten (Vertex-Typ) enthält sowie den Index der zugehörigen Textur. Zum Schluss habe ich noch einen Sektor-Typ entworfen. Dieser enthält einen Zeiger auf die Dreiecke und die Anzahl verwendeten Dreiecke.

Private Type TpVertex 'Vertex-Struktur
   X As GLfloat 'X-Koordinate
   Y As GLfloat 'Y-Koordinate
   Z As GLfloat 'Z-Koordinate
   U As GLfloat 'U-Koordinate (Textur)
   V As GLfloat 'V-Koordinate (Textur)
End Type
 
Private Type TpTriangle 'Dreieck-Struktur
   Vertex(2) As TpVertex  'Array mit 3 Punkten
   T As Integer 'Index der verwendeten Textur
End Type
 
Private Type TpSector 'Sektor-Struktur
   Sct_NummTriangle As Integer 'Anzahl der Dreiecke im Sektor
   Triangle() As TpTriangle 'Zeiger auf ein Array mit Dreiecken
End Type
 
Private Sektor(0) As TpSector 'Array für einen Sektor

Listing 1: Benötigte Datentypen

Zum Einlesen der Daten erzeugen wir eine neue Prozedur LoadWorld:

Public Sub LoadWorld(File As String)
   Dim I, J As Integer 'Zählvariablen
   Dim F As Long 'Dateinummer
   Dim Zeile As String 'Einzelzeile aus der Datei
   Dim Segment() As String 'einzelne Anweisung / Koordinate in der Zeile
   Dim AnzDreieck As Integer 'Anzahl der Dreiecke in der Datei
 
   'Datei öffnen
   F = FreeFile
   Open File For Input Shared As #F
 
   '1. Zeile lesen
   Line Input #F, Zeile
   Segment = Split(Zeile, " ") 'Space als Trennzeichen
   'hier in der 1. Zeile die Anzahl der Dreiecke
   If Segment(0) = "NUMTRIANGLES" Then
      AnzDreieck = Val(Segment(1))
      ReDim Sektor(0).Triangle(AnzDreieck) 'Speicher für Dreieck-Koordinaten
      Sektor(0).Sct_NummTriangle = AnzDreieck 'Anzahl der Dreiecke ablegen
      I = 0
      J = 0
      Do 'Alle Dreiecke einlesen
         Line Input #F, Zeile 'Zeile lesen
         Zeile = Trim(Zeile) 'führende Spaces abschneiden
         'Leere Zeilen und Kommentare (beginnen mit /) ignorieren
         If Len(Zeile) > 0 And Left(Zeile, 1) <> "/" Then
             Do
                Zeile = Replace(Zeile, "  ", " ") 'entfernen mehrfacher Spaces
             Loop Until InStr(1, Zeile, "  ") = 0
             If J < 3 Then
                Segment = Split(Zeile, " ") 'in Punkte zerlegen
                'Punkte für Dreieck und Texturen in Struktur speichern
                Sektor(0).Triangle(I).Vertex(J).X = Val(Segment(0))
                Sektor(0).Triangle(I).Vertex(J).Y = Val(Segment(1))
                Sektor(0).Triangle(I).Vertex(J).Z = Val(Segment(2))
                Sektor(0).Triangle(I).Vertex(J).U = Val(Segment(3))
                Sektor(0).Triangle(I).Vertex(J).V = Val(Segment(4))
                J = J + 1
             Else
                Sektor(0).Triangle(I).T = Val(Trim(Zeile)) 'Texturindex merken
                J = 0 'Punktezähler zurücksetzen
                I = I + 1 'nächstes Dreieck
             End If
         End If
      Loop Until I >= AnzDreieck
   End If
End Sub

Listing 2: Prozedur LoadWorld

Das Ganze hat mit OpenGL direkt eigentlich nichts zu tun, sollte aber dem interessierten Einsteiger eine Grundlage zur Erstellung eigener Datenformate liefern.

Bewegen in einer 3D-Welt  

Kommen wir wieder zu OpenGL zurück. Wie bewegen wir uns nun in der Welt? Vielleicht sollten wir nach jeder Bewegung des Spielers den Sichtbereich von dessen Position aus entsprechend neu berechnen und ausgeben. Das wäre ein unglaublicher Aufwand und dementsprechend langsam. Sehen wir es doch mal anders herum. Wenn der Spieler sich nach links dreht, dann drehen wir in Wirklichkeit einfach die gesamte Welt nach rechts. Der Aufwand dazu ist gering denn es genügt ein einziger Aufruf der Funktion glRotatef. Das gleiche tun wir mit Bewegung. Wenn der Spieler nach vorne geht bewegen wir die Welt zurück und umgekehrt. Praktischerweise bleibt der Spieler so immer im Koordinatenursprung der Welt stehen. Mit diesem Wissen können wir seine Position mit einfacher Trigonometrie am Einheitskreis errechnen. Darauf möchte ich hier nicht weiter eingehen denn es würde den Rahmen des Artikels sprengen. Den interessierten Leser verweise ich daher auf Wikipedia und auf das Mathematik-Lehrbuch seiner Wahl. Die benötigten Werte legen wir in einem eigenen Typ Kamera ab.

Private Type TpKamera
   X As GLfloat 'X-Position
   Y As GLfloat 'Y-Position
   Z As GLfloat 'Z-Position
   DY As GLfloat 'Drehwinkel um Y-Achse
End Type
Public Kamera As TpKamera

Listing 3: Kamera-Typ

Die Berechnung der neuen Position der Welt erledigen wir direkt im KeyDown-Ereignis:

'Kamera.DY ist keine Position sondern der Winkel, um den gedreht werden soll
'Drehung Links
If KeyCode = vbKeyLeft Then
  Kamera.DY = Kamera.DY + 1.5
End If
'Drehung Rechts
If KeyCode = vbKeyRight Then
  Kamera.DY = Kamera.DY - 1.5
End If
'Welt entgegen der Bewegung der Kamera verschieben
'PiToRad = Konstante zur Umrechnung Grad - Radiant
If KeyCode = vbKeyUp Then
  'X-Position im Einheitskreis = Sin(Y)
  Kamera.X = Kamera.X + Sin(Kamera.DY * PiToRad) * 0.04
  'Z-Position im Einheitskreis = Cos(Y)
  Kamera.Z = Kamera.Z + Cos(Kamera.DY * PiToRad) * 0.04
End If
If KeyCode = vbKeyDown Then
  Kamera.X = Kamera.X - Sin(Kamera.DY * PiToRad) * 0.04
  Kamera.Z = Kamera.Z - Cos(Kamera.DY * PiToRad) * 0.04
End If

Listing 4: KeyDown-Ereignis

Nun müssen wir in der Sub Main nur noch die Welt entsprechend bewegen und anschließend die eingelesenen Daten aus der Struktur an OpenGL übergeben.

Kamera.Y = -0.35 'wir bewegen uns nicht auf/ab und fixieren daher Y
glRotatef 360 - Kamera.DY, 0, 1, 0 'Welt in entgegengesetzter Richtung der Kamera drehen
glTranslatef Kamera.X, Kamera.Y, Kamera.Z  'Welt verschieben

'Darstellung der eingelesenen Daten in einer Schleife
For I = 0 To Sektor(0).Sct_NummTriangle - 1 'alle Dreiecke abarbeiten
   'Auswahl der gewünschten Textur
   glBindTexture GL_TEXTURE_2D, Texture(Sektor(0).Triangle(I).T)
   glBegin bmTriangles
      For J = 0 To 2 '3 Punkte je Dreieck
          With Sektor(0).Triangle(I).Vertex(J)
               glTexCoord2f .U, .V: glVertex3f .X, .Y, .Z
          End With
      Next
   glEnd
Next

Listing 5: Die Welt wird bewegt

Es geht weiter  

Geschafft! Um das alles auch anschaulich zu machen ist in unserem Beispielprojekt eine kleine Welt aus 36 Dreiecken und 3 Texturen zusammengesetzt. Die Berechnung der Bewegungen ist darauf ausgerichtet, dass es entlang der Y-Achse keine Bewegung gibt und entsprechend vereinfacht. Eine völlig freie Bewegung in einem dreidimensionalen Raum ist etwas aufwendiger und wird in einem späteren Kapitel genauer beleuchtet.
Im Kapitel 8 beschäftigen wir uns zunächst mit Displaylisten. Mit Hilfe dieser Listen können wir uns viele Zeichenanweisungen in der Hauptschleife ersparen und somit auch in VB umfangreichere Welten erstellen, ohne die Geschwindigkeit in den Keller zu treiben.

Beispielprojekt  

Beispielprojekt zum Tutorial [286652 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.