OpenGL.org 3Dsource.de: Infos rund um 3D-Computergrafik, OpenGL und VRML
space Telefon-Tarife space VDI - Arbeitskreis Fahrzeugtechnik space Beschallungsanlagen und Elektroakustik space Heise-News space

18 Licht und Schattendarstellung

18.010 Was sollte ich grundsätzlich über die Beleuchtung der OpenGL wissen ?

Eine Voraussetzung ist die Angabe der Normalenvektoren, die man entweder selbst bereitstellt oder durch die Evaluators berechnen lassen kann. Dieses Thema wird auch noch in Frage 18.020 behandelt.

Die Beleuchtungsmethoden der OpenGL greifen auch nicht auf die mittels glColor*() definierten Farben zurück, dafür gibt es den Befehl glMaterial*(). Es ist allerdings möglich, die vorhandenen Objektfarben als Materialeigenschaften zu übernehmen, und zwar mittels glEnable(GL_COLOR_MATERIAL). Diese Variante ist in der Regel auch weniger aufwendig (für Programmierer und die OpenGL) als glMaterial*(). Am besten auch nochmal in die Frage 18.080 reinschauen.

Das Licht (bzw. die daraus resultierende Farbe) wird getrennt für jedes Vertex berechnet und über den Bereich des Polygons interpoliert, also lineare Zwischenberechnungen durchgeführt. Dadurch kann es passieren, dass ein Polygon zu dunkel dargestellt wird, auch wenn die Lichtquelle direkt auf die Oberfläche scheint (z.B. wenn die Eckpunkte im Schatten liegen). Eine realistischere Beleuchtung erhält man entweder über eine andere Interpolation bzw. angepasste Normalenvektoren der Eckpunkte oder mittels Light Maps.

Eine mit glLight*() angegebene Position der Lichtquelle wird mit den Daten der aktuellen ModelView Matrix berechnet, also genauso wie jede Koordinate auch. Weitere Informationen über Lichtquellen bietet Frage 18.050.

18.020 Warum werden meine Objekte mit nur einer Farbe und nicht schattiert und beleuchtet dargestellt ?

Das passiert üblicherweise, wenn keine Normalenvektoren angegeben wurden.

OpenGL braucht die Normalenvektoren, um die Beleuchtung zu berechnen, kann diese allerdings nicht selbst bereitstellen (ausser in Verbindung mit Evaluators). Erfolgt also kein glNormal*() Aufruf im Programm, bleiben diese Vektoren auf der Voreinstellung von (0.0, 0.0, 1.0) und die Farbwerte aller Vertices sind gleich (meist gleichbleibend schwarz wie beim Flat Shading).

Die einzige Lösung ist die korrekte Berechnung und manuelle Übergabe an die OpenGL mit glNormal3f() vor der betroffenen Vertex Angabe.

Normalerweise wird ein Normalenvektor einfach als Kreuzprodukt zweier vom Vertex weggehender Vektoren (zu benachbarten Punkten) berechnet. Das Red Book enthält ein kleines Beispiel zur Berechnung von Normalenvektoren, wie auch jedes andere Buch zur 3D Computergrafik, da dieses nicht OpenGL spezifisch ist.

18.030 Kann OpenGL die Oberflächennormalen auch selbst berechnen ?

Das geht nur in Verbindung mit Evaluators, ansonsten bietet die OpenGL diese Option nicht.

18.040 Warum erhalte ich nur Flat Shading, wenn ich mein Objekt beleuchte ?

Erstmal das einfachste überprüfen: Ist glShadeModel() auf GL_SMOOTH gesetzt (Voreinstellung) ?. Falls ja, wird die Ursache bei den Normalenvektoren zu finden sein.

Um einen weichen Farbverlauf über eine Oberfläche zu erzielen, sind an den Eckpunkten verschiedene Normalenvektoren erforderlich, ansonsten erfolgt keine Farbänderung (zumindest nicht durch das Licht). Man sollte auch nicht aus den Augen verlieren, dass ein typischer Normalenvektor immer senkrecht zum betrachteten Flächenelement steht. Mit einer geschickten Anordung der Normalenvektoren an den Eckpunkten kann man sogar den Eindruck erwecken, dass die betrachtete plane Oberfläche scheinbar abgerundet ist.

