Die Community zu .NET und Classic VB.
Menü

OpenGL in Visual Basic - Texture Mapping I

 von 

Vorbereitungen 

Wenn Sie mit dem bisher erarbeiteten Wissensstand anstatt einfacher Farben ein komplexeres Bild erstellen wollten, müßten Sie dieses aus hunderten von bunten Dreiecken zusammensetzen. Das wäre ein völlig unmöglicher Aufwand - einmal ganz davon abgesehen, dass Ihr Computer und Ihre Grafikkarte schnell völlig ausgelastet wären und wirklich komplexe Welten überhaupt nicht mehr zu rendern wären. Diesem Missstand lässt sich mit Texturen schnell Abhilfe schaffen. Man klebt einfach ein Bild auf die Oberfläche und schon sieht das Ganze (fast) real aus. Natürlich geht das nun wieder nicht mit ein paar Codezeilen, aber wir werden das schon schaffen.

Zunächst einmal löschen wir aus der Sub Main die Pyramide heraus und arbeiten der Einfachheit halber nur mit dem Würfel weiter. Des weiteren platzieren wir auf der Form1 eine Picturebox. Diese sollte unsichtbar sein, nach Pixeln bemessen werden und AutoSize sowie AutoRedraw sollten an sein. Die Picturebox werden wir brauchen, um die Grafik in den Speicher zu laden.

Eine weitere wichtige Vorraussetzung für ein erfolgreiches Texture Mapping ist die Größe der Grafik. Es können ausschließlich genau quadratische Grafiken verwendet werden. Außerdem muss die Seitenlänge der Grafik eine 2er Potenz sein. So sind also nur Grafiken mit folgenden Ausmaßen möglich: 2,4,8,16,32,64,128,256,512,1024 etc. Hierbei sollte die Größe der Textur sorgfältig gewählt werden, denn zu große Grafiken verlangsamen die Darstellung ganz enorm.


Abbildung 1: Ein bemalter Würfel ist doch gleich viel interessanter.

Texture Mapping  

Texture Mapping ist bereits eine etwas kompliziertere Funktion in OpenGL. Grundsätzlich kann mit OpenGL aber jede Grafik an eine beliebige Fläche geklebt werden. OpenGL erwartet die Grafikdaten als eine Bitmap mit 24-Bit-Farbtiefe. Um uns die Arbeit in VB zu erleichtern und vor allem keine umfangreichen Funktionen für die Umwandlung von .jpg oder .gif Grafiken ins .bmp Format zu schreiben, gehen wir einen kleinen Umweg. Die Grafik wird in eine unsichtbare Picturebox geladen und von dort per API in eine BITMAP-Struktur geschrieben. Trotzdem bleibt da noch einiges zu tun:

Zuerst fügen wir im Deklarationsbereich des Moduls ein neues Array ein:

Public Texture(15) As GLuint 'Handles der Texturen

Private Declare Function GetObjectA Lib "gdi32" ( _
  ByVal hObject As Long, _
  ByVal nCount As Long, _
  ByRef lpObject As Any) As Long
  
Type BITMAP '14 bytes
    bmType As Long
    bmWidth As Long
    bmHeight As Long
    bmWidthBytes As Long
    bmPlanes As Integer
    bmBitsPixel As Integer
    bmBits As Long
End Type

Listing 1: Deklarationen

Mit diesem Array könnten wir nun 16 Texturen speichern. Das Array wird verwendet, um die Zeiger auf die tatsächlichen Texture-Strukturen aufzunehmen. Diese Zeiger werden von OpenGL zurückgegeben.
Die API-Funktion wird zum Erzeugen der Texturen benötigt und die Struktur ist der oben genannte BITMAP-Typ zum Aufnehmen der Textur.

Als nächstes benötigen wir eine Prozedur, um die Grafiken einzulesen. Eine Möglichkeit wäre, alle Daten aus der Picturebox Punkt für Punkt auszulesen und in eine Bitmap-Struktur zu schreiben. Es geht aber auch viel effizienter:

Public Sub LoadTexture(ByVal Filename As String, ByVal Index As Long)
   Dim bmp As BITMAP
   'Vorraussetzungen:
   'Die Grafik muß quadratisch sein!
   'Die Seitenlänge der Grafiken müssen 2er Potenzen entsprechen:
   '16,32,64,128,256,512,1024 Pixel etc.
   'Durch den Umweg über die Picturebox sind aber auch Formate wie jpg oder gif möglich.
   'Die Autosize-Eigenschaft der Picturebox muß auf True stehen!
 
   Form1.Picture1.Picture = LoadPicture(Filename) 'Bild laden
 
   'GetObjectA gibt Informationen über die im Aufruf übermittelte Bitmap der
   'Picturebox zurück und speichert diese in der bmp-Struktur.
   'Es wird also eine Bitmap aus den Daten der Picturebox im Speicher erstellt.
   If GetObjectA(Form1.Picture1.Picture.Handle, LenB(bmp), bmp) Then
        glGenTextures 1, Texture(Index)  'kreiert eine Textur
        glBindTexture glTexture2D, Texture(Index) 'Zuweisung: die Textur ist 2D
        'Generieren der eigentlichen Texture in OpenGL
        glTexImage2D glTexture2D, 0, 3, bmp.bmWidth, bmp.bmHeight, 0, tiBGRExt, GL_UNSIGNED_BYTE, ByVal bmp.bmBits
        'Der 1. Parameter besagt wieder 2D Textur
        'Der 2. Parameter gibt den Grad der Details an, ist aber vorerst einmal unwichtig
        'Der 3. Parameter gibt die Art der Farbkanäle an, 3 = RGB.
        'Parameter 4 und 5 sind Breite und Höhe der Textur
        'Der 6. Parameter gibt die Rahmenbreite an, vorerst aber mal 0.
        'Parameter 7 und 8 geben Farbformat (RGB) und Art der Daten (Unsigned Byte = 0 - 255) an.
        'Der letzte Parameter übergibt schlußendlich die eigentliche Textur.
 
        'Diese Zeilen legen fest, welcher Filter verwendet werden soll:
        glTexParameteri glTexture2D, tpnTextureMinFilter, GL_LINEAR
        glTexParameteri glTexture2D, tpnTextureMagFilter, GL_LINEAR
        'tpnTextureMinFilter besagt, mit welchem Filter die Grafik zusammengedrückt werden soll.
        'tpnTextureMagFilter besagt, mit welchem Filter die Grafik auseinandergezogen wird.
        'Der Parameter GL_LINEAR erzeugt das beste Ergebnis, belastet aber den Prozessor.
        'Möglich wäre auch GL_NEAREST. Das entlastet zwar die Hardware, sieht aber schnell recht pixelig aus.
        LoadTexturePic = True
   End If
End Sub

Listing 2: Texturen laden I

Natürlich müssen wir die Texturen auch in Auftrag geben. Damit das richtig funktioniert, fügen wir den Aufruf dazu in der bereits bekannten Prozedur CreateGLWindow ein.

'...
   frm.Show 'Fenster anzeigen
   Call LoadTexture(App.Path & "\mauer.bmp", 1) 'laden und erzeugen einer Textur
   Call LoadTexture(App.Path & "\wolken.jpg", 2) 'laden und erzeugen einer weiteren Textur
   glEnable glcTexture2D 'Einschalten des Texture Mappings
'...

Listing 3: Aufruf von LoadTexture

In diesem Beispiel wurden Aufrufe für zwei verschiedene Texturen eingefügt und anschließend mit der Anweisung glEnable aktiviert.

Darstellung  

Damit wären die Vorarbeiten abgeschlossen. Nun müssen wir in unserer Hauptschleife in der Sub Main nur noch einen Würfel einfügen und die Texturen zuweisen. Wie Sie aus dem folgenden Quellcode ersehen können, werden die Eckpunkte der Textur mit den Eckpunkten der aktuellen Fläche verbunden. Um mehrere Texturen zu verwenden ist es notwendig, die Definition der Flächen und Texturen mit einem glEnd abzuschließen und dann eine neue Textur zu aktivieren. Ein fliegender Wechsel innerhalb einer Auflistung von mehreren Flächen ist nicht möglich. Im folgenden Beispiel erhält der Deckel des Würfels eine andere Textur als alle anderen Flächen. Damit das etwas besser aussieht, drehen wir den Würfel in unterschiedlichen Schritten um alle Achsen. Dies sollte Ihnen aber aus den vorangegangenen Kapiteln vertraut sein.

Kommen wir zu den Koordinaten der Texturen:
Die Anweisung glTexCoord2f gibt in 2D-Parametern an, wie die Textur auf die Fläche geklebt wird. Die linke untere Ecke der Textur trägt dabei die Koordinaten 0/0, die linke obere Ecke die Koordinaten 0/1, die rechte obere Ecke die Koordinaten 1/1 und die rechte untere Ecke die Koordinaten 1/0. Damit wird die Textur normal auf der Fläche aufgespannt.


Abbildung 2: Eckpunkte der Textur

Eine falsche Reihenfolge dieser Koordinaten gibt es nicht, da man damit Texturen z.B. gespiegelt darstellen kann. Geben Sie statt 1 größere Zahlen an, wird die Textur in entsprechend vielen Teilen gekachelt auf der Fläche dargestellt. Dadurch lassen sich z.B. größere Flächen mit einer relativ kleinen Textur füllen.

Public Sub Main()
   Static r4eckX As GLfloat 'Winkel der X-Achse für das Viereck
   Static r4eckY As GLfloat 'Winkel der Y-Achse für das Viereck
   Static r4eckZ As GLfloat 'Winkel der Z-Achse für das Viereck
 
   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 0#, 0#, -6# 'Zeichenpunkt zurücksetzen
 
         glRotatef r4eckX, 1#, 0#, 0# 'Drehen des Würfels um die X Achse
         glRotatef r4eckY, 0#, 1#, 0# 'Drehen des Würfels um die Y Achse
         glRotatef r4eckZ, 0#, 0#, 1# 'Drehen des Würfels um die Z Achse
 
         glBindTexture GL_TEXTURE_2D, Texture(2) 'Auswahl der gewünschten Textur
         glBegin bmQuads 'Deckel eines Würfels
            glTexCoord2f 1#, 1#: glVertex3f 1#, 1#, -1# 'Oben Rechts (Oberseite)
            glTexCoord2f 0#, 1#: glVertex3f -1#, 1#, -1# 'Oben Links (Oberseite)
            glTexCoord2f 0#, 0#: glVertex3f -1#, 1#, 1# 'Unten Links (Oberseite)
            glTexCoord2f 1#, 0#: glVertex3f 1#, 1#, 1# 'Unten Rechts (Oberseite)
         glEnd 'Ende des Würfels
 
         glBindTexture GL_TEXTURE_2D, Texture(1) 'Auswahl der gewünschten Textur
         glBegin bmQuads 'Rest eines Würfels
            glTexCoord2f 1#, 1#: glVertex3f 1#, -1#, 1# 'Oben Rechts (Unterseite)
            glTexCoord2f 0#, 1#: glVertex3f -1#, -1#, 1# 'Oben Links (Unterseite)
            glTexCoord2f 0#, 0#: glVertex3f -1#, -1#, -1# 'Unten Links (Unterseite)
            glTexCoord2f 1#, 0#: glVertex3f 1#, -1#, -1# 'Unten Rechts (Unterseite)
            glTexCoord2f 1#, 1#: glVertex3f 1#, 1#, 1# 'Oben Rechts (Vorderseite)
            glTexCoord2f 0#, 1#: glVertex3f -1#, 1#, 1# 'Oben Links (Vorderseite)
            glTexCoord2f 0#, 0#: glVertex3f -1#, -1#, 1# 'Unten Links (Vorderseite)
            glTexCoord2f 1#, 0#: glVertex3f 1#, -1#, 1# 'Unten Rechts (Vorderseite)
            glTexCoord2f 0#, 0#: glVertex3f 1#, -1#, -1# 'Unten Links (Rückseite)
            glTexCoord2f 1#, 0#: glVertex3f -1#, -1#, -1# 'Unten Rechts (Rückseite)
            glTexCoord2f 1#, 1#: glVertex3f -1#, 1#, -1# 'Oben Rechts (Rückseite)
            glTexCoord2f 0#, 1#: glVertex3f 1#, 1#, -1# 'Oben Links (Rückseite)
            glTexCoord2f 1#, 1#: glVertex3f -1#, 1#, 1# 'Oben Rechts (Links)
            glTexCoord2f 0#, 1#: glVertex3f -1#, 1#, -1# 'Oben Links (Links)
            glTexCoord2f 0#, 0#: glVertex3f -1#, -1#, -1# 'Unten Links (Links)
            glTexCoord2f 1#, 0#: glVertex3f -1#, -1#, 1# 'Unten Rechts (Links)
            glTexCoord2f 1#, 1#: glVertex3f 1#, 1#, -1# 'Oben Rechts (Rechts)
            glTexCoord2f 0#, 1#: glVertex3f 1#, 1#, 1# 'Oben Links (Rechts)
            glTexCoord2f 0#, 0#: glVertex3f 1#, -1#, 1# 'Unten Links (Rechts)
            glTexCoord2f 1#, 0#: glVertex3f 1#, -1#, -1# 'Unten Rechts (Rechts)
         glEnd 'Ende des Würfels
 
         SwapBuffers (frm.hDC) 'Puffer tauschen (Double Buffering)
         DoEvents
 
         'damit es eine laufende Drehung wird, ändern wir mit jedem Schleifendurchlauf alle Winkel
         r4eckX = r4eckX + 0.4
         r4eckY = r4eckY + 0.3
         r4eckZ = r4eckZ + 0.2
 
      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 4: Sub Main

Es geht weiter  

Damit sind schon alle grundlegenden Vorraussetzungen für eine kleine 3D-Welt gegeben. Mit Ihrem jetzigen Kenntnisstand wären Sie z.B. bereits in der Lage, einen kleinen Bildschirmschoner zu entwickeln, der sechs Urlaubsfotos auf einem sich zufällig drehenden Würfel darstellt. Den Bildschirmschoner gibt es zwar bereits, aber wir fangen ja auch gerade erst an, in die faszinierende Welt der 3D-Darstellung einzutauchen. Damit sind die Fähigkeiten des Texture Mappings noch lange nicht erschöpft. Aber bevor Sie sich den weiteren Möglichkeiten zur Darstellung einer möglichst realen Welt in Kapitel 4 stellen, sollten Sie das Ganze einfach mal verinnerlichen und mit dem Beispielprojekt ein wenig herumspielen.

Beispielprojekt  

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