OpenGL in Visual Basic - Dynamische 3D-Welten
von Hans Henning Klein
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.
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.