Die Community zu .NET und Classic VB.
Menü

OpenGL in Visual Basic - Licht

 von 

Es werde Licht... 

Licht ist die Grundlage allen Lebens auf unserer Welt. Ohne Licht würde keine Pflanze wachsen, kein Tier oder Mensch leben können. Deshalb ist Licht auch in unserer 3D-Welt unverzichtbar. Was will denn der jetzt nun wieder, werden einige denken. Bisher haben wir unsere Objekte doch wunderbarerweise gesehen. Das ist natürlich richtig. Aber wir sehen nur deshalb etwas, weil OpenGL automatisch ein Licht mit gewissen Standardwerten einfügt, wenn wir nicht explizit etwas anderes sagen. Dass wir gerne unser eigenes Licht verwenden würden, sagen wir OpenGL mit der Anweisung glEnable GL_LIGHTING. OpenGL verwaltet insgesamt 8 Lichtquellen. Diese lassen sich einzeln mit der Anweisung glEnable GL_LIGHTx einschalten, wobei 'x' für Zahlen von 0 bis 7 steht.
Lichtquellen sind in OpenGL nicht sichtbar. Auch wenn das Licht direkt vor Ihren Augen hängen müßte, es bleibt unsichtbar.


Abbildung 1: 3D kommt erst mit Licht richtig zur Geltung.

Arten des Lichts  

OpenGL kennt mehrere Arten von Licht. Dabei besteht jede Lichtquelle aus einer Mischung dieser Lichtarten. Als erstes sei hier das Umgebungslicht (Ambient) genannt. Das Umgebungslicht lässt keine Richtung erkennen, aus der es kommt. Es entsteht durch mehrfache Reflexionen an Wänden und Flächen. Es ist in der Regel sowohl in Räumen als auch in der freien Natur gut vertreten. Als zweites gibt es noch diffuses Licht (Diffuse). Es lässt deutlich erkennen, wo es herkommt. Dem Licht zugewandte Flächen werden deutlich heller dargestellt als andere. Zuletzt gibt es noch Glanz (Specular). Es kommt aus einer bestimmten Richtung und neigt dazu, auch wieder in eine bestimmte Richtung reflektiert zu werden. Dieses Licht lässt sich sehr gut für metallische oder gläserne Oberflächen einsetzen.

Material  

Licht allein macht noch keine realistische Umgebung. Schließlich sieht ein Stück Kohle im gleichen Licht doch deutlich anders aus als ein Diamant. Daher gibt es in OpenGL auch die Möglichkeit, Materialien für Oberflächen zu bestimmen. Materialbeschreibungen bestehen ebenfalls aus den 3 Arten des Lichts und bestimmen, wie die entsprechende Lichtart behandelt wird.

Normalen  

Bevor Sie jetzt vorausdenken - es geht hier nicht um normales und unnormales Licht. Letzteres gibt es nicht mal in OpenGL. Normalen helfen vielmehr bei der Berechnung, wie hell eine Fläche wirklich ist. Dazu muss OpenGL wissen, in welche Richtung diese Fläche überhaupt "zeigt". Die Normale einer Fläche ist also die Richtung, in der die Fläche am meisten Licht reflektiert. Genau genommen ist es ein Zeiger, der senkrecht auf der Oberfläche steht. Ein Zeiger (oder Vektor) wird genau wie ein Punkt dargestellt, wobei die Koordinaten die Richtung (Anstelle der Position) angeben.
Die Grafik verdeutlicht dies:
Der rote und der grüne Pfeil stehen für X- und Y-Achse. Der blaue Pfeil entspricht dann der Normalen und damit der Z-Achse und wird als Vektor mit den Koordinaten 0/0/1 beschrieben.


Abbildung 2: Normalvektor

Praxis  

Um das Ganze ein wenig anschaulich zu machen, verwenden wir mal wieder den guten alten Würfel, der sich dreht. Das Meiste der Sub Main sollte Ihnen nun bereits geläufig sein. Nur die Normalen sind hinzugekommen. Der hier gezeigte Quelltext enthält daher nur den relevanten Bereich.

Public Sub Main()
'...
   glBegin bmQuads 'Ein Würfel
      glColor3f 1, 1, 0 'Gelb
      glNormal3f 0#, 1#, 0# 'Normale der Oberseite zeigt positiv entlang der Y Achse
      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)
      glColor3f 1, 0, 1 'Violett
      glNormal3f 0#, -1#, 0# 'Normale der Unterseite zeigt negativ entlang der Y Achse
      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)
      glColor3f 0, 0, 1 'Blau
      glNormal3f 0#, 0#, 1# 'Normale der Vorderseite zeigt positiv entlang der Z Achse
      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)
      glColor3f 0, 1, 1 'Türkis
      glNormal3f 0#, 0#, -1# 'Normale der Rückseite zeigt negativ entlang der Z Achse
      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)
      glColor3f 0, 1, 0 'Grün
      glNormal3f -1#, 0#, 0# 'Normale der linke Seite zeigt negativ entlang der X Achse
      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)
      glColor3f 1, 0, 0 'Rot
      glNormal3f 1#, 0#, 0# 'Normale der rechten Seite zeigt positiv entlang der X Achse
      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