Um Fehler im eigenen Beleuchtungsmodell zu lokalisieren bietet sich ein Testprogramm an, dass schrittweise an den Umfang des eigenen Programms angepasst wird. So sollte es leicht möglich sein, eine Abweichung vom erwarteten zum tatsächlich erzielten Effekt zu erkennen und die Ursache festzustellen.

18.050 Wie kann ich die Position meiner Lichtquelle verändern oder diese kontrollieren ?

Man sollte wissen, wie die Position einer Lichtquelle durch die OpenGL transformiert wird.

Nach dem Aufruf von glLightfv(GL_LIGHT_POSITION, ...) werden die angegebenen Koordinaten mit der aktuellen ModelView Matrix multipliziert, durchlaufen ab dann also den normalen Transformationsprozess aller Koordinaten. Das heisst aber auch, dass eine Änderung deer ModelView Matrix (z.B. im nächsten Frame) nur dann Einfluss auf die Lichtquelle hat, wenn wiederum ein Aufruf von glLightfv(GL_LIGHT_POSITION, ...) erfolgt.

Die Frage nach einer festen Lichtquelle oder einer ständigen Nachführung lässt sich auch nicht pauschal beantworten, deshalb sollen hier nochmal einige spezielle Punkte herausgestellt werden:

  • Wie kann man die Lichtquelle mit dem jeweiligen Augpunkt verbinden, deren Position also laufend anpassen ?
  • Dazu muss die Position der Lichtquelle im Augpunktkoordinatensystem definiert werden. Das ist dann der Fall, wenn die ModelView Matrix mit glLoadIdentity() zurückgesetzt und direkt danach die Lichtquelle positioniert wird. Um die Lichtquelle direkt am Augpunkt zu befestigen und die Szene in Blickrichtung auszuleuchten, wird hier die Position auf den Koordinatenursprung und die Blickrichtung entlang der negativen z-Achse gesetzt(0,0,0, -1).

    In diesem Fall muss die Lichtquelle auch nur einmal positioniert werden, am besten direkt nach Programmstart.

  • Wie kann ich eine Lichtquelle fest im Raum positionieren, also unabhängig von meinem Augpunkt ?
  • Mit jeder Änderung der Szene ändert sich auch die ModelView Matrix. Damit wird es notwendig, die Lichtquelle für jedes Frame neu zu positionieren, was z.B. ähnlich dem folgenden Pseudo-Code realisiert werden könnte:

        - Viewing Transformation durchführen,
        - Lichtquelle installieren // glLightfv(GL_LIGHT_POSITION, ...)
        - Objekte definieren,
        - swapBuffers

    Soll das Licht zusammen mit einem bestimmten Objekt bewegt werden, muss für Lichtquelle und Objekt eine gemeinsame ModelView Transformation durchgeführt werden, also noch vor der Installation der Lichtquelle.

  • Wie kann ich die Lichtquelle frei durch den Raum bewegen (z.B. als Sonne) ?
  • Auch hier muss das Licht in jedem Frame neu positioniert werden. Zusätzlich ist vor der Installation der Lichtquelle noch eine entsprechende ModelView Transformation durchzuführen. Als Pseudo-Code etwa so:

        - Viewing Transformation durchführen,
        - die ModelView Matrix sichern // glPush()
        - die ModelView Transformation für die Lichtquelle durchführen,
        - Lichtquelle installieren // glLightfv(GL_LIGHT_POSITION, ...)
        - die alte ModelView Matrix reaktivieren // glPop()
        - Objekte definieren,
        - swapBuffers

18.060 Wie kann ich ein Spotlight (Punktlichtquelle) realisieren ?

