Welcome to the new Friends-of-FPC!

Here you can find all kinds of information about the FreePascal Compiler. We have many tutorials and howtos as well as a selection of tools to help you with your programming. We also have some example codes for you. And if you want to contribute some information/ sources/ tools yourself you can do so.
Also we have finally relaunched the FoFPC forum. It's your chance for some Q&A about everything FreePascal.

Friends-of-FPC

Tutorials: Learn how to code with FreePascal.

Source Codes: A collection of examples, miscellaneous source codes and open source stuff.

Tools and Help Files: Intro- duction of some tools that might help you with FPC.

Community

Forum: Ask or answer questions about the FreePascal Compiler, programming or just babble about coding.

Contribute! Contribute your own Tutorial, Source Codes or Tools and send them to us!

Website

About: Information about Friends-of-FPC.org.

Einführung in OpenGL - Teil 12 - by Delax

Dieses Mal geht es um Beleuchtung, oder zumindest die Grundlagen der Beleuchtung in OpenGL. Wir werden nur die Theorie machen, ein bischen Mathe ist auch mit von der Partie. Versucht einfach zu verstehen, was ich hier schreibe und macht euch die Beispiele klar.

Seht euch einmal in dem Raum um, wo ihr gerade steht/ sitzt/ was auch immer. Achtet darauf, wo Lichtquellen sind und wie sie sich auf die anderen Objekte im Raum auswirken. Schatten, Beleuchtung, Reflektionen und so weiter. All das hat mit der Beleuchtung der Umwelt zu tun. Und in OpenGL sind natürlich auch umfangreiche Möglichkeiten gegeben eine Szene zu beleuchten.

Im Grunde handelt es sich dabei um 3 Varianten von Licht und deren Abwandlungen. Einmal ambientes Licht (Ambient Light), diffuses Light (Diffuse Light) und direktes Licht (Specular Light). Doch vorsicht, denkt jetzt nicht gleich an eure Farb- und Lichtlehre auf dem Papier.

Ambientes Licht ist relativ einfach. Es ist Licht, welches von allen Seiten kommt. Es gibt zwar eine Lichtquelle, aber das Licht wird von überall her reflektiert, so das keine Quelle mehr auszumachen ist. Alle Objekte werden von allen Seiten gleichmäßig von ambientem Licht erhellt.

Diffuses Licht hat eine Lichtquelle, aber wird von einer Oberfläche in alle Richtungen reflektiert. Je näher die Lichtquelle an einem Objekt ist, desto heller wird es erleuchtet. Wenn eine Lichtquelle direkt auf eine Fläche strahlt, wird diese heller erleuchtet als eine geneigte Fläche. Bsp: haltet eine Fläche (ein Buch oder so) neben eine Lampe und schaut seitlich neben der Lampe darauf.

Direktes Licht ist ähnlich wie diffuses Licht, wird aber scharf und zielgerichtet reflektiert. Ein sehr helles, gerichtetes Licht erzeugt eine helle Reflektion wo es direkt auf Flächen auftrifft. Man sieht dieses nur, wenn sich die Fläche zwischen einem selbst und der Lichtquelle befindet, oder die Lichtquelle auf die Kante der Fläche scheint. Bsp: haltet eine Fläche vor die Lichtquelle und schaut in Höhe der Lichtquelle, mit Blick zur Lichtquelle auf die Fläche. Im Prinzip seht ihr eine Spiegelung der Lichtquelle auf der Fläche.

Es ist aber eigentlich nie der Fall, das eine Lichtquelle nur ein Charakteristika dieser 3 "Lichtarten" aufweist, sondern immer alle 3. Also hat jede Lichtquelle einen ambientem Anteil, einen diffusen Anteil und einen gerichteten Anteil. Aber diese Anteile können sehr gering ausfallen.

Jetzt ein Beispiel. Ein roter Laserstrahl hat zum Beispiel unter normalen Bedingungen keine ambienten und keine diffusen Anteile. Sobald aber der Strahl durch Rauch oder Staub den Strahl minimal ablenken, kommen die diffusen Anteile hinzu. Und wenn der Raum sehr dunkel ist, nehmt ihr einen schwachen roten Schimmer im Raum wahr. Das wäre der ambiente Anteil.

