OpenGL in Visual Basic - Licht
von Hans Henning Klein
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.
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.