'...

Listing 1: Sub Main

Dazu kommt noch die Initialisierung des Lichts in der Funkion CreateGLWindow. Hierzu benötigen wir zuerst einmal eine Anzahl von Arrays um die Werte für die Licht- und Materialeigenschaften zu speichern. Dazu initialisieren wir hier einmalig Material- und Lichteigenschaften für alle Flächen und Licht Nummer 1. Dann nur noch Licht einschalten und Licht Nr. 1 aktivieren.

Public Function CreateGLWindow(frm As Form, Width As Integer, Height As Integer, Bits As Integer) As Boolean
   Dim Mt_Diffuse(3) As GLfloat 'Material für diffuses Licht
   Dim Mt_Ambient(3) As GLfloat 'Material für Umgebungslicht
   Dim Mt_Specular(3) As GLfloat 'Material für gespiegeltes Licht
   Dim Mt_Shiness(0) As GLfloat 'Glanz des Materials
 
   Dim Lt_Position(3) As GLfloat 'Position der Lichtquelle
   Dim Lt_Diffuse(3) As GLfloat 'diffuses Licht
   Dim Lt_Ambient(3) As GLfloat 'Umgebungslicht
   Dim Lt_Specular(3) As GLfloat 'Spotlicht / Glanzlicht
 
   'Position der Lichtquelle
   'auf der Z-Achse als positiver Wert = 10 Einheiten vor dem Bildschirm
   Lt_Position(0) = 0#: Lt_Position(1) = 0#: Lt_Position(2) = 10#: Lt_Position(3) = 1#
 
   'Materialeigenschaft für diffuses Licht
   Mt_Diffuse(0) = 0.8: Mt_Diffuse(1) = 0.8: Mt_Diffuse(2) = 0.8: Mt_Diffuse(3) = 1#
 
   'Materialeigenschaft für Umgebungslicht
   Mt_Ambient(0) = 0.3: Mt_Ambient(1) = 0.3: Mt_Ambient(2) = 0.3: Mt_Ambient(3) = 1#
 
   'Materialeigenschaft für Glanz
   Mt_Specular(0) = 1: Mt_Specular(1) = 1: Mt_Specular(2) = 1: Mt_Specular(3) = 1
 
   'Exponent für Glanz
   Mt_Shiness(0) = 50#
 
   'Lichtstärke/farbe für Umgebungslicht
   Lt_Ambient(0) = 0.2: Lt_Ambient(1) = 0.2: Lt_Ambient(2) = 0.2: Lt_Ambient(3) = 1#
 
   'Lichtstärke/farbe für diffuses Licht
   Lt_Diffuse(0) = 0.5: Lt_Diffuse(1) = 0.5: Lt_Diffuse(2) = 0.5: Lt_Diffuse(3) = 0.5
 
   'Lichtstärke/farbe für Spot (Glanz)
   Lt_Specular(0) = 1: Lt_Specular(1) = 1: Lt_Specular(2) = 1: Lt_Specular(3) = 1
 
   Dim pfd As PIXELFORMATDESCRIPTOR ' pfd erklärt Windows, wie das Fenster beschaffen sein soll
   Dim PixelFormat As GLuint ' enthält das Ergebnis vom Versuch, ein Fenster mit den gegebenen Parametern zu erstellen
 
   pfd.cColorBits = Bits 'Farbtiefe
   pfd.cDepthBits = 16 '16 Bit Tiefenpuffer
   'Der Tiefenpuffer enthält die Entfernung eines Pixels zur Kamera (Betrachter).
   'Er verhindert, dass Objekte im Hintergrund beim Zeichnen Objekte im Vordergund überlagern.
   pfd.dwFlags = PFD_DRAW_TO_WINDOW Or PFD_SUPPORT_OPENGL Or PFD_DOUBLEBUFFER
   'PFD_DRAW_TO_WINDOW = das Format muss als Fenster sichtbar sein können
   'PFD_SUPPORT_OPENGL = das Format muss OpenGL unterstützen
   'PFD_DOUBLEBUFFER = das Format muss Double Buffering unterstützen
   pfd.iLayerType = PFD_MAIN_PLANE 'Die Hauptebene auf der gezeichnt wird.
   pfd.iPixelType = PFD_TYPE_RGBA 'Pixel werden im RGBA Modus dargestellt.
   'RGB ist mit VB identisch. Für A wird ein Alpha-Wert für die Transparenz übergeben
   pfd.nSize = Len(pfd) 'Größe der Struktur sollte natürlich stimmen
   pfd.nVersion = 1 'Versionsnummer
 
   PixelFormat = ChoosePixelFormat(frm.hDC, pfd) 'Prüfen, ob das oben beschriebene Pixelformat verfügbar ist
   If PixelFormat <> 0 Then
      'Das Format ist verfügbar.
      If SetPixelFormat(frm.hDC, PixelFormat, pfd) <> 0 Then
         'Einrichten des Pixelformates war erfolgreich
         hrc = wglCreateContext(frm.hDC)
         If hrc <> 0 Then
            'ein Rendering Kontext wurde erstellt
            If wglMakeCurrent(frm.hDC, hrc) <> 0 Then
               'Der Kontext wurde aktiviert
               frm.Show 'Fenster anzeigen
               Call LoadTextureS(App.Path & "\holz.bmp", 1) 'Laden und Erzeugen einer Textur
               glEnable glcTexture2D 'Einschalten des Texture Mappings
               glShadeModel smSmooth 'schaltet schöne Farbübergange ein
               glClearColor 0#, 0#, 0#, 0# 'schwarzer Hintergrund
               glClearDepth 1# 'Tiefenpuffer zurücksetzten (später mehr)
               glEnable glcDepthTest 'Aktivierung des Tiefentests (später mehr)
               glDepthFunc cfLEqual 'Typ des Tiefentests (später mehr)
               glHint htPerspectiveCorrectionHint, hmNicest 'Art der Perspektiven-Ansicht
               'hmNicest = beste Ansicht / hmFastest = schnellste Darstellung
 
               glMaterialfv GL_FRONT, GL_SPECULAR, Mt_Specular(0) 'Materialeigenschaft für Specular setzen
               glMaterialfv GL_FRONT, GL_SHININESS, Mt_Shiness(0) 'Exponent für den Glanz
               glMaterialfv GL_FRONT, GL_DIFFUSE, Mt_Diffuse(0) 'Materialeigenschaft für Diffus setzen
               glMaterialfv GL_FRONT, GL_AMBIENT, Mt_Ambient(0) 'Materialeigenschaft für Ambient setzen
               glLightfv GL_LIGHT1, GL_DIFFUSE, Lt_Diffuse(0) 'diffuses Licht setzen
               glLightfv GL_LIGHT1, GL_AMBIENT, Lt_Ambient(0) 'Umgebungslicht setzen
               glLightfv GL_LIGHT1, GL_SPECULAR, Lt_Specular(0) 'Glanzlicht setzen
               glLightfv GL_LIGHT1, GL_POSITION, Lt_Position(0) 'Position der Lampe setzen
 
               glEnable GL_LIGHTING 'Licht einschalten.
               glEnable GL_LIGHT1 'Licht Nr. 1 einschalten.
               'glEnable GL_COLOR_MATERIAL 'Farben unter Textur aktivieren
               CreateGLWindow = True
            End If
         End If
      End If
   End If