Jede Lichtart einer Lichtquelle hat auch eine Farbe, die zusammen die Färbung des Lichts ergeben, mit der die Lichtquelle die Szene erhellt. Es werden 4 Parameter für jedes Licht angegeben: Rot, Grün, Blau und Alpha. Den Alpha Kanal ignorieren wir, bis wir ihn später genauer unter die Lupe nehmen. Bleiben nur noch die RGB Werte und die kennt ihr ja schon von den vorherigen Farbeinstellungen.

Je heller die Farbe, desto heller auch das Licht. Also ein graues Licht (0.5,0.5,0.5) wäre nichts anderes, als ein weißes Licht, welches aber nur mit halber Intensität leuchtet. Unseren roten Laser würden wir etwas so beschreiben.

Typ Rot Grün Blau
Ambient 0.05 0.0 0.0
Diffuse 0.1 0.0 0.0
Specular 0.985 0.0 0.0

So, da haben wir ihn. Damit könnt ihr einen Laserpointer für euren nächsten 3D Ego-Shooter definieren. In der Zwischenzeit merkt euch das jede Art von Licht von 0.0 (min) bis 1.0 (max) reichen kann.

Kommen wir zu einer anderen Sache, die auch mit Licht zusammenhängt. Es geht um das Material der Flächen. Nun, eigentlich nicht direkt das Material, sondern eher die Farbe der Flächen. Eine blaue Fläche absorbiert alle Farben außer blau (daher die Farbe). Eine blaue Fläche wird also unter grünem Licht schwarz sein, da keine Blauanteile reflektiert werden können.

Und jetzt muß man OpenGL nur noch mitteilen welches Material unsere Flächen haben. Also nicht die Farbe an sich, sondern die Intensität, mit der bestimmte Farben reflektiert weden. Als Beispiel könnte man anführen, das eine rot lackierte Motorhaube das Licht durchaus anders reflektiert als ein rotes Tischtuch aus Stoff, obwohl beides rote Flächen sind.

Die Darstellung des ganzen ist eigentlich recht einfach. Wir splitten das Licht in die Bestandteile RGB auf (vorsicht, nicht mit realem Licht verwechseln. Künstler bekommen regelmäßig Anfälle, wenn Computergrafiker mit dem RGB System ankommen).

Die Bedeutung ist wesentlich unspektakulärer als die Darstellung. Der Rotanteil der Lichtquelle wird multipliziert mit dem Rotanteil der Reflektion und heraus kommt der reflektierte Rotanteil. Selbiges für Grün und Blau.

Beispiel: enthält die Lichtquelle einen Rotanteil von 0.8 und das Material einen Rotanteil von 0.9 wird 0.72 davon reflektiert und die Fläche hat den neuen Rotwert von 0.72 statt der ursprünglichen 0.9. Eigentlich ganz einfach, oder? Außerdem nimmt uns OpenGL die Berechnung dieses Materials ab.

Jetzt müssen wir OpenGL noch mitteilen in welche Richtung unsere Lichquelle scheint. Nicht immer hat man es mit Lichtquellen zu tun, die gleichmäßig in alle Richtungen leuchten. So oder so treffen die Lichtstrahlen in irgendeinem Winkel auf unserer Fläche auf und werden in einem Gegenwinkel wieder abgestrahlt (unter der Annahme das es sich nicht um ambientes Licht handelt und unsere Fläche nicht von der Lichtquelle abgeneigt oder verdeckt ist). Um das Licht korrekt wiederzugeben muß OpenGL in der Lage sein diese Winkel zu berechnen.

Und jetzt beginnt die Sache fies zu werden. Wie findet man einen Punkt auf einer Fläche, die aus Eckpunkten besteht? Und noch schlimmer - jeder Punkt innerhalb dieser Fläche wird getroffen. Und dann kommt noch dazu das es keine Möglichkeit gibt Winkel zwischen einem Punkt und einer Linie zu bestimmen, da ein Punkt keine wirkliche Dimension hat.

