Tutorial Lektion 8: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Multitex. verlinkt)
K (Ausser -> Außer)
 
(8 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
= Das Wesen von hell und dunkel - Licht =
+
= Das Wesen von Hell und Dunkel - Licht =
  
 
== Licht an ==
 
== Licht an ==
  
 
Hi,
 
Hi,
und wieder melde ich mich mit einem neuen Tutorial zurück. Heutiges Thema: Licht. Ohne Licht wäre die Umwelt dunkel und kein Pflänzchen könnte Zucker mittels Photosynthese erzeugen. Gäbe es kein Licht, so gäbe es die Welt nicht, so wie wir sie kennen. Um in den eigenen OpenGL Programmen eine auch nur annähernd realistische Atmosphäre zu erzeugen ist Licht entsprechend unabdingbar. Man kann Szenen dadurch einen düsteren, geheimnisvollen oder auch einen farbenfrohen, fast kitschigen Stil verleihen.
+
und wieder melde ich mich mit einem neuen Tutorial zurück. Heutiges Thema: Licht. Ohne Licht wäre die Umwelt dunkel und kein Pflänzchen könnte Zucker mittels Photosynthese erzeugen. Gäbe es kein Licht, so gäbe es die Welt nicht, wie wir sie kennen. Um in den eigenen OpenGL Programmen eine auch nur annähernd realistische Atmosphäre zu erzeugen ist Licht entsprechend unabdingbar. Man kann Szenen dadurch einen düsteren, geheimnisvollen oder auch einen farbenfrohen, fast kitschigen Stil verleihen.
  
 
== Arten des Lichts ==
 
== Arten des Lichts ==
  
In OpenGL gibt es unterschiedliche Arten des Lichts. Umgebungs Licht ('''Ambient''') ist eine Art des Lichts, dessen Richtung nicht zu erkennen ist. Es ensteht durch mehrfache Refelktion an Wänden oder anderen Flächen. Entsprechend gut vertreten ist es in Räumen, aber auch in der freien Natur umgibt uns nicht zu wenig davon.
+
In OpenGL gibt es unterschiedliche Arten des Lichts. Umgebungs-Licht ('''Ambient''') ist eine Art des Lichts, dessen Richtung nicht zu erkennen ist. Es entsteht durch mehrfache Reflexion an Wänden oder anderen Flächen. Entsprechend gut vertreten ist es in Räumen, aber auch in der freien Natur umgibt uns nicht zu wenig davon. An sich ist es das am schwierigsten zu berechnende, aber in vielen Szenen reicht es aus, eine Konstante vorzugeben - so macht es jedenfalls OpenGL.
  
"Diffuses" Licht('''Diffuse''') dagegen lässt die Richtung aus der es kommt erkennen. Je stärker eine Seite in das Licht gehalten wird, desto heller erscheint die Oberfläche. Je weiter man sie herausdreht, desto dunkler wird die Oberfläche. Die eintreffenden Lichtstrahlen werden in alle Richtungen verteilt, so dass die Oberfläche von allen Augenpositionen gleich hell erscheint.
+
"Diffuses" Licht ('''Diffuse''') dagegen lässt die Richtung aus der es kommt erkennen. Je stärker eine Seite in das Licht gehalten wird, desto heller erscheint die Oberfläche. Je weiter man eine Fläche vom Licht abwendet, desto dunkler wird die Oberfläche. Die eintreffenden Lichtstrahlen werden in alle Richtungen verteilt, so dass die Oberfläche von allen Augenpositionen gleich hell erscheint.
  
Glanz('''Specular''') kommt aus einer bestimmten Richtung und tendiert dazu in eine bestimmte Richtung gespiegelt zu werden. Platik, Metall und geschliffene Edelsteine, etc. haben einen grossen Glanzanteil, während Holz, kohle und Wandfarbe einen eher geringen Anteil haben.
+
Glanz ('''Specular''') kommt aus einer bestimmten Richtung und tendiert dazu, in einer bestimmten Richtung besonders hell zu erscheinen. Plastik, gebürstetes oder nicht matt lackiertes Metall oder auch geschliffene Edelsteine haben einen hohen Glanzanteil, während Holz, Kohle und Wandfarbe so gar nicht glänzen wollen.
  
 
== Materialeigenschaften ==
 
== Materialeigenschaften ==
  
Der vom Licht erzeugte Eindruck hängt aber nicht nur vom Licht selbst ab, sondern auch vom beleuchteten Matrial. Eine Kohleplatte wirkt im selben Licht anders, als eine polierte Edelstahloberfläche oder das Blech eines lackierten Autos. Deshalb gibt es in OpenGL die sog. Materialeigenschaften. Sie entsprechen denen des Lichts selbst, nur geben sie den Reflexionsgrad und nicht die Intensität wieder.
+
Der vom Licht erzeugte Eindruck hängt aber nicht nur vom Licht selbst ab, sondern auch vom beleuchteten Material. Eine Kohleplatte wirkt im selben Licht anders, als eine polierte Edelstahloberfläche oder das Blech eines lackierten Autos. Deshalb gibt es in OpenGL die sog. Materialeigenschaften. Sie entsprechen denen des Lichts selbst, nur geben sie den Reflexionsgrad und nicht die Intensität wieder. {{Hinweis|Hier im Wiki findet ihr in der [[Materialsammlung]] eine Auswahl von Parameterwerten.}}
  
Objekte können nun aber auch selbst Licht emittieren. Deshalb befindet sich unter den Materialeigenschaften zusätzlich noch ein Emissionswert. Ein Objekt mit Emission ist also auch bei kompletter Dunkelheit noch sichtbar, so z. B. die grüne Leuchtdiode an meinen Boxen, wenn ich die Rolläden geschlossen habe. Schade nur, dass OpenGL dieses emittierte Licht nicht auch auf andere Objekte überträgt :-(.
+
Objekte können nun aber auch selbst Licht emittieren. Deshalb befindet sich unter den Materialeigenschaften zusätzlich noch ein Emissionswert. Ein Objekt mit Emission ist also auch bei kompletter Dunkelheit noch sichtbar, so z.B. die grüne Leuchtdiode an meinen Boxen, wenn ich die Rollläden geschlossen habe. Schade nur, dass OpenGL dieses emittierte Licht nicht auch auf andere Objekte überträgt :-(.
  
 
== Normalen ==
 
== Normalen ==
  
OpenGL berechnet die Farbe des Lichts, das auf ein Objekt trifft mithilfe der Richtung aus der das Licht kommt und der Ausrichtung der Oberfläche eines Vertex. Nun stellt sich die Frage: in welche Richtung zeigt denn die Oberfläche? Wir können bei der Übergabe der Vertexdaten einfach mit glNormalx auch einfach die Normalen mitgeben. Normalerweise sollte die Länge des übergebenen Vektors = 1 sein. Bei einer Kugel entspricht die Normale übrigens immer der Richtung des Mittelpunktes zum betrachteten Vertex. Für andere Körper gibts weiter unten ein Paar Tipps zum Berechnen der Normalen.
+
OpenGL berechnet die Farbe des Lichts, das auf ein Objekt trifft mit Hilfe der Richtung aus der das Licht kommt und der Ausrichtung der Oberfläche eines Vertex. Nun stellt sich die Frage: in welche Richtung zeigt denn die Oberfläche? Wir können bei der Übergabe der Vertexdaten einfach mit [[glNormal]]x die Normalen angeben. Die Länge des Normalenvektors sollte 1 sein. Bei einer Kugel entspricht die Normale übrigens immer der Richtung des Mittelpunktes zum betrachteten Vertex. Für kompliziertere Gegenstände gibt es weiter unten ein paar Tipps zum Berechnen der Normalen auf "Nachbarschaftsbasis".
  
 
== Erste Lichtstrahlen ==
 
== Erste Lichtstrahlen ==
  
=== Licht anwerfen ===
+
=== Licht an ===
  
 
[[Bild:Tutorial_lektion8_test.gif|center]]
 
[[Bild:Tutorial_lektion8_test.gif|center]]
  
Es wird Zeit, die ersten Gehversuche mit dem OpenGL Licht zu bewerkstelligen. Wir brauchen dazu eigentlich nicht viel zu tun. Wir müssen am Anfang das Licht mittels '''[[glEnable]](GL_LIGHTING)'''  erst einmal scharf schalten und dann einen unserer Lichtquellen aktivieren. Das geht mittels '''[[glEnable]](GL_LIGHTx)'''  ''(Zur Information: normale OpenGL Implementationen unterstützen 8 Lichter. Es gibt aber Implementationen, die wesentlich mehr unterstützen. Um Zugriff auf alle vorhandenen Lichter zu erhalten, muss man einfach zu GL_LIGHT0 einen entsprechenden Wert hinzuaddieren. GL_LIGHT0+8 wäre dann z. B. das Licht #9).''
+
Es wird Zeit. Machen wir erste Gehversuche mit OpenGL Licht! Wir brauchen dazu nicht viel zu tun. Mit glEnable(GL_LIGHTING) schalten wir die Lichtberechnung scharf und müssen dann eine Lichtquelle aktivieren. Das geht mit glEnable(GL_LIGHTx) (Zur Information: OpenGL Implementationen müssen 8 Lichter unterstützen. Es gibt aber Implementationen, die hier mehr zu bieten haben. Um Zugriff auf alle vorhandenen Lichter zu erhalten, genügt es zu GL_LIGHT0 einen entsprechenden Wert zu addieren. GL_LIGHT0+8 wäre dann z.B. das 9. Licht).
  
<pascal>
+
 
 +
<source lang="pascal">
 
   glEnable(GL_LIGHTING);
 
   glEnable(GL_LIGHTING);
 
   glEnable(GL_LIGHT0);
 
   glEnable(GL_LIGHT0);
</pascal>
+
  glEnable(GL_COLOR_MATERIAL);
 +
</source>
  
=== Lichtquelleneigenschaften übergeben ===
+
=== Sonnenbrand erzeugen ===
  
Der nächste Schritt besteht darin, die Eigenschaften der Lichtquelle(n) an OpenGL weiterzureichen. Dies läuft immer mit der Funktion '''[[glLight]]x'''. Sie nimmt 3 Parameter: '''light''': Licht das gerade eingestellt wird, z.&nbsp;B. GL_LIGHT0. pname: Eigenschaft, die gerade eingestellt wird. '''params''': Neuer Wert für die Eigenschaft.
+
Der nächste Schritt besteht darin, die Eigenschaften der Lichtquelle(n) an OpenGL weiterzureichen. Dies läuft immer mit der Funktion '''[[glLight]]x'''. Sie nimmt 3 Parameter: '''light''': Die Lichtquelle die gerade eingestellt wird. '''pname''': Die Eigenschaft, die wir manipulieren möchten. '''params''': Der neue Wert der Eigenschaft.
  
Es stehen eine ganze Reihe Eigenschaften zur Verfügung:
+
Mit '''pname''' dürfen wir aus einer ganzen Sammlung von Eigenschaften schöpfen:
  
 
{| border="1" align="center"
 
{| border="1" align="center"
Zeile 48: Zeile 50:
 
! Bedeutung
 
! Bedeutung
 
|-
 
|-
| GL_AMBIENT || 0.0, 0.0, 0.0, 1.0 || Ambienter RGBA Anteil des Lichts
+
| GL_AMBIENT || 0.0, 0.0, 0.0, 1.0 || Ambienter RGBA Anteil des Lichtes
 
|-
 
|-
| GL_DIFFUSE || 1.0, 1.0, 1.0, 1.0 || Diffuser RGBA Anteil des Lichts
+
| GL_DIFFUSE || 1.0, 1.0, 1.0, 1.0 || Diffuser RGBA Anteil des Lichtes
 
|-
 
|-
| GL_SPECULAR || 1.0, 1.0, 1.0, 1.0 || Glanz RGBA Anteil des Lichts
+
| GL_SPECULAR || 1.0, 1.0, 1.0, 1.0 || Glanz RGBA Anteil des Lichtes
 
|-
 
|-
| GL_POSITION || 0.0, 0.0, 1.0, 0.0 || Koordinaten des Lichts
+
| GL_POSITION || 0.0, 0.0, 1.0, 0.0 || Koordinaten des Lichtes
 
|-
 
|-
| GL_SPOT_DIRECTIONT || 0.0, 0.0, -1.0 || Richtung des Spotlights
+
| GL_SPOT_DIRECTION || 0.0, 0.0, -1.0 || Richtung des Spotlights
 
|-
 
|-
 
| GL_SPOT_EXPONENT || 0.0 || Spotlight-Exponent
 
| GL_SPOT_EXPONENT || 0.0 || Spotlight-Exponent
 
|-
 
|-
| GL_SPOT_CUTOFFT || 180.0 || Spotlight Sperrwinkel
+
| GL_SPOT_CUTOFF || 180.0 || Spotlight Sperrwinkel
 
|-
 
|-
| GL_xxx_ATTENUATION || ... || Abschwächung des Lichts mit der Entfernung
+
| GL_xxx_ATTENUATION || ... || Abschwächung des Lichtes mit der Entfernung
 
|}
 
|}
  
 
=== Materialeigenschaften übergeben ===
 
=== Materialeigenschaften übergeben ===
  
Das gleiche Spielchen müssen wir jetzt auch mit den Materialeigenschaften treiben. Diesmal allerdings mit der Funktion '''[[glMaterial]]x'''. Die Parameter sind die gleichen wie bei '''[[glLight]]x'''.
+
Das gleiche Spielchen müssen wir mit den Materialeigenschaften treiben. Diesmal ist die Funktion '''[[glMaterial]]x''' der Dreh und Angelpunkt. Die Parameterfolge ist die gleiche wie bei '''[[glLight]]x'''.
  
 
{| border="1" align="center"
 
{| border="1" align="center"
Zeile 78: Zeile 80:
 
| GL_DIFFUSE || 0.8, 0.8, 0.8, 1.0 || RGBA Reflexionsgrad von diffusem Licht
 
| GL_DIFFUSE || 0.8, 0.8, 0.8, 1.0 || RGBA Reflexionsgrad von diffusem Licht
 
|-
 
|-
| GL_SPECULAR || 0.0, 0.0, 0.0, 1.0 || RGBA Glanzreflexionsgrad des Lichts
+
| GL_SPECULAR || 0.0, 0.0, 0.0, 1.0 || RGBA Glanzreflexionsgrad des Lichtes
 
|-
 
|-
 
| GL_SHININESS || 0.0 || Glanz-Exponent
 
| GL_SHININESS || 0.0 || Glanz-Exponent
Zeile 89: Zeile 91:
 
=== Beispiellicht ===
 
=== Beispiellicht ===
  
Das obige Beispiel mit den Kugeln wurde mit den folgenden Code erzeugt:
+
Das Beispiel mit den Kugeln wurde mit folgendem Code erzeugt:
  
<pascal>
+
<source lang="pascal">
 
   procedure TStationarySphereState.RenderState;
 
   procedure TStationarySphereState.RenderState;
 
   const
 
   const
Zeile 113: Zeile 115:
 
     glLightfv(GL_LIGHT0, GL_POSITION, @light_position[0]);
 
     glLightfv(GL_LIGHT0, GL_POSITION, @light_position[0]);
  
 +
    glEnable(GL_COLOR_MATERIAL);
 
     glEnable(GL_LIGHTING);
 
     glEnable(GL_LIGHTING);
 
     glEnable(GL_LIGHT0);
 
     glEnable(GL_LIGHT0);
    ...Szene Rendern...
+
    ...Szene Rendern...
 
   end;
 
   end;
</pascal>
+
</source>
  
 
== Etwas schwächer bitte ==
 
== Etwas schwächer bitte ==
  
Viele Lichtquellen werden mit der Entfernung schwächer. Bei unserer Sonne tritt dieser Effekt so gut wie nicht auf, bei kleinen Funzeln dagegen ist recht bald die maximale Ausleuchtweite erreicht. Diesen Effekt können wir natürlich auch mit OpenGL erzeugen.
+
Viele Lichtquellen werden mit der Entfernung schwächer. Bei unserer Sonne tritt dieser Effekt so gut wie nicht auf, bei kleinen Funzeln dagegen ist recht bald die maximale Leuchtweite erreicht. Diesen Effekt können wir auch mit OpenGL nachahmen.
  
 
[[Bild:Tutorial_lektion8_attenuation.gif|center]]
 
[[Bild:Tutorial_lektion8_attenuation.gif|center]]
  
Auf diesem Bild ist schön zu erkennen, dass die Kugeln rechts oben heller sind als die links unten, die weiter von der Lichtquelle entfernt sind (wer nichts sieht, spiele doch mal bitte mit dem Kontrast und der Helligkeit des Monitors - ich kann nicht versprechen, dass der Effekt auf jedem Monitor so schön wie auf meinem herauskommt). Wie wird nun dieser Effekt erzeugt?
+
Auf diesem Bild ist schön zu erkennen, dass die Kugeln rechts oben heller sind als die links unten, welche weiter von der Lichtquelle entfernt sind (wer nichts sieht, spiele doch mal bitte mit dem [[Farbraum#Grundlegende_Bildschirmkalibrierung|Kontrast und der Helligkeit]] des Monitors - ich kann nicht versprechen, dass der Effekt auf jedem Monitor so schön wie auf meinem herauskommt). Wie wird nun dieser Effekt erzeugt?
  
 
OpenGL kennt einen sog. Attenuation Factor:
 
OpenGL kennt einen sog. Attenuation Factor:
Zeile 136: Zeile 139:
 
* k<sub>q</sub> = GL_QUADRATIC_ATTENUATION
 
* k<sub>q</sub> = GL_QUADRATIC_ATTENUATION
  
Dieser Faktor bei der Lichtberechnung einfach mit den Lichtstärken multipliziert, so dass das Licht schwächer wird. Übergeben werden die Werte wieder mittels '''[[glLight]]x''':
+
Dieser wird Faktor bei der Lichtberechnung einfach mit den Lichtstärken multipliziert, so dass das Licht stärker oder schwächer wird. Übergeben werden die Werte wieder mittels '''[[glLight]]x''':
  
<pascal>
+
<source lang="pascal">
 
   glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0);
 
   glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0);
 
   glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.001);
 
   glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.001);
 
   glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.004);
 
   glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.004);
</pascal>
+
</source>
  
 
== Das Positionsproblem ==
 
== Das Positionsproblem ==
  
Lichter sind unterschiedlich - Manche sind fest an den Betrachter der Szene gekoppelt, andere irgendwo fest in der Szene und wieder andere sind unendlich weit entfernt und ihr Licht trifft parallel auf die Gegenstände. All dies will simuliert werden. OpenGL hält alles parat, was wir dafür benötigen. Uns kommt auch noch zugute, dass OpenGL die Übergabe der Licht Positions genauso behandlet wie ein Vertex und entsprechend durch die Modelview Matrix jagd.
+
Lichter sind unterschiedlich - Manche sind fest an den Kopf des Betrachters gekoppelt, andere irgendwo in der Szene platziert und wieder andere sind unendlich weit entfernt und ihr Licht trifft parallel auf die Gegenstände. All dies will simuliert werden. OpenGL hält es parat. Uns kommt auch noch zugute, dass OpenGL die Übergabe der Licht Position genauso behandelt wie die eines Vertex und entsprechend durch die Modelview Matrix jagt.
  
 
=== Unendlich weit entfernte Lichter ===
 
=== Unendlich weit entfernte Lichter ===
  
Das Universum, die Milchstarsse, der äussere Ring, unser Sonnensystem, die Sonne und unsere Erde. Das Licht, das unsere Sonne ausstrahlt und bei uns auf der Erde ankommt, trifft uns in nahezu parallelen Lichtstrahlen. Damit ist die Sonne eine unendlich entfernte Lichtquelle. Wenn wir eine Solche Lichtquelle angeben wollen, ist also nicht ihre Position intressant(wobei das auch ginge), sondern allein die Richtung aus der das Licht kommt. Um das mit OpenGL zu bewerkstelligen, müssen wir die w-Koordinate(=die letzte Koordinate) der Position bei der Übergabe auf 0 setzen und voila. Unsere unendlich weit entfernte Lichtquelle ist komplett. Sinnigerweise ist dann im übrigen auch Attenuation abgeschaltet.
+
Das Universum, die Milchstraße, der äußere Ring, unser Sonnensystem, die Sonne und unsere Erde. Das Licht, das unsere Sonne ausstrahlt und bei uns auf der Erde ankommt, trifft uns in nahezu parallelen Lichtstrahlen. Damit verhält sich die Sonne wie eine unendlich entfernte Lichtquelle. Wenn wir eine solche Lichtquelle angeben wollen, ist also nicht ihre Position interessant (wobei das auch ginge), sondern allein die Richtung aus der das Licht kommt. Wie? Wir setzen die w-Koordinate(=die letzte Koordinate) der Position auf 0. Unsere unendlich weit entfernte Lichtquelle ist komplett. Sinnigerweise ist dann im übrigens auch Attenuation abgeschaltet.
  
 
=== Stationäre Lichtquellen ===
 
=== Stationäre Lichtquellen ===
  
Lichtquellen, die an einer bestimmten Stelle in der Szene stehen, müssen mit der Kamerabewegung mitgeschoben werden. Entsprechend muss das Licht angegeben werden, bevor die Modelviewmatrix durch Objekte verschoben wird, aber nachdem die Kamera initialisiert wurde.
+
Lichtquellen, die an einer bestimmten Stelle in der Szene stehen, müssen mit der Kamerabewegung mitgeschoben werden. Entsprechend wird das Licht angegeben werden, bevor die Modelviewmatrix durch Objekte verschoben wird, aber nachdem die Kamera initialisiert wurde.
<pascal>
+
<source lang="pascal">
 
   glLoadIdentity;
 
   glLoadIdentity;
 
   //..glRotatef, glTransaltef.. für die Kamera
 
   //..glRotatef, glTransaltef.. für die Kamera
Zeile 163: Zeile 166:
 
     DrawObjekts;
 
     DrawObjekts;
 
   glPopMatrix;
 
   glPopMatrix;
</pascal>
+
</source>
  
 
=== Relatives Licht ===
 
=== Relatives Licht ===
  
Wollen wir stattdessen unsere Lichtquelle relativ zu einem bestimmten Objekt bewegen, so müssen wir ein wenig anders vorgehen: erst Modelviewmatrix so bearbeiten, dass wir das Objekt zeichen könnten, vor dem Zeichen selbst jedoch die Matrixfunktion für das Licht ausführen und die Lichtposition übergeben:
+
Wollen wir stattdessen unsere Lichtquelle relativ zu einem bestimmten Objekt bewegen, so müssen wir ein wenig anders vorgehen: Erst die Modelviewmatrix so bearbeiten, dass wir das Objekt zeichnen könnten, vor dem Zeichen selbst jedoch die Matrixfunktion für das Licht ausführen und die Lichtposition übergeben:
  
<pascal>
+
<source lang="pascal">
 
   //..glRotatef, glTransaltef, glMultMatrix, etc...
 
   //..glRotatef, glTransaltef, glMultMatrix, etc...
 
   glPushMatrix;
 
   glPushMatrix;
Zeile 176: Zeile 179:
 
   glPopMatrix;
 
   glPopMatrix;
 
   DrawObject;
 
   DrawObject;
</pascal>
+
</source>
  
 
=== Lichtquellen, die an der Kamera kleben ===
 
=== Lichtquellen, die an der Kamera kleben ===
Zeile 182: Zeile 185:
 
Wenn eine Lichtquelle immer relativ zum Betrachter sein soll(wenn also das Licht z. B. feste am Kopf montiert ist, wie bei einem Minenarbeiter), so langt es die Position des Lichts zu definieren, bevor die Modelviewmatrix mit der Kameraposition und Drehung bearbeitet wurde:
 
Wenn eine Lichtquelle immer relativ zum Betrachter sein soll(wenn also das Licht z. B. feste am Kopf montiert ist, wie bei einem Minenarbeiter), so langt es die Position des Lichts zu definieren, bevor die Modelviewmatrix mit der Kameraposition und Drehung bearbeitet wurde:
  
<pascal>
+
<source lang="pascal">
 
   glLoadIdentity;
 
   glLoadIdentity;
 
   glLightfv(GL_LIGHT0, GL_POSITION, @position[0]);
 
   glLightfv(GL_LIGHT0, GL_POSITION, @position[0]);
</pascal>
+
</source>
  
 
== Spot an! ==
 
== Spot an! ==
  
Wer kennt sie nicht, die Spotlights? Im Fernsehen und Theater oft gebraucht, aber auch im Realen leben, z.&nbsp;B. bei Taschenlampen, Suchscheinwerfern, etc. oft zu sehen. Häufig benötigt man eine ganze Schaar von ihnen, dort eigen sich die OpenGL Lichter nur schlecht oder gar nicht. Man nehme hier z.&nbsp;B. eine Lightmap um einen äquivalenten Effekt zu erzielen. Bei wenigen, oder wenn man mit entsprechenden Techniken noch keine Erfahrungen hat, eignen sich die OpenGL Spotlights vorerst einmal besser.
+
Wer kennt sie nicht, die Spotlights? Im Fernsehen und Theater oft gebraucht, aber auch im realen Leben, z.B. bei Taschenlampen, Suchscheinwerfern (etwa noch keinen in der Hand gehabt? Ich schon :-) ), etc. oft zu sehen. Leider benötigt man nicht selten eine ganze Schar von ihnen, wozu sich die OpenGL Lichter nur schlecht eignen - aus der Patsche hilft da oft [[Blending]] oder andere Techniken. Wenn man mit entsprechenden Projektions-Techniken noch keine Erfahrungen hat, müssen die OpenGL-Lichter dann aber doch wieder herhalten.
  
 
=== Parameter setzen ===
 
=== Parameter setzen ===
Zeile 195: Zeile 198:
 
[[Bild:Tutorial_lektion8_spotlight.gif|center]]
 
[[Bild:Tutorial_lektion8_spotlight.gif|center]]
  
Schauen wir uns einmal die Lichtparameter für Spotlights an: '''GL_SPOT_DIRECTION''' gibt die Richtung an, in die das Spotlicht zeigt. Diese lässt sich wieder mithilfe geschickter Plazierung im Code durch die Modelviewmatrix jagen um den gewünschten Effekt zu erzielen. '''GL_SPOT_CUTOFF''' gibt den Winkel an, der zwischen Richtung und max. Auswurf besteht. Zuletzt gibt es noch den '''GL_SPOT_EXPONENT''', der beschreibt, wie stark die Lichtstärke nach aussen hin abnimmt.
+
Schauen wir uns die Lichtparameter für Spotlights an: '''GL_SPOT_DIRECTION''' gibt die Richtung an, in die das Spotlicht zeigt. Diese lässt sich wieder mit Hilfe geschickter Platzierung im Code durch die Modelviewmatrix jagen um den gewünschten Effekt zu erzielen - wie immer eben. '''GL_SPOT_CUTOFF''' gibt den Winkel an, der zwischen Richtung und max. Auswurf besteht. Zuletzt gibt es noch den '''GL_SPOT_EXPONENT''', der beschreibt, wie stark die Lichtstärke nach außen hin abnimmt.
  
<pascal>
+
<source lang="pascal">
 
const
 
const
 
     light_position : Array[0..3] of GlFloat = (0.0, 10.0, 0.0, 1.0);
 
     light_position : Array[0..3] of GlFloat = (0.0, 10.0, 0.0, 1.0);
Zeile 220: Zeile 223:
 
     //...
 
     //...
 
   end;
 
   end;
</pascal>
+
</source>
 +
 
 +
Zur besseren Illustration ein Bild
  
 
[[Bild:Tutorial_lektion8_spots_highres.gif|center]]
 
[[Bild:Tutorial_lektion8_spots_highres.gif|center]]
Zeile 226: Zeile 231:
 
== glLightModel? ==
 
== glLightModel? ==
  
Bislang haben wir einen Befehl völlig ausser Acht gelassen: '''[[glLightModel]]x'''. Er nimmt zwei Parameter: pname und param:
+
Bislang haben wir einen Befehl völlig außer Acht gelassen: '''[[glLightModel]]x'''. Er nimmt zwei Parameter: '''pname''' und '''param''':
  
 
{| border="1" align="center"
 
{| border="1" align="center"
Zeile 250: Zeile 255:
 
== Das Vertexproblem ==
 
== Das Vertexproblem ==
  
Sehr einfach muss auf euch die Benutzung von Licht bislang gewirkt haben. Aber der ein oder andere wird es vielleicht schon herausgelesent haben: OpenGL Licht wird per Vertex und nicht per Pixel berechnet. Das Problem dabei ist, dass sich mit der Auflösung des Objekts das Aussehen des Lichts stark ändern kann. Bei diffusem und ambientem Licht spielt das meist so gut wie kaum eine Rolle. Specular und Spotlights reagieren allerdings emfindlich darauf. Die schönen Rundungen, die diese Lichtarten häufig ausbilden, können dadurch empfindlich verschandelt werden. Die Ergebnisse sind nicht mehr wirklich Rund, sondern vielmehr eckig und kantig. Ihr glaubt mir nicht? Dann schaut nicht zu genau hin:
+
Die Benutzung von Licht muss bislang auf euch sehr einfach gewirkt haben. Aber der eine oder andere wird es vielleicht schon herausgelesen haben: OpenGL Licht wird per Vertex und nicht per Pixel berechnet. Das Problem dabei ist, dass sich mit der Auflösung des Objekts das Aussehen des Lichts stark ändern kann. Bei diffusem und ambientem Licht spielt das kaum eine Rolle. Specular und Spotlights reagieren empfindlich. Die schönen Rundungen, die diese Lichtarten häufig ausbilden, können verschandelt werden. Die Ergebnisse sind nicht mehr wirklich rund, vielmehr eckig und kantig. Ihr glaubt mir nicht? Dann schaut nicht zu genau hin:
  
 
[[Bild:Tutorial_lektion8_spots_lowres.gif|center]]
 
[[Bild:Tutorial_lektion8_spots_lowres.gif|center]]
  
Und was lernen wir daraus? Schönes Licht braucht viele Vertices.
+
Und was lernen wir draus? Schönes Licht braucht viele Vertices.
  
 
== Normalen die 2. ==
 
== Normalen die 2. ==
Zeile 260: Zeile 265:
 
=== Länge = 1? ===
 
=== Länge = 1? ===
  
Kommen wir also nun wie versprochen noch einmal zu den Normalen zurück. Normalen, die mit glVertexx angegeben werden, werden genauso wie die Vertices mit der Modelview Matrix multipliziert(von Verschiebungen abgesehen). Normalen sollten für korrekte Lichtberechnung die Länge 1 haben. Ist mit der Multiplikation mit der Modelviewmatrix auch eine Skalierung verbunden, so haben wir ein Problem, denn die länge ist nicht mehr 1. Um das Problem zu beheben, kann man mit '''[[glEnable]](GL_NORMALIZE)''' OpenGL anweisen, die Normalen selbst zu normieren(=1). Aber Achtung: Das kann ganz schön die Leistung beeinträchtigen.
+
Kommen wir also nun wie versprochen noch einmal zu den Normalen zurück. Normalen, die mit [[glNormal|glNormalx]] angegeben werden, werden, abgesehen von Verschiebungen, wie Vertices mit der Modelview Matrix multipliziert. Normalen sollten für korrekte Lichtberechnung normiert sein, dass heißt die Länge 1 haben (ist sie länger, so erscheint das Objekt heller, ist sie kürzer, so wird das Objekt dunkler). Ist mit der Multiplikation mit der Modelviewmatrix auch eine Skalierung verbunden, so haben wir ein Problem, denn die Länge ist nicht mehr 1. Um das zu beheben, kann vermittels '''[[glEnable]](GL_NORMALIZE)''' OpenGL angewiesen werden, die Normalen selbst auf Länge eins zu skalieren. Aber Achtung: Das kostet Rechenleistung. Allerdings kostet selber berechnen vermutlich noch mehr.
 
 
  
 
=== Einfache Normalen ===
 
=== Einfache Normalen ===
  
Manchmal ist die Berechnung von Normalen ganz einfach. So z. B. bei Kugeln. Die Richtung die der Mittelpunkt zu irgendeinem Vertex auf der Oberfläche beschreibt, ist gleich der Richtung der normalen - sie muss nur noch normiert werden. Bei Flächen auf den Achsen muss unsereins auch nicht groß nachdenken, wie denn die passende Normale auszusehen hat. Aber wie ist es, wenn unser betrachteter Körper keine so einfache Fläche hat? Was dann?
+
Manchmal ist die Berechnung von Normalen ganz einfach. So bei Kugeln. Die Richtung die der Mittelpunkt zu irgendeinem Vertex auf der Oberfläche beschreibt, ist gleich der Richtung der Normalen - sie muss nur noch normiert werden. Bei Flächen auf den Achsen muss unsereins auch nicht groß nachdenken, wie denn die passende Normale auszusehen hat. Aber wie ist es, wenn unser betrachteter Körper keine so einfache Oberfläche hat? Was dann?
  
 
=== Normalenberechnung per Dreieck ===
 
=== Normalenberechnung per Dreieck ===
  
[[Bild:Tutorial_lektion8_normalen_flat.gif|center]]
+
Will man anhand von Dreiecksdaten Normalen erzeugen, so muss man mal wieder zur Mathematik rüberschauen - die haben das ideale Werkzeug dafür: Das [[Vektorprodukt|Vektor-Kreuzprodukt]]. Um die Normale der Fläche beschrieben durch die 3 Ecken V<sub>1</sub>, V<sub>2</sub>, V<sub>3</sub> zu bekommen, braucht man nur zu rechnen:
 
 
Will man nun mithilfe von Dreiecksdaten normalen erzeugen, so muss man mal wieder zur Mathematik rüberschauen - die haben das ideale Werkzeug dafür: Das [[Vektorprodukt|Vektor-Kreuzprodukt]]. Um die Normale der Fläche beschrieben durch die 3 Ecken V<sub>1</sub>, V<sub>2</sub>, V<sub>3</sub> zu bekommen, muss man rechnen:
 
  
 
N = (V<sub>1</sub> - V<sub>2</sub>)x(V<sub>2</sub> - V<sub>3</sub>)
 
N = (V<sub>1</sub> - V<sub>2</sub>)x(V<sub>2</sub> - V<sub>3</sub>)
  
 +
Wenn wir das für jedes Polygon durchziehen, dann können wir für jeweils 3 Vertices die Normale angeben. Das schaut ein bisschen eckig aus, bringt uns unserem Ziel aber gewaltig näher. Erinnert irgendwie an die ersten Spiele, die mit Licht arbeiteten... Flatshading war schon was schönes, aber OpenGL kann mehr, wie wir bereits an den Kugeln gesehen haben.
  
Und für alle, die nicht die geometry.pas verwenden wollen, sondern ihre Funktionen selber schreiben wollen, das Vektor-(Kreuz)Produkt:
+
[[Bild:Tutorial_lektion8_normalen_flat.gif|center]]
  
    x<sub>1</sub>      y<sub>1</sub>      x<sub>2</sub>y<sub>3</sub> - x<sub>3</sub>y<sub>2</sub>
+
'''Und nicht vergessen:''' Die Normalen sollten noch auf die Länge 1 gebracht werden, siehe [[Normale]].
  ( x<sub>2</sub> ) x ( y<sub>2</sub> ) = ( x<sub>3</sub>y<sub>1</sub> - x<sub>1</sub>y<sub>3</sub> )
 
    x<sub>3</sub>      y<sub>3</sub>      x<sub>1</sub>y<sub>2</sub> - x<sub>2</sub>y<sub>1</sub>
 
 
 
 
 
Wenn wir das jetzt für jedes Polygon in unserem Mesh machen, dann können wir für jeweils 3 Vertices die Normale angeben. Das schaut ein bischen eckig aus, bringt uns unserem Ziel aber gewaltig näher. Erinnert irgendwie an die ersten Spiele, die mit licht arbeiteten... Flatshading war schon was schönes, aber OpenGL kann mehr, wie wir bereits an den Kugeln gesehen haben.
 
 
 
'''Und nicht vergessen:''' Die Normalen sollten noch auf die Länge 1 gebracht werden...
 
  
 
=== Normalen glätten ===
 
=== Normalen glätten ===
Zeile 291: Zeile 287:
 
[[Bild:Tutorial_lektion8_normalen_smooth.gif|center]]
 
[[Bild:Tutorial_lektion8_normalen_smooth.gif|center]]
  
Um die Polygone nun ein wenig unsichtbarer zu machen gibt es ein einfaches Hilfsmittel: Man nehme die direkt anliegenden Flächennormalen um ein Vertex, bilde den Durchschnitt und voila: Unser Objekt schaut schon wesentlich abgerundeter aus. An einigen Stellen sollte man damit aber vorsichtig walten: Geht es einmal wirklich scharf um eine spitze Kante, so schaut diese Abrundung vermutlich etwas seltsam und abnormal aus. Also: nicht einfach drauflos abrunden. Da bei komplexeren Objekten jeder vermutlich eh einen 3D Modeller verwendet, bei dem man das etwas feinfühlig kontrollieren kann, sollte das kein grösseres Problem sein - jeder der seinen eigenen Modeller schreibt, muss sich natürlich etwas einfallen lassen - nützlich erweist sich hier sicherlich das [[Standard_Skalarprodukt|Skalarprodukt]], da sich damit sehr leicht Winkel zwischen den Normalen berechnen lassen. Ist der Winkel zu groß, kann es sich nicht um eine Rundung handeln und wir haben es sicher mit einer Kante zu tun.
+
Um die Polygone nun ein wenig unsichtbarer zu machen, gibt es ein einfaches Hilfsmittel: Man nehme die direkt anliegenden Flächennormalen um ein Vertex, bilde den Durchschnitt und voilà: Unser Objekt schaut schon runder aus. An einigen Stellen sollte man damit aber vorsichtig walten: Geht es einmal wirklich scharf um eine spitze Kante, so schaut diese Abrundung vermutlich etwas seltsam und abnormal aus. Also: Nicht einfach drauflos abrunden. Da bei komplexeren Objekten jeder vermutlich sowieso einen 3D Modeller verwendet, bei dem man das etwas feinfühlig kontrollieren kann, sollte das kein größeres Problem sein - jeder der seinen eigenen Modeller schreibt, muss sich natürlich etwas einfallen lassen - nützlich erweist sich hier das [[Standard_Skalarprodukt|Skalarprodukt]], da man damit den Winkel zwischen zwei Normalen bestimmen kann. Ist der Winkel zu groß, kann es sich nicht um eine Rundung handeln und wir haben es mit einer Kante zu tun - zumindest solange unsere Polygondaten halbwegs fein aufgelöst sind.
  
 
== Das etwas andere Licht ==
 
== Das etwas andere Licht ==
  
[[Bild:Tutorial_lektion8_diabolo.gif|center]]
+
[[Bild:Tutorial_lektion8_RoteRinge.jpg|center]]
  
OpenGL's Standardmethode einen Beleuchtungseffekt zu erzielen, ist natürlich nicht die einzige Methode, den Eindruck von Licht zu erzeugen. Ein anderer Trick drängt sich bei entsprechendem Wissen über die Möglichkeiten von OpenGL geradezu auf: Environment Mapping, oder im folgendem Beispiel speziell Sphere Mapping. Den entstehenden Effekt kann man vergleichen, mit einer perfekten Spiegelung. Das einzige was wir tun müssen, ist OpenGL anweisen, die Texturkoordianten selbst zu berechnen:
+
Mit OpenGLs Standardmethode einen Beleuchtungseffekt zu erzielen, ist natürlich nicht der Weisheit letzter Schluss. Ein anderer Trick drängt sich bei entsprechendem Wissen über die Möglichkeiten geradezu auf: Environment Mapping, oder im folgendem speziell Sphere Mapping. Den entstehenden Effekt kann man mit einer perfekten Spiegelung vergleichen. Das einzige was wir tun müssen, ist OpenGL anzuweisen, die Texturkoordinaten selbst zu berechnen:
  
<pascal>
+
<source lang="pascal">
 
     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
 
     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
 
     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
 
     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
 
     glEnable(GL_TEXTURE_GEN_S);
 
     glEnable(GL_TEXTURE_GEN_S);
 
     glEnable(GL_TEXTURE_GEN_T);
 
     glEnable(GL_TEXTURE_GEN_T);
</pascal>
+
</source>
  
Das wars eigentlich schon, wir benötigen nur noch eine Sphere Map:
+
Das wars schon, wir benötigen nur noch eine Sphere Map:
  
 
[[Bild:Tutorial_lektion8_lightmap.jpg|center]]
 
[[Bild:Tutorial_lektion8_lightmap.jpg|center]]
  
Problem gelöst. Wenn man das ganze nun noch mit [[Multitexturing]] verknüpft, so kann man eine Texturlage für die normale Textur verwenden und die 2. für das Licht... Happy Coding ;-)
+
Wenn man das ganze nun noch mit [[Multitexturing]] verknüpft, so kann man eine Texturlage für die normale Textur verwenden und die 2. für das Licht. Eine rote Spheremap erzeugt aber rotes Licht, gell... Happy Coding ;-)
 
 
Und noch ein kleiner Tipp für alle, die Models in ihren Programmen verwenden wollen, aber gerade zu faul sind, sich ein eigenes Dateiformat einfallen zu lassen oder die ihre 3D Modelle nicht erst in ihr eigenes Format konvertieren wollen: Das Programm MCAD, das ich in den Sources für dieses Tutorial auch verwendet hab, ist einfach eine gute Lösung für dieses Problem.
 
  
 
== Licht aus ==
 
== Licht aus ==
  
<pascal>
+
<source lang="pascal">
 
     glDisable(GL_LIGHTING)
 
     glDisable(GL_LIGHTING)
</pascal>
+
</source>
  
Und hiermit ist wieder eines meiner Tutorials am Ende. Wenn ich micht nicht irre ists die Nummer 11. Wer gibt mir den verdienten Schnaps aus? Na? Keine Freiwilligen? Schade :-( . In jedem Fall hast du es jetzt ersteinmal hinter dir und kannst dich enstpannen - oder, was mir besser gefallen würde: Ein paar OpenGL Programme mit Lichtunterstützung schreiben und uns das Ergebnis als BdW oder als ganzes einsenden. Ersatzweise tuts auch etwas freundliches Feedback - das lasst ihr nämlich allesamt immer sehr missen, was das Team zurecht schade findet. Also lasst uns nicht hängen, sondern gebt von euch, was eich an den Tutorials gefällt und was nicht.
+
Und wieder ist eines meiner Tutorials am Ende. Wenn ich mich nicht irre, ist's die Nummer 11. Wer gibt mir den verdienten Schnaps aus? Na? Keine Freiwilligen? Schade :-( . In jedem Fall hast Du es jetzt erst einmal hinter Dir und kannst Dich ganz entspannt zurücklehnen - oder, was mir besser gefallen würde: Ein paar OpenGL-Programme mit Lichtunterstützung schreiben und uns das Ergebnis als IOTW oder als solches einsenden. Ersatzweise tut's auch etwas freundliches Feedback - das lasst ihr nämlich allesamt immer sehr missen, was das Team zurecht schade findet. Also lasst uns nicht hängen, sondern gebt von euch, was euch an den Tutorials gefällt und was nicht.
  
 
und jetzt:
 
und jetzt:
Zeile 326: Zeile 320:
 
...have a lot of fun!
 
...have a lot of fun!
  
[[Benutzer:Nico Michaelis|Delphic]]
+
'''[[Benutzer:Delphic|Delphic]]
 +
& Co-Autor [[Benutzer:Traude|Traude]]'''
  
 
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 7]] | [[Tutorial 2D]] }}
 
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 7]] | [[Tutorial 2D]] }}
  
 
[[Kategorie:Tutorial|Lektion8]]
 