Ein Spotlight ist eine Lichtquelle, die einen stark gebündelten Lichtstrahl aussendet. Ein Punktlicht dagegen ist eine Lichtquelle, die einen Lichtaustrittswinkel von bis zu 180 Grad haben kann, also der allgemeinere Fall. Der Aufruf einer Punktlichtquelle sieht z.B. so aus:

    glLightf (GL_LIGHT1, GL_SPOT_CUTOFF, 15.0);

Dieser Befehl installiert die Lichtquelle 1 mit einem Radius von 15 Grad, also einem Austrittswinkel von 30 Grad. Position und Richtung der Lichtquelle werden wie für jede andere auch realisiert.

18.070 Kann ich auch mehr als die über GL_MAX_LIGHTS vorgegebenen Lichtquellen nutzen ?

Zunächst sollte man versuchen, mit den von OpenGL bereitgestellten Lichtquellen auszukommen, da in den meisten Fällen nicht einmal diese Anzahl durch die Hardware unterstützt wird, ein Performanceverlust also in Kauf genommen werden muss.

Will man z.B. eine Strasse mit vielen Gebäuden und Strassenlaternen darstellen, würde die Anzahl der Lichtquellen sehr gross werden. Hier sollte man genau prüfen, welche Objekte von welchen Lichtquellen aktiv angestrahlt werden und welche durch eine geeignete Farbwahl bzw. Lightmaps (mittels Blending aufgehellte Bereiche der Szene ) ausreichend realistisch simuliert werden können. In der Regel reduziert sich die Anzahl der Lichtquellen so drastisch.

Für die verbleibenden Lichtquellen ist zu überlegen, ob einige nicht mehrfach in einem Frame verwendet werden können. GLUT enthält auch ein Beispielprogramm (multilight.c).

Da es nicht möglich ist, die Anzahl der von OpenGL bereitgestellten Lichtquellen zu erhöhen, muss also eine geeignete Alternative gesucht werden. Das kann eine manuelle Lichtberechnung (bzw. die Farbveränderung der Objekte) oder auch geeignete Texturen sein.

18.080 Was ist schneller, glMaterial*() oder glColorMaterial() ?

Innerhalb eines glBegin()/glEnd() Blocks ist glColor3f() meist deutlich schneller als glMaterialfv(). Die Ursache hierfür liegt darin, dass sich eine Farbdefinition leichter optimieren und damit berechnen lässt als eine Materialfestlegung. Es ist also immer angebracht, mit glColorMaterial() den aktuellen Farbwert mit einer Materialeigenschaft gleichzusetzen.

18.090 Warum funktioniert mein Lichtmodell nicht mehr richtig, nachdem ich die Szene skaliert habe ?

Die OpenGL kann nur mit normalisierten Normalenvektoren arbeiten (Länge = 1.0). Da die Normalenvektoren genauso wie jede andere Koordinate durch die ModelView Transformation verändert werden, kann sich durch ein glScale() auch die Länge der Normalenvektoren ändern und somit die Beleuchtung der Szene verfälschen.

Seit OpenGL 1.1 kann die Normalisierung der Vektoren auch automatisch durch den Befehl (GL_NORMALIZE) aktiviert werden, hat durch die notwendige Quadratwurzelberechnung aber auch einen negativen Einfluss auf die Performance des Programms.

Die zweite Möglichkeit steht ab OpenGL 1.2 bzw. als Extension auch für OpenGL 1.1 in Form von glEnable(GL_RESCALE_NORMAL) zur Verfügung. Diese Berechnung ist weniger aufwendig als der erstgenannte Befehl, da hier die durchgeführte Skalierung durch einfache Multiplikation rückgängig gemacht wird.

Ist der Verzicht auf Skalierung nicht möglich, sollte im Falle gleichzeitiger Verzerrung der Objekte (verschiedene Faktoren für glScale) trotzdem auf die korrekte Berechnung der Normalenvektoren mittels GL_NORMALIZE zurückgegriffen werden.

18.100 Wenn ich die Beleuchtung einschalte, wird meine gesamte Szene beleuchtet. Kann ich auch nur einzelne Objekte beleuchten ?