Wir helfen uns in so fern aus, als das wir selbst eine Linie definieren, die von unserer Fläche weg führt. Eine sogenannte "Normale" oder "normal vector". Diese Normale zeigt in die Richtung, die 90° von der Fläche weg führt. Es ist also die Richtung, in die die Fläche selbst zeigt.

Diese Normalen für Flächen zu finden kann manchmal gar nicht so leicht sein. Nicht jede Fläche ist brav in der horizontalen oder vertikalen gezeichnet. Je schräger die Fläche, desto schräger die Normale (logisch). Polygonen, Kurven oder unebenen Flächen wie Landschaften sind auch nicht zu verachten.

Um die Normale eines Polygons mit mindestens 3 Eckpukten zu bestimmen braucht man Vektorrechnung. Wer sein Abi schon hinter sich hat wird stöhnen, wer noch keine Ahnung von dem Thema hat darf sich jetzt anschnallen. Wenn ihr zu jung dafür seid oder einfach keine Lust auf den Mathequatsch (Würde mal sagen, wenn euch das Angst macht solltet ihr gleich die Finger von ernsthafter Grafikprogrammierung lassen) habt, dann nutzt einfach nur die Prozeduren aus den Sources. So oder so gebe ich hier eine kleine Einführung.

Bei 3 Punkten (P1, P2, P3) in einem Raum kann man von einem Punkt 2 Vektoren (V1 und V2) bilden. Von P1 nach P2 und von P1 nach P3. Um nun die Normale zu erhalten müssen wir von diesen Vektoren das Kreuzprodukt (V3 = V1 X V2) berechnen. Daraus resultiert ein neuer Vektor (V3), der unsere Normale darstellt.

Das Kreuzprodukt wird folgendermaßen berechnet: |V1 X V2| = (P1)(P2) sin(V1,V2). Da eine Sinusberechnung aber langsam ist suchen wir uns einen alternativen Weg, der da lautet:

Normalx = (vtx1y - vtx2y) * (vtx2z - vtx3z) - (vtx1z - vtx2z) * (vtx2y - vtx3y)
Normaly = (vtx1z - vtx2z) * (vtx2x - vtx3x) - (vtx1x - vtx2x) * (vtx2z - vtx3z)
Normalz = (vtx1x - vtx2x) * (vtx2y - vtx3y) - (vtx1y - vtx2y) * (vtx2x - vtx3x)

Da OpenGL aber nur Normale mit der Länge einer Einheit haben will müssen wir das Kreuzprodukt noch auf diese Länge anpassen. Das kann man zwar mit GL_NORMALIZE auch automatisch erledigen lassen, aber es selbst zu tun ist auf jeden Fall die bessere Lösung.

Im Grunde ist die "Normalisierung einer Normalen" nicht schwer. Man quadriere jede Koordinate eines Vertex, addiert das ganze und zieht die Quadratwurzel. Teilt jede Koordinate durch das Ergebnis und schon habt ihr einen Vektor, der in die gleiche Richtung zeigt, aber nur eine Einheit lang ist.

Einfaches Beispiel: 0.0, 9.0, 0.0. Länge dieser Normalen wäre 9.

0^2 = 0, 9^2 = 81, 0^2 = 0. 
0 + 81 + 0 = 81. 
81^(1/2) = 9. 
0/9 = 0, 81/9 = 1, 0/9 = 0. 

Neue Koordinaten (0.0, 1.0, 0.0). Länge der normalisierten Normalen ist eins. Billiges Beispiel, aber wirksam.

Und weil ich nicht mit Hilfemails bombardiert werden will, hier die fertige Prozedur. Aufgerufen mit je 3 beliebigen Vertices aus einem Polygon. Aber Achtung: auf jeden Fall die Vertices in CCW, also gegen den Uhrzeigersinn angeben. Das Ergebnis wird in CNormal gespeichert, einem Array[1..3] of real.

PROCEDURE FindNormal(v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z : real);
const	x = 1;
	y = 2;
	z = 3;