[[Kategorie:Tutorial|Lektion8]]

Aktuelle Version vom 21. März 2012, 15:58 Uhr

Das Wesen von Hell und Dunkel - Licht

Licht an

Hi, und wieder melde ich mich mit einem neuen Tutorial zurück. Heutiges Thema: Licht. Ohne Licht wäre die Umwelt dunkel und kein Pflänzchen könnte Zucker mittels Photosynthese erzeugen. Gäbe es kein Licht, so gäbe es die Welt nicht, wie wir sie kennen. Um in den eigenen OpenGL Programmen eine auch nur annähernd realistische Atmosphäre zu erzeugen ist Licht entsprechend unabdingbar. Man kann Szenen dadurch einen düsteren, geheimnisvollen oder auch einen farbenfrohen, fast kitschigen Stil verleihen.

Arten des Lichts

In OpenGL gibt es unterschiedliche Arten des Lichts. Umgebungs-Licht (Ambient) ist eine Art des Lichts, dessen Richtung nicht zu erkennen ist. Es entsteht durch mehrfache Reflexion an Wänden oder anderen Flächen. Entsprechend gut vertreten ist es in Räumen, aber auch in der freien Natur umgibt uns nicht zu wenig davon. An sich ist es das am schwierigsten zu berechnende, aber in vielen Szenen reicht es aus, eine Konstante vorzugeben - so macht es jedenfalls OpenGL.

