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).
|