var	temp_v1, temp_v2 : array[1..3] of real;
	temp_lenght : real;
begin

temp_v1[x] := v1x - v2x;
temp_v1[y] := v1y - v2y;
temp_v1[z] := v1z - v2z;

temp_v2[x] := v2x - v3x;
temp_v2[y] := v2y - v3y;
temp_v2[z] := v2z - v3z;

// calculate cross product
CNormal[x] := temp_v1[y]*temp_v2[z] - temp_v1[z]*temp_v2[y];
CNormal[y] := temp_v1[z]*temp_v2[x] - temp_v1[x]*temp_v2[z];
CNormal[z] := temp_v1[x]*temp_v2[y] - temp_v1[y]*temp_v2[x];

// normalize normal
temp_lenght :=	(CNormal[x]*CNormal[x])+
		(CNormal[y]*CNormal[y])+
		(CNormal[z]*CNormal[z]);

temp_lenght := sqrt(temp_lenght);

// prevent n/0
if temp_lenght = 0 then temp_lenght := 1;

CNormal[x] /= temp_lenght;
CNormal[y] /= temp_lenght;
CNormal[z] /= temp_lenght;

end;

Nun solltet ihr die Prozedur nach euren Hausregeln tunen, in ASM Code umwandeln und fertig ist die Angelegenheit. Hey, seht mich nicht so ungläubig an ;)

Na gut, ich gebe euch noch einen Tip auf den Weg. In den folgenden Code-Beispielen werden wir die Prozedur nicht brauchen, weil wir nur auf einer Ebene arbeiten. Also sobald eine Ebene bei allen Vertices eines Polygons gleich ist, entspricht die Normale 1.0 auf dieser Ebene.

 glVertex3f( 3.0, 3.0,-3.0);
 glVertex3f( 3.0, 3.0, 3.0);
 glVertex3f( 3.0,-3.0, 3.0);
 glVertex3f( 3.0,-3.0,-3.0);
              ^
              |
= glNormal3f(1.0, 0.0, 0.0);

Alles klar? Und je nach Richtung der Fläche ist die Normale 1 oder -1 auf der Ebene. Wenn ihr keine gemeinsame Ebene habt benötigt ihr halt die FindNormal Prozedur. Und keine Sorge - mit ein paar Tricks geht die Performance nicht ganz so in den Keller wie ihr vielleicht denkt.

Ein paar Anmerkungen zum Schluß.

Licht ist enorm wichtig in OpenGL. Eine Szene wirkt nur dann realistisch, wenn sie auch realistisch ausgeleuchtet wird. Mit Licht lassen sich Stimmungen schaffen, Aufmerksamkeit auf Objekte lenken und vieles mehr. Es ist empfehlenswert sich ein Buch über Photographie zu besorgen, oder noch besser, speziell eins was sich nur mit dem Thema Be- und Ausleuchten beschäftigt. Die Bibliothek von nebenan tut's oft auch.

Die Frage "Wann lassen wir OpenGL etwas berechnen und wann berechnen wir es selbst?" wie bei der normalisierung von Normalen stellt sich automatisch. Die Antwort lautet etwa so: "Sobald die Mehrheit der Grafikkarten dieses Feature in Hardware unterstützt, sollte man die Arbeit immer OpenGL überlassen". Was auch Sinn macht, denn wenn die Grafikkarte die ganze Arbeit macht, kann man noch so sehr optimieren, man wird nur schwer an die Leistung der Karte herankommen. Wenn man allerdings davon ausgehen kann, das so wie so nur der Treiber die ganze Arbeit macht, können wir auch selbst Algorithmen entwickeln und tunen. Oftmals können wir dann auch Aufgaben effektiv verbinden (wie das berechnen und anschließende normalisieren von Normalen). Macht Sinn? Oh, und wie ihr herausfindet welche Karten was unterstützen? Schaut mal in die Handbücher eurer Karten. Oder wenn es da nicht steht in die Whitepapers. Gibt es meißtens direkt bei den Herstellern.

Delax/ Sundancer Inc.
[delax@sundancerinc.de]

Back to previous page

Useful Links









Link to us