OpenGL in Visual Basic - Texture Mapping II
von Hans Henning Klein
Grundlagen
Nachdem wir im letzten, reichlich umfangreichen, Kapitel gelernt haben, wie man Bilder auf Flächen klebt, gehen wir jetzt noch einen Schritt weiter. Wir werden Bilder auf Flächen bewegen.
Wozu soll das nur gut sein? Ganz einfach: Stellen Sie sich vor, sie haben für ein 3D-Spiel ein schönes Haus entworfen. Innerhalb des Spiels kann sich der Spieler frei im Haus bewegen. Was aber, wenn der Spieler vor einem Fenster stehen bleibt? In der Mehrzahl der Fälle wird er irgend ein Straßenszenario sehen, das in Wirklichkeit nur eine Textur auf einer Fläche ist. Damit das Ganze aber realistischer erscheint, wäre es doch schön, wenn am blauen Himmel Wolken vorbei ziehen würden. Das könnten wir jetzt natürlich lösen, in dem wir für das Fensterglas zwei Flächen definieren. Beide Flächen könnten zusammen durch bewegen und verformen den Eindruck vermitteln, es ziehen Wolken vorbei. Das läuft in etwa so wie eine endlose Filmschleife mit 2 gleichen Bildern, von denen immer nur Teile zu sehen sind. Der Aufwand dafür wäre aber unverhältnismässig gross und zum Glück auch unnötig.
In OpenGL gibt es dafür eine wesentlich einfachere Methode - wir bewegen nur die Textur auf der Fläche des Fensterglases. Dazu müssen wir die aktuelle Matrix wechseln. Bisher haben wir alles in der Modell-Matrix getan. OpenGL kennt aber noch weitere Matrizen. Eine davon ist die Textur-Matrix. Sobald wir in diese Matrix gewechselt haben, beziehen sich alle Anweisungen auf die aktuelle(n) Textur(en). Das einzige, was wir also tun müssen, ist in die Texur-Matrix zu wechseln, die Textur zu bewegen und wieder zurück in die Modell-Matrix zu gehen. Sobald keine Textur mehr bewegt werden soll, müssen wir wieder in die Textur-Matrix wechseln, die Bewegung aufheben und wieder in die Modell-Matrix zurückkehren.
Abbildung 1: Weitere Spielereien mit Texturen.
Beispiel animiertes Fenster
Im folgenden Beispiel erschaffen wir einen Fensterrahmen und eine Fensterscheibe. Dem Rahmen geben wir eine schöne Holz-Textur und auf der Scheibe sollen Wolken vorbeiziehen. Dazu müssen natürlich die Texturen in der Prozedur CreateGLWindow geladen werden.
Call LoadTextureS(App.Path & "\fenster.bmp", 1) 'laden und erzeugen einer Textur Call LoadTextureS(App.Path & "\wolkene.bmp", 2) 'laden und erzeugen einer weiteren Textur
Listing 1: Laden der Fenstertexturen
Dann ändern wir die Sub Main und kreieren die beiden beschriebenen Flächen. Beide Fläche sind auf der X und Y-Achse zentriert und liegen auf der Z-Achse in der gleichen Ebene. Sie unterscheiden sich nur durch ihre Abmessungen. Das sollte Ihnen nach den vergangenen Lektionen keine Schwierigkeiten bereiten. Wichtig sind die Wechsel zwischen den Matrizen mit der Funktion glMatrixMode. Die Variable tGlid dient nur dem Hochzählen der Bewegung in sehr kleinen Schritten.
Public Sub Main() Static tGlid As GLfloat 'Schritte, um die eine Textur verschoben wird Dim frm As Form PrgRun = True 'Flag = das Programm läuft Set frm = New Form1 'Fenster für OpenGL-Ausgabe frm.ScaleMode = vbPixels 'das Fenster muss zwingend in Pixel bemessen werden If CreateGLWindow(frm, 640, 480, 16) Then 'Fenster initialisieren Do 'Endlosschleife, in der das Fenster laufend gelöscht und neu aufgebaut wird. 'Die Laufzeit dieser Schleife ist ausschlaggebend, wieviele Objekt gezeichnet werden können glClear clrColorBufferBit Or clrDepthBufferBit ' löscht das Fenster und den Tiefenpuffer glLoadIdentity 'setzt die aktuelle Modell-Matrix zurück glTranslatef -1#, 0#, -8# 'Zeichenpunkt zurücksetzen glBindTexture GL_TEXTURE_2D, Texture(1) 'Auswahl der gewünschten Textur glBegin bmQuads 'Fensterrahmen glTexCoord2f 1#, 1#: glVertex3f 2#, 2#, 1# 'Oben Rechts glTexCoord2f 0#, 1#: glVertex3f -2#, 2#, 1# 'Oben Links glTexCoord2f 0#, 0#: glVertex3f -2#, -2#, 1# 'Unten Links glTexCoord2f 1#, 0#: glVertex3f 2#, -2#, 1# 'Unten Rechts glEnd 'Ende des Fensterrahmens glMatrixMode GL_TEXTURE 'Umschalten in die Textur-Matrix glLoadIdentity 'setzt die aktuelle Textur-Matrix zurück glTranslatef tGlid, 0, 0 'verschiebt die aktuelle Textur entlang der X-Achse 'OpenGL setzt automatisch den 'überstehenden' Teil wieder an glMatrixMode GL_MODELVIEW 'und zurück in den Modell-Matrix glBindTexture GL_TEXTURE_2D, Texture(2) 'Auswahl der gewünschten Textur glBegin bmQuads 'Fensterscheibe glTexCoord2f 1#, 1#: glVertex3f 1.335, 1.49, 1# 'Oben Rechts glTexCoord2f 0#, 1#: glVertex3f -1.3, 1.49, 1# 'Oben Links glTexCoord2f 0#, 0#: glVertex3f -1.3, -1.4, 1# 'Unten Links glTexCoord2f 1#, 0#: glVertex3f 1.335, -1.4, 1# 'Unten Rechts glEnd 'Ende der Fensterscheibe glMatrixMode GL_TEXTURE 'Umschalten in die Textur-Matrix glLoadIdentity 'setzt die aktuelle Textur-Matrix zurück glMatrixMode GL_MODELVIEW 'und zurück in die Modell-Matrix SwapBuffers (frm.hDC) 'Puffer tauschen (Double Buffering) DoEvents tGlid = tGlid + 0.00005 'der Wert zum Verschieben ist Rechnerabhängig! 'Eleganter wäre es, die Verschiebung anhand der verstrichenen Zeit zu berechnen. Loop While PrgRun 'Programm nur beenden, wenn PrgRun = False 'PrgRun ist Global definiert und wird im KeyDown-Ereignis von Form1 bei Drücken von Escape gesetzt. 'alles freigeben und Programm beenden If hrc <> 0 Then 'hatten wir einen Gerätekontext für OpenGL? wglMakeCurrent 0, 0 'Freigeben des Gerätekontexts wglDeleteContext (hrc) 'Freigeben des Renderingkontexts End If Unload frm Set frm = Nothing End End If End Sub
Listing 2: Sub Main für ein animiertes Fenster
Wie Sie sehen, ist der Aufwand minimal, der Effekt im Verhältnis aber gewaltig. Natürlich lassen sich Texturen ebenso wie Flächen auch in alle anderen Richtungen bewegen. Und weil es so schön ist, setzen wir noch einen drauf.
UV-Mapping
Gesetzt den Fall, wir möchten auf einer Fläche eine animierte Textur darstellen, also so etwas wie ein animiertes GIF. Jetzt könnten wir dazu natürlich eine größere Zahl an einzelnen Bildern in ebenso viele Texturen laden und diese dann nacheinander anzeigen. Sind wir mal ehrlich: Das ist wieder jede Menge Arbeit für die Katz - gerade wenn es um 3D-Grafik geht, ist jede überflüssige Anweisung in der Hauptschleife eine zu viel.
Auch hier hilft uns OpenGL ganz entscheidend weiter. Wir benötigen nur eine Grafik, in der alle Einzelbilder enthalten sind. Um, wie im folgenden Beispiel vorgestellt, einen Zähler auf eine Fläche zu bekommen, sagen wir OpenGL einfach, an welcher Position unser Einzelbild steht. Das Ganze nennt sich dann UV-Mapping. Dazu verwenden wir eine Grafik mit den Zahlen von 1 - 8. Für diese Grafik gelten wieder die üblichen Einschränkungen: Länge und Breite müssen aus 2er Potenzen bestehen. Aber diese muss an sich nicht quadratisch sein, nur die verwendeten Einzelbilder müssen diese Bedingung erfüllen. So haben wir eine Grafik, die 256 * 32 Pixel groß ist, jedes Einzelbild aber nur 32 * 32 Pixel belegt.
Abbildung 2: Einzelbilder für einen Zähler
Jetzt müssen wir OpenGL nur noch sagen, wo die Einzelbilder stehen. Dazu ist ein minimaler Rechnaufwand nötig:
PicLength = 1 / 8
Das Bild besteht aus 8 Einzelbildern, eines hat somit 1/8 der Breite des Gesamtbildes
PicPos = Round(Pic) * PicLength
Errechnen, an welcher Position das Einzelbild beginnt.
Durch einsetzen der Round()-Funktion wird PicPos immer um genau 1 hochgezählt. Dadurch schaltet der Zähler genau von einem zum anderen Einzelbild. Entfernen Sie die Round()-Funktion und Sie haben eine Laufschrift!
Beispiel Zähler
Erweitern Sie die Sub Main einfach um folgenden Code:
Dim PicPos As GLfloat 'Position des Einzelbildes in der Gesamtgrafik Dim Piclength As GLfloat 'Breite einer Einzelgrafik Dim Pic As GLfloat 'Zähler für den Counter glLoadIdentity 'setzt die aktuelle Modell-Matrix zurück glTranslatef 2#, 0#, -8# 'Zeichenpunkt setzen Piclength = 1 / 8 'das Bild besteht aus 8 Einzelbildern PicPos = Round(Pic) * Piclength 'errechnen, an welcher Position das Einzelbild steht glBindTexture GL_TEXTURE_2D, Texture(3) 'Auswahl der gewünschten Textur glBegin bmQuads 'Beginn Zähler glTexCoord2f PicPos + Piclength, 1#: glVertex3f 0.5, 0.5, 1# 'Oben Rechts glTexCoord2f PicPos, 1#: glVertex3f -0.5, 0.5, 1# 'Oben Links glTexCoord2f PicPos, 0#: glVertex3f -0.5, -0.5, 1# 'Unten Links glTexCoord2f PicPos + Piclength, 0#: glVertex3f 0.5, -0.5, 1# 'Unten Rechts glEnd 'Ende des Zählers
Listing 3: Sub Main für einen Zähler
In der Hauptschleife sollte ausserdem noch die Variable Pic erhöht werden:
Pic = Pic + 0.001
Auch hier wäre die bessere Lösung eine Erhöhung anhand der verstrichenen Zeit, damit die Geschwindigkeit nicht hardwareabhängig ist.
Es geht weiter
Damit hätten wir das umfangreiche Thema Texture Mapping erst einmal erledigt. Es bleibt ganz Ihrer Kreativität überlassen, was Sie aus diesen Möglichkeiten machen. Im Kapitel 5 werden wir dann lernen, wie Licht in unsere Welt kommt.
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.