OpenGL arbeitet als Status-Maschine. Mit anderen Worten, eine Beleuchtung findet nur solange statt, wie der Befehl aktiviert ist. Als Antwort soll folgender Pseudo-Code dienen:

    glEnable(GL_LIGHTING);
    // beleuchtete Objekte zeichnen
    glDisable(GL_LIGHTING);
    // unbeleuchtete Objekte zeichnen

18.110 Kann ich auch Light Maps (wie in Quake) mit OpenGL nutzen ?

Diese Frage wird im Zusammenhang mit Texturen beantwortet.

18.120 Wie kann ich einen Brechungseffekt realisieren ?

Bei einer solchen Fragestellung sollte man eventuell überlegen, nicht OpenGL sondern ein Raytracing Programm zu nutzen, dass derartig komplexe Lichtberechnungen besser realisieren kann.

Bleibt die Wahl bei OpenGL, muss man den Brechungseffekt auf irgendeine Weise simulieren, denn OpenGL selbst bietet keine direkten Methoden zur Darstellung komplexer Lichteffekte. Eine oftmals gewählte Lösung ist es, eine Textur mit dem gewünschten Brechungsbild zu erzeugen und diese auf die betroffenen Objekte aufzubringen.

18.130 Wie kann ich Oberflächenstrukturen nachbilden ?

(im englischen Original ist von caustics die Rede, was eigentlich als Ätzmittel übersetzt werden müsste, passt aber nicht ganz ;-)
Bei Lichteinfall erkennbare Muster auf der Oberfläche lassen sich mit OpenGL nur durch entsprechende Texturen darstellen. Hier helfen die Beispielprogramme von GLUT weiter, die verschiedene Lichteffekte demonstrieren.

18.140 Wie kann ich Schatten nachbilden ?

OpenGL bietet leider keine direkte Unterstützung für Schatten. Man kann allerdings eigene Algorithmen zur Schattendarstellung verwenden. Beispiele findet man auf http://www.opengl.org im Bereich Tutorials->Techniques->Rendering Techniques und dort unter Lighting, Shadows & Reflections.

GLUT 3.7 kommt mit einigen Beispielen, wie sich Schatten durch Projektion und Stencil Buffer nachbilden lassen.

Schatten durch Projektion der schattenwerfenden Objekte sind nur dann brauchbar, wenn sich dieser Schatten auf einer planen Fläche abbilden soll. Realisieren lässt sich diese Idee mittels glFrustum(), dass eine 2D-Projektion des Objekts auf die gewünschte Fläche wirft.

Der Stencil Buffer ist dagegen wesentlich flexibler, man kann die Schatten auf jedem beliebig geformten Objekt abbilden. Die Grundidee besteht im Berechnen eines "Schatten-Volumens". Hier wird die Rückseite eines Objekts nicht gezeichnet (Back Face Culling) und die Vorderseite in den Stencil Buffer geschrieben. Dann erfolgt die gleiche Prozedur nochmal umgekehrt, indem man die Rückseite in den Stencil Buffer zeichnet. Danach enthält der Stencil Buffer nur an den Stellen ein gesetztes Bit, wo der Schatten gezeichnet werden muss. Nun kann die Szene ein zweites mal gezeichnet werden, wobei nur ambientes Licht aktiviert sein und der Tiefentest auf GL_EQUAL gesetzt sein soll. Als Ergebnis erhält man den Schatten eines Objekts.

Eine weitere Möglichkeit wurde auf der SIGGRAPH 92 vorgestellt (Fast Shadows and Lighting Effects Using Texture Mapping, Mark Segal et al). Dieser Vortrag beschreibt eine relativ einfache Erweiterung der OpenGL, um den Tiefenbuffer als eine Schatten-Textur zu nutzen. Nutzen lässt sich diese Idee bei Vorhandensein der Extensions GL_EXT_depth_texture bzw. GL_EXT_depth_texture3D (bzw. ab OpenGL 1.2).

Seite durchsuchen nach:

Fragen oder Ideen an:
Thomas.Kern@3Dsource.de
Linux-Counter
(C) Thomas Kern