"Diffuses" Licht (Diffuse) dagegen lässt die Richtung aus der es kommt erkennen. Je stärker eine Seite in das Licht gehalten wird, desto heller erscheint die Oberfläche. Je weiter man eine Fläche vom Licht abwendet, desto dunkler wird die Oberfläche. Die eintreffenden Lichtstrahlen werden in alle Richtungen verteilt, so dass die Oberfläche von allen Augenpositionen gleich hell erscheint.

Glanz (Specular) kommt aus einer bestimmten Richtung und tendiert dazu, in einer bestimmten Richtung besonders hell zu erscheinen. Plastik, gebürstetes oder nicht matt lackiertes Metall oder auch geschliffene Edelsteine haben einen hohen Glanzanteil, während Holz, Kohle und Wandfarbe so gar nicht glänzen wollen.

Materialeigenschaften

Der vom Licht erzeugte Eindruck hängt aber nicht nur vom Licht selbst ab, sondern auch vom beleuchteten Material. Eine Kohleplatte wirkt im selben Licht anders, als eine polierte Edelstahloberfläche oder das Blech eines lackierten Autos. Deshalb gibt es in OpenGL die sog. Materialeigenschaften. Sie entsprechen denen des Lichts selbst, nur geben sie den Reflexionsgrad und nicht die Intensität wieder.