End Function

Listing 2: CreateGLWindow

...und es wurde Licht!  

Experimentieren Sie mit den Werten für Licht und Material ruhig viel herum. Für ein weiteres kleines Experiment kommentieren Sie bitte die Zeile glEnable glcTexture2D aus. Ohne die Textur haben Sie jetzt einen mehr oder weniger hellen Würfel. Darauf lassen sich vor Allem die Glanz-Effekte besser beobachten. Aber wieso ein heller Würfel? Wir haben doch Farben für jede Seite definiert!
Die Antwort ist ganz einfach: OpenGL ignoriert Flächenfarben, sobald Materialeigenschaften definiert werden. Aktivieren Sie einfach die Zeile glEnable GL_COLOR_MATERIAL und die für den Würfel definierten Farben werden verwendet. Selbstverständlich lassen sich Farben und Texturen auch gemeinsam verwenden. Dann entstehen eingefärbte Texturen. Da auch das Licht nicht unbedingt gleichmäßige RGB-Anteile haben muss, lässt sich die Szene durch farbige Lichter weiter verfremden.

Es geht weiter  

Lassen Sie Ihrer Phantasie ruhig freien Lauf und testen Sie drauflos. Es ist wichtig, dass Sie diese Grundlagen bis ins Detail verstanden haben, um später erfolgreich mit OpenGL arbeiten zu können. Im Kapitel 6 lassen wir dann eine Fläche transparent werden.

Beispielprojekt  

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