Info DGL.png Hier im Wiki findet ihr in der Materialsammlung eine Auswahl von Parameterwerten.

Objekte können nun aber auch selbst Licht emittieren. Deshalb befindet sich unter den Materialeigenschaften zusätzlich noch ein Emissionswert. Ein Objekt mit Emission ist also auch bei kompletter Dunkelheit noch sichtbar, so z.B. die grüne Leuchtdiode an meinen Boxen, wenn ich die Rollläden geschlossen habe. Schade nur, dass OpenGL dieses emittierte Licht nicht auch auf andere Objekte überträgt :-(.

Normalen

OpenGL berechnet die Farbe des Lichts, das auf ein Objekt trifft mit Hilfe der Richtung aus der das Licht kommt und der Ausrichtung der Oberfläche eines Vertex. Nun stellt sich die Frage: in welche Richtung zeigt denn die Oberfläche? Wir können bei der Übergabe der Vertexdaten einfach mit glNormalx die Normalen angeben. Die Länge des Normalenvektors sollte 1 sein. Bei einer Kugel entspricht die Normale übrigens immer der Richtung des Mittelpunktes zum betrachteten Vertex. Für kompliziertere Gegenstände gibt es weiter unten ein paar Tipps zum Berechnen der Normalen auf "Nachbarschaftsbasis".

Erste Lichtstrahlen

Licht an

Tutorial lektion8 test.gif

Es wird Zeit. Machen wir erste Gehversuche mit OpenGL Licht! Wir brauchen dazu nicht viel zu tun. Mit glEnable(GL_LIGHTING) schalten wir die Lichtberechnung scharf und müssen dann eine Lichtquelle aktivieren. Das geht mit glEnable(GL_LIGHTx) (Zur Information: OpenGL Implementationen müssen 8 Lichter unterstützen. Es gibt aber Implementationen, die hier mehr zu bieten haben. Um Zugriff auf alle vorhandenen Lichter zu erhalten, genügt es zu GL_LIGHT0 einen entsprechenden Wert zu addieren. GL_LIGHT0+8 wäre dann z.B. das 9. Licht).


  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_COLOR_MATERIAL);

Sonnenbrand erzeugen

Der nächste Schritt besteht darin, die Eigenschaften der Lichtquelle(n) an OpenGL weiterzureichen. Dies läuft immer mit der Funktion glLightx. Sie nimmt 3 Parameter: light: Die Lichtquelle die gerade eingestellt wird. pname: Die Eigenschaft, die wir manipulieren möchten. params: Der neue Wert der Eigenschaft.

Mit pname dürfen wir aus einer ganzen Sammlung von Eigenschaften schöpfen:

Eigenschaft Standard Wert Bedeutung
GL_AMBIENT 0.0, 0.0, 0.0, 1.0 Ambienter RGBA Anteil des Lichtes
GL_DIFFUSE 1.0, 1.0, 1.0, 1.0 Diffuser RGBA Anteil des Lichtes
GL_SPECULAR 1.0, 1.0, 1.0, 1.0 Glanz RGBA Anteil des Lichtes
GL_POSITION 0.0, 0.0, 1.0, 0.0 Koordinaten des Lichtes
GL_SPOT_DIRECTION 0.0, 0.0, -1.0 Richtung des Spotlights
GL_SPOT_EXPONENT 0.0 Spotlight-Exponent
GL_SPOT_CUTOFF 180.0 Spotlight Sperrwinkel
GL_xxx_ATTENUATION ... Abschwächung des Lichtes mit der Entfernung

Materialeigenschaften übergeben

Das gleiche Spielchen müssen wir mit den Materialeigenschaften treiben. Diesmal ist die Funktion glMaterialx der Dreh und Angelpunkt. Die Parameterfolge ist die gleiche wie bei glLightx.

Eigenschaft Standard Wert Bedeutung
GL_AMBIENT 0.2, 0.2, 0.2, 1.0 RGBA Reflexionsgrad von ambientem Licht
GL_DIFFUSE 0.8, 0.8, 0.8, 1.0 RGBA Reflexionsgrad von diffusem Licht
GL_SPECULAR 0.0, 0.0, 0.0, 1.0 RGBA Glanzreflexionsgrad des Lichtes
GL_SHININESS 0.0 Glanz-Exponent
GL_EMISSION 0.0, 0.0, 0.0, 1.0 RGBA Emission des Materials
GL_COLOR_INDEXES 0, 1, 1 Ambient, diffuse, specular Farbindices

Beispiellicht

Das Beispiel mit den Kugeln wurde mit folgendem Code erzeugt:

  procedure TStationarySphereState.RenderState;
  const
    mat_specular   : Array[0..3] of GlFloat = (1.0, 1.0, 1.0, 1.0);
    mat_shininess  : Array[0..0] of GlFloat = (50.0);
    mat_ambient    : Array[0..3] of GlFloat = (0.4, 0.4, 0.4, 1.0);
    mat_diffuse    : Array[0..3] of GlFloat = (0.4, 0.8, 0.4, 1.0);

    light_position : Array[0..3] of GlFloat = (10.0, 10.0, 0.0, 1.0);
    light_ambient  : Array[0..3] of GlFloat = (0.8, 0.8, 0.8, 1.0);
    light_diffuse  : Array[0..3] of GlFloat = (0.8, 0.8, 0.8, 1.0);

  begin
    glMaterialfv(GL_FRONT, GL_SPECULAR,  @mat_specular[0]);
    glMaterialfv(GL_FRONT, GL_SHININESS, @mat_shininess[0]);
    glMaterialfv(GL_FRONT, GL_AMBIENT,   @mat_ambient[0]);
    glMaterialfv(GL_FRONT, GL_DIFFUSE,   @mat_diffuse[0]);

    glLightfv(GL_LIGHT0, GL_AMBIENT,  @light_ambient[0]);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  @light_diffuse[0]);
    glLightfv(GL_LIGHT0, GL_POSITION, @light_position[0]);

    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
     ...Szene Rendern...
  end;

Etwas schwächer bitte

Viele Lichtquellen werden mit der Entfernung schwächer. Bei unserer Sonne tritt dieser Effekt so gut wie nicht auf, bei kleinen Funzeln dagegen ist recht bald die maximale Leuchtweite erreicht. Diesen Effekt können wir auch mit OpenGL nachahmen.

Tutorial lektion8 attenuation.gif

Auf diesem Bild ist schön zu erkennen, dass die Kugeln rechts oben heller sind als die links unten, welche weiter von der Lichtquelle entfernt sind (wer nichts sieht, spiele doch mal bitte mit dem Kontrast und der Helligkeit des Monitors - ich kann nicht versprechen, dass der Effekt auf jedem Monitor so schön wie auf meinem herauskommt). Wie wird nun dieser Effekt erzeugt?

OpenGL kennt einen sog. Attenuation Factor:

Attenuation Factor = 1 / (kc + kl*d + kq*d*d)

  • d = Entfernung zwischen dem berechneten Vertex und der Lichtquelle
  • kc = GL_CONSTANT_ATTENUATION
  • kl = GL_LINEAR_ATTENUATION
  • kq = GL_QUADRATIC_ATTENUATION

Dieser wird Faktor bei der Lichtberechnung einfach mit den Lichtstärken multipliziert, so dass das Licht stärker oder schwächer wird. Übergeben werden die Werte wieder mittels glLightx:

  glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0);
  glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.001);
  glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.004);

Das Positionsproblem

Lichter sind unterschiedlich - Manche sind fest an den Kopf des Betrachters gekoppelt, andere irgendwo in der Szene platziert und wieder andere sind unendlich weit entfernt und ihr Licht trifft parallel auf die Gegenstände. All dies will simuliert werden. OpenGL hält es parat. Uns kommt auch noch zugute, dass OpenGL die Übergabe der Licht Position genauso behandelt wie die eines Vertex und entsprechend durch die Modelview Matrix jagt.

Unendlich weit entfernte Lichter

Das Universum, die Milchstraße, der äußere Ring, unser Sonnensystem, die Sonne und unsere Erde. Das Licht, das unsere Sonne ausstrahlt und bei uns auf der Erde ankommt, trifft uns in nahezu parallelen Lichtstrahlen. Damit verhält sich die Sonne wie eine unendlich entfernte Lichtquelle. Wenn wir eine solche Lichtquelle angeben wollen, ist also nicht ihre Position interessant (wobei das auch ginge), sondern allein die Richtung aus der das Licht kommt. Wie? Wir setzen die w-Koordinate(=die letzte Koordinate) der Position auf 0. Unsere unendlich weit entfernte Lichtquelle ist komplett. Sinnigerweise ist dann im übrigens auch Attenuation abgeschaltet.

Stationäre Lichtquellen

Lichtquellen, die an einer bestimmten Stelle in der Szene stehen, müssen mit der Kamerabewegung mitgeschoben werden. Entsprechend wird das Licht angegeben werden, bevor die Modelviewmatrix durch Objekte verschoben wird, aber nachdem die Kamera initialisiert wurde.

  glLoadIdentity;
  //..glRotatef, glTransaltef.. für die Kamera
  glLightfv(GL_LIGHT0, GL_POSITION, @position[0]);
  glPushMatrix;
    //..glRotatef, glTransaltef.. für die Objekte
    DrawObjekts;
  glPopMatrix;

Relatives Licht

Wollen wir stattdessen unsere Lichtquelle relativ zu einem bestimmten Objekt bewegen, so müssen wir ein wenig anders vorgehen: Erst die Modelviewmatrix so bearbeiten, dass wir das Objekt zeichnen könnten, vor dem Zeichen selbst jedoch die Matrixfunktion für das Licht ausführen und die Lichtposition übergeben:

  //..glRotatef, glTransaltef, glMultMatrix, etc...
  glPushMatrix;
    glRotatef(Drehung, 0.0, 1.0, 0.0);
    glLightfv(GL_LIGHT0, GL_POSITION, @position[0]);
  glPopMatrix;
  DrawObject;

Lichtquellen, die an der Kamera kleben

Wenn eine Lichtquelle immer relativ zum Betrachter sein soll(wenn also das Licht z. B. feste am Kopf montiert ist, wie bei einem Minenarbeiter), so langt es die Position des Lichts zu definieren, bevor die Modelviewmatrix mit der Kameraposition und Drehung bearbeitet wurde:

  glLoadIdentity;
  glLightfv(GL_LIGHT0, GL_POSITION, @position[0]);

Spot an!

Wer kennt sie nicht, die Spotlights? Im Fernsehen und Theater oft gebraucht, aber auch im realen Leben, z.B. bei Taschenlampen, Suchscheinwerfern (etwa noch keinen in der Hand gehabt? Ich schon :-) ), etc. oft zu sehen. Leider benötigt man nicht selten eine ganze Schar von ihnen, wozu sich die OpenGL Lichter nur schlecht eignen - aus der Patsche hilft da oft Blending oder andere Techniken. Wenn man mit entsprechenden Projektions-Techniken noch keine Erfahrungen hat, müssen die OpenGL-Lichter dann aber doch wieder herhalten.

Parameter setzen

Tutorial lektion8 spotlight.gif

Schauen wir uns die Lichtparameter für Spotlights an: GL_SPOT_DIRECTION gibt die Richtung an, in die das Spotlicht zeigt. Diese lässt sich wieder mit Hilfe geschickter Platzierung im Code durch die Modelviewmatrix jagen um den gewünschten Effekt zu erzielen - wie immer eben. GL_SPOT_CUTOFF gibt den Winkel an, der zwischen Richtung und max. Auswurf besteht. Zuletzt gibt es noch den GL_SPOT_EXPONENT, der beschreibt, wie stark die Lichtstärke nach außen hin abnimmt.

const
    light_position : Array[0..3] of GlFloat = (0.0, 10.0, 0.0, 1.0);
    light0_ambient  : Array[0..3] of GlFloat = (0.8, 0.0, 0.0, 1.0);
    light0_diffuse  : Array[0..3] of GlFloat = (0.8, 0.0, 0.0, 1.0);
  var
    LightDirection : Array[0..2] of GlFloat;
  begin
    LightDirection[0] := Sin(Position)/2;
    LightDirection[1] := -1.0;
    LightDirection[2] := 0.0;
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, @LightDirection[0]);

    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 10.0);
    glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 50.0);

    glLightfv(GL_LIGHT0, GL_AMBIENT,  @light0_ambient[0]);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  @light0_diffuse[0]);
    glLightfv(GL_LIGHT0, GL_POSITION, @light_position[0]);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
    //...
  end;

Zur besseren Illustration ein Bild

Tutorial lektion8 spots highres.gif

glLightModel?

Bislang haben wir einen Befehl völlig außer Acht gelassen: glLightModelx. Er nimmt zwei Parameter: pname und param:

Eigenschaft Standard Wert Bedeutung
GL_LIGHT_MODEL_AMBIENT 0.2, 0.2, 0.2, 1.0 Abientes RGBA Licht der gesamten Szene
GL_LIGHT_MODEL_LOCAL_VIEWER 0.0 oder GL_FALSE Lokaler oder unendlicher Viewport. Wirkt bei Specular Reflexionen:
GL_FALSE
Die Richtung von Betrachter zu Vertex wird nur 1 mal berechnet und bleibt damit für alle Vertices konstant.
GL_TRUE
Die Richtung Betrachter zu Vertex wird für jedes Vertex neu berechnet, was in einem realistischeren Anblick endet.
GL_LIGHT_MODEL_TWO_SIDE 0.0 oder GL_FALSE Einseitiges oder doppelseitiges Lighting

Das Vertexproblem

Die Benutzung von Licht muss bislang auf euch sehr einfach gewirkt haben. Aber der eine oder andere wird es vielleicht schon herausgelesen haben: OpenGL Licht wird per Vertex und nicht per Pixel berechnet. Das Problem dabei ist, dass sich mit der Auflösung des Objekts das Aussehen des Lichts stark ändern kann. Bei diffusem und ambientem Licht spielt das kaum eine Rolle. Specular und Spotlights reagieren empfindlich. Die schönen Rundungen, die diese Lichtarten häufig ausbilden, können verschandelt werden. Die Ergebnisse sind nicht mehr wirklich rund, vielmehr eckig und kantig. Ihr glaubt mir nicht? Dann schaut nicht zu genau hin:

Tutorial lektion8 spots lowres.gif

Und was lernen wir draus? Schönes Licht braucht viele Vertices.

Normalen die 2.

Länge = 1?

Kommen wir also nun wie versprochen noch einmal zu den Normalen zurück. Normalen, die mit glNormalx angegeben werden, werden, abgesehen von Verschiebungen, wie Vertices mit der Modelview Matrix multipliziert. Normalen sollten für korrekte Lichtberechnung normiert sein, dass heißt die Länge 1 haben (ist sie länger, so erscheint das Objekt heller, ist sie kürzer, so wird das Objekt dunkler). Ist mit der Multiplikation mit der Modelviewmatrix auch eine Skalierung verbunden, so haben wir ein Problem, denn die Länge ist nicht mehr 1. Um das zu beheben, kann vermittels glEnable(GL_NORMALIZE) OpenGL angewiesen werden, die Normalen selbst auf Länge eins zu skalieren. Aber Achtung: Das kostet Rechenleistung. Allerdings kostet selber berechnen vermutlich noch mehr.

Einfache Normalen

Manchmal ist die Berechnung von Normalen ganz einfach. So bei Kugeln. Die Richtung die der Mittelpunkt zu irgendeinem Vertex auf der Oberfläche beschreibt, ist gleich der Richtung der Normalen - sie muss nur noch normiert werden. Bei Flächen auf den Achsen muss unsereins auch nicht groß nachdenken, wie denn die passende Normale auszusehen hat. Aber wie ist es, wenn unser betrachteter Körper keine so einfache Oberfläche hat? Was dann?

Normalenberechnung per Dreieck

Will man anhand von Dreiecksdaten Normalen erzeugen, so muss man mal wieder zur Mathematik rüberschauen - die haben das ideale Werkzeug dafür: Das Vektor-Kreuzprodukt. Um die Normale der Fläche beschrieben durch die 3 Ecken V1, V2, V3 zu bekommen, braucht man nur zu rechnen:

N = (V1 - V2)x(V2 - V3)

Wenn wir das für jedes Polygon durchziehen, dann können wir für jeweils 3 Vertices die Normale angeben. Das schaut ein bisschen eckig aus, bringt uns unserem Ziel aber gewaltig näher. Erinnert irgendwie an die ersten Spiele, die mit Licht arbeiteten... Flatshading war schon was schönes, aber OpenGL kann mehr, wie wir bereits an den Kugeln gesehen haben.

Tutorial lektion8 normalen flat.gif

Und nicht vergessen: Die Normalen sollten noch auf die Länge 1 gebracht werden, siehe Normale.

Normalen glätten

Tutorial lektion8 normalen smooth.gif

Um die Polygone nun ein wenig unsichtbarer zu machen, gibt es ein einfaches Hilfsmittel: Man nehme die direkt anliegenden Flächennormalen um ein Vertex, bilde den Durchschnitt und voilà: Unser Objekt schaut schon runder aus. An einigen Stellen sollte man damit aber vorsichtig walten: Geht es einmal wirklich scharf um eine spitze Kante, so schaut diese Abrundung vermutlich etwas seltsam und abnormal aus. Also: Nicht einfach drauflos abrunden. Da bei komplexeren Objekten jeder vermutlich sowieso einen 3D Modeller verwendet, bei dem man das etwas feinfühlig kontrollieren kann, sollte das kein größeres Problem sein - jeder der seinen eigenen Modeller schreibt, muss sich natürlich etwas einfallen lassen - nützlich erweist sich hier das Skalarprodukt, da man damit den Winkel zwischen zwei Normalen bestimmen kann. Ist der Winkel zu groß, kann es sich nicht um eine Rundung handeln und wir haben es mit einer Kante zu tun - zumindest solange unsere Polygondaten halbwegs fein aufgelöst sind.

Das etwas andere Licht

Tutorial lektion8 RoteRinge.jpg

Mit OpenGLs Standardmethode einen Beleuchtungseffekt zu erzielen, ist natürlich nicht der Weisheit letzter Schluss. Ein anderer Trick drängt sich bei entsprechendem Wissen über die Möglichkeiten geradezu auf: Environment Mapping, oder im folgendem speziell Sphere Mapping. Den entstehenden Effekt kann man mit einer perfekten Spiegelung vergleichen. Das einzige was wir tun müssen, ist OpenGL anzuweisen, die Texturkoordinaten selbst zu berechnen:

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);

Das wars schon, wir benötigen nur noch eine Sphere Map:

Tutorial lektion8 lightmap.jpg

Wenn man das ganze nun noch mit Multitexturing verknüpft, so kann man eine Texturlage für die normale Textur verwenden und die 2. für das Licht. Eine rote Spheremap erzeugt aber rotes Licht, gell... Happy Coding ;-)

Licht aus

    glDisable(GL_LIGHTING)

Und wieder ist eines meiner Tutorials am Ende. Wenn ich mich nicht irre, ist's die Nummer 11. Wer gibt mir den verdienten Schnaps aus? Na? Keine Freiwilligen? Schade :-( . In jedem Fall hast Du es jetzt erst einmal hinter Dir und kannst Dich ganz entspannt zurücklehnen - oder, was mir besser gefallen würde: Ein paar OpenGL-Programme mit Lichtunterstützung schreiben und uns das Ergebnis als IOTW oder als solches einsenden. Ersatzweise tut's auch etwas freundliches Feedback - das lasst ihr nämlich allesamt immer sehr missen, was das Team zurecht schade findet. Also lasst uns nicht hängen, sondern gebt von euch, was euch an den Tutorials gefällt und was nicht.

und jetzt:

...have a lot of fun!

Delphic & Co-Autor Traude


Vorhergehendes Tutorial:
Tutorial Lektion 7
Nächstes Tutorial:
Tutorial 2D

Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com.
Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen.