Tutorial Lektion 7: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Was ist den nun blenden?)
K (Und es ward Licht…)
 
(12 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 67: Zeile 67:
 
=== Claim for the World Order ===
 
=== Claim for the World Order ===
  
Wie alles hat auch das Blending einen kleinen Schönheitsfehler. Viele Leute denken immer wieder, dass auch beim Blending praktisch eine 3D-Welt existiert. Das ist jedoch falsch, da die Quelle ja im Frame-Buffer bereits vorliegt. Praktisch bereits fertig gerendert. Es macht also für das Blending einen enormen Unterschied, in welcher Reihenfolge die Objekte zur Kamera hin gerendert werden.
+
Wie alles hat auch das Blending einen kleinen Schönheitsfehler. Viele Leute denken immer wieder, dass auch beim Blending praktisch eine 3D-Welt existiert, d.h. man hinrendern kann wo man will. Das ist jedoch falsch, da die Quelle ja im Frame-Buffer bereits vorliegt. Praktisch bereits fertig gerendert. Es macht also für das Blending einen enormen Unterschied, in welcher Reihenfolge die Objekte zur Kamera hin gerendert werden.
  
Würden wir zum Beispiel ein Haus mit einem Fenster render wollen und das Fensterglas mit Hilfe von Blending darstellen und wir würden zunächst das Glas rendern, so würde OpenGL dieses mit dem Hintergrund (vermutlich ne Himmel) blenden und dann anschließend das Haus drum herum zeichnen. Der ahnungslose Programmierer wird dann mit großen Augen vor dem Fenster stehen und sich wundern, wieso das Fenster so merkwürdig aussieht und man praktisch durch das Glas ins Freie sehen kann. Würden wir kein Blending haben, so würde es nicht weiter auffallen in welcher Reihenfolge wir die Objekte gerendert haben.
+
Würden wir zum Beispiel ein Haus mit einem Fenster render wollen und das Fensterglas mit Hilfe von Blending darstellen und wir würden zunächst das Glas rendern, so würde OpenGL dieses mit dem Hintergrund (vermutlich dem Himmel) blenden und dann anschließend das Haus drum herum zeichnen. Der ahnungslose Programmierer wird dann mit großen Augen vor dem Fenster stehen und sich wundern, wieso er durch die hintere Wand des Hauses ins Freie sehen kann.  
  
Darum gilt, alle Objekte rendern, bevor man zum Blending greift. Man sollte sich also durchaus Gedanken dazu machen, wie man seine Objekte rendert. Es gibt verschiedene Verfahren seine Objekte so ordnen zu lassen, dass das hier beschrieben Problem nicht auftritt, darauf möchte ich nicht näher eingehen, ihr seit gewarnt worden ;D Eigentlich ist es ein recht logischer Vorgang, allerdings erliegt man doch recht schnell diesem kleinen Irrglauben ;)
+
Kurz: Auch durchsichtige Objekte sind Objekte. Auch sie schreiben Werte in den [[Tiefenpuffer]]. Deshalb kann man nicht "hinter" durchsichtige Objekte etwas rendern.
 +
 
 +
Ohne Blending, würde so etwas nicht weiter auffallen, mit Blending muss man Acht geben in welcher Reihenfolge wir die Objekte rendern.
 +
 
 +
Darum gilt: '''alle undurchsichtigen Objekte rendern, bevor man zum Blending greift.''' Man sollte sich also durchaus Gedanken dazu machen, wie man seine Objekte rendert. Es gibt verschiedene Verfahren seine Objekte so ordnen zu lassen, dass das hier beschrieben Problem nicht auftritt, darauf möchte ich nicht näher eingehen, ihr seid gewarnt worden ;D Eigentlich ist es ein recht logischer Vorgang, allerdings erliegt man doch recht schnell dem kleinen Irrglauben hinter eine Scheibe rendern zu können. ;)
  
 
=== Gleichheit für Alles! ===
 
=== Gleichheit für Alles! ===
Zeile 135: Zeile 139:
 
|}
 
|}
  
Sieht anfangs etwas verwirrend aus, aber der Mensch kann sich an vieles gewöhnen. Immerhin lest ihr bereits seit einiger Zeit Texte, die mit meinem Rechtschreibfehlern und meiner Sprache angereichert sind ;D
+
Sieht anfangs etwas verwirrend aus, aber der Mensch kann sich an vieles gewöhnen. Immerhin lest ihr bereits seit einiger Zeit Texte, die mit meinen Rechtschreibfehlern und meiner Sprache angereichert sind ;D
  
 
Um sicher zu gehen, dass auch wirklich alles verstanden ist nehmen wir uns noch einmal kurz folgenden Aufruf zu herzen:
 
Um sicher zu gehen, dass auch wirklich alles verstanden ist nehmen wir uns noch einmal kurz folgenden Aufruf zu herzen:
  
 
<source lang="pascal">
 
<source lang="pascal">
   glBlendFunc(GL_SRC_COLOR,GL_ONE);
+
   glBlendFunc(GL_ONE, GL_SRC_COLOR); // Zur Info: GL_SRC_COLOR darf nur auf
 +
                                    // den Paramter destination angewendet
 +
                                    // werden, siehe dazu obenstehende Tabelle
 
</source>
 
</source>
  
Zeile 160: Zeile 166:
  
 
<source lang="pascal">
 
<source lang="pascal">
   glBlendFunc(GL_ONE,GL_DST_COLOR);
+
   glBlendFunc(GL_DST_COLOR, GL_ONE); // Zur Info: GL_DST_COLOR darf nur
 +
                                    // auf den Parameter source angewendet
 +
                                    // werden. Siehe dazu obenstehende Tabelle.
 
</source>
 
</source>
  
Zeile 177: Zeile 185:
 
</source>
 
</source>
  
Besonders hoch bei der Quelle werten wir in diesem Fall das Schwarz. Bei der Zielfarbe bewerten wir die Farbe sehr hoch die die zweite Textur hat. D.h. alles was schön weiß ist wird entsprechend geblendet. Wer sich nun am Kopf kratzt sollte sich ein wenig Code schnappen und ein wenig damit herum spielen. Recht schnell merkt man, wie es funktioniert, den Blending ist unter OpenGL gar nicht so schwer! Am besten auch gleich die obere Tabelle ausdrucken und daneben liegen haben ;)
+
Besonders hoch bei der Quelle werten wir in diesem Fall das Schwarz. Bei der Zielfarbe bewerten wir die Farbe sehr hoch die die zweite Textur hat. D.h. alles was schön weiß ist wird entsprechend geblendet. Wer sich nun am Kopf kratzt sollte sich ein wenig Code schnappen und ein wenig damit herum spielen. Recht schnell merkt man, wie es funktioniert, denn Blending ist unter OpenGL gar nicht so schwer! Am besten auch gleich die obere Tabelle ausdrucken und daneben liegen haben ;)
  
 
=== Der Überläufer ===
 
=== Der Überläufer ===
Zeile 207: Zeile 215:
 
   glEnd;
 
   glEnd;
  
   glDisable(GL_DEPTH_TEST); // wichtig da wir sonst das erste Quad verwerfen
+
   glDisable(GL_DEPTH_TEST); // Wichtig! Da wir sonst das erste Quad verwerfen
                             // und damit auch die Textur, kann auch in der Init
+
                             // und damit auch die Textur, der Aufruf könnte auch  
                             // Funktion stehen.
+
                             // in einer Init Funktion stehen.
 
   glEnable(GL_BLEND);
 
   glEnable(GL_BLEND);
 
   glBlendFunc(GL_SRC_ALPHA,GL_DST_ALPHA);
 
   glBlendFunc(GL_SRC_ALPHA,GL_DST_ALPHA);
Zeile 255: Zeile 263:
 
=== Und es ward Licht… ===
 
=== Und es ward Licht… ===
  
Ein wirklich tolles Spielzeug, welches einen mit Blending in die Hand gelegt wird sind "Lightmaps". Vielleicht hat jemand von Euch bereits einmal versucht in seinem Projekt so schöne Lichtquellen einzubauen, wie er es aus bekannten kommerziellen Spielen kennt und hat sofort anschließend ein langes Gesicht gezogen als die Augen auf die Framerate blickten. Dazu kommt, dass das OpenGL-Licht meist nicht einmal besonders ansehnlich ist. Ärgerlich…
+
Ein wirklich tolles Spielzeug, welches einem mit Blending in die Hand gelegt wird sind "Lightmaps". Vielleicht hat jemand von Euch bereits einmal versucht in seinem Projekt so schöne Lichtquellen einzubauen, wie er es aus bekannten kommerziellen Spielen kennt und hat sofort anschließend ein langes Gesicht gezogen als die Augen auf die Framerate blickten. Dazu kommt, dass das OpenGL-Licht meist nicht einmal besonders ansehnlich ist. Ärgerlich…
  
 
Doch es gibt natürlich eine Möglichkeit schnell und effizient mit wenig Aufwand genauso schöne Lichter in seinem Projekt einzubauen. Das Geheimnis ist schlicht und ergreifend, dass wir nicht auf OpenGL-Lichter aufbauen, sondern einen kleinen Trick auf unsere Benutzer spielen. Wir zeichnen vor dem Zielobjekt eine neue Texture, die nur aus Graustufen besteht. Je heller ein Punkt ist, desto mehr soll er anschließend leuchten.
 
Doch es gibt natürlich eine Möglichkeit schnell und effizient mit wenig Aufwand genauso schöne Lichter in seinem Projekt einzubauen. Das Geheimnis ist schlicht und ergreifend, dass wir nicht auf OpenGL-Lichter aufbauen, sondern einen kleinen Trick auf unsere Benutzer spielen. Wir zeichnen vor dem Zielobjekt eine neue Texture, die nur aus Graustufen besteht. Je heller ein Punkt ist, desto mehr soll er anschließend leuchten.
Zeile 275: Zeile 283:
  
 
<source lang="pascal">
 
<source lang="pascal">
   glBlendFunc(GL_SRC_COLOR,GL_DST_COLOR);
+
   glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
 
</source>
 
</source>
  
Zeile 297: Zeile 305:
 
  # Objekt transformieren
 
  # Objekt transformieren
  
Klingt soweit auch alles logisch! Wie bereits jedoch erwähnt frisst vor allem das transformieren die Rechenpower und in unserem Fall liegt das Objekt übereinander, es wäre eine Transformation der Vertizen eigentlich gar nicht nötig. Wir müssten also nur die beiden Texturen auf ein transformiertes Objekt bekommen. Würden wir einfach nur zwei Texturen hintereinander setzen, so würde OpenGL die zuletzt gewählte verwenden und die andere schlichtweg ignorieren. Die meisten heutigen Grafikkarten (z.B: GeForce2 MX) haben zwei Textur-Units. Das muss man sich vorstellen wie zwei Schubladen. In beiden legen wir eine Textur rein und wenn OpenGL rendert werden reingeschaut und beide auf das Ziel gepappt. Somit haben wir uns eine Transformation gespart, was vor allem wenn man sehr viel mit Lightmaps arbeitet schon ein beachtlicher Geschwindigkeitsgewinn von knapp 50% ist! Einige sehr moderne Grafikkarten haben bereits 4 Texturen-Units und die Zeit bis es welche mit 8 gibt ist auch schon bereits voraussehbar ;)
+
Klingt soweit auch alles logisch! Wie bereits jedoch erwähnt frisst vor allem das transformieren die Rechenpower und in unserem Fall liegt das Objekt übereinander, es wäre eine Transformation der Vertizen eigentlich gar nicht nötig. Wir müssten also nur die beiden Texturen auf ein transformiertes Objekt bekommen. Würden wir einfach nur zwei Texturen hintereinander setzen, so würde OpenGL die zuletzt gewählte verwenden und die andere schlichtweg ignorieren. Die meisten heutigen Grafikkarten (z.B: GeForce8800 GTX) haben 64 Textur-Units. Das muss man sich vorstellen wie 64 Schubladen. In die ersten beiden legen wir eine Textur rein und wenn OpenGL rendert wird reingeschaut und beide werden auf das Ziel gepappt. Somit haben wir uns eine Transformation gespart, was vor allem wenn man sehr viel mit Lightmaps arbeitet schon ein beachtlicher Geschwindigkeitsgewinn von knapp 50% ist! Einige sehr moderne Grafikkarten haben bereits 128 Texturen-Units und die Zeit bis es welche mit 256 gibt ist auch schon bereits voraussehbar ;)
  
 
Wie funktioniert das ganze nun für uns? OpenGL verwendet als Standard die Unit 0. Binden wir eine Textur oder definieren das Blending so beziehen sich alle diese Angaben immer auf die momentan selektierte Textur-Unit. Alles was wir machen müssen ist, die gewünschte auszuwählen:
 
Wie funktioniert das ganze nun für uns? OpenGL verwendet als Standard die Unit 0. Binden wir eine Textur oder definieren das Blending so beziehen sich alle diese Angaben immer auf die momentan selektierte Textur-Unit. Alles was wir machen müssen ist, die gewünschte auszuwählen:

Aktuelle Version vom 19. September 2012, 22:25 Uhr

Verblendet!

Vorwort

Nachdem wir nun ein wenig Erfahrung in der Praxis gesammelt haben, wenden wir uns einen anderen Bereich in der OpenGL-Programmierung zu, der uns helfen wird einige wirklich tolle Spezial-Effekte zu realisieren. Die meisten unter Euch werden den Begriff bereits das eine oder andere Mal gehört haben - die Rede ist vom so genannten Blending. Das berühmteste davon wird sicherlich das "Alpha Blending" sein. Wer nun glaubt Blending ist, wenn man sich ne Halogen-Werfer nimmt und damit versucht seinen Nachbarn zu quälen, ist auf dem Holzweg ;)

Blending weist OpenGL dazu an, zu entscheiden was passieren soll, wenn man zwei Objekte übereinander zeichnet. Im Normalfall wird das obere Objekt das untere überdecken, aber vielleicht wollen wir ja auch, dass das obere Objekt transparent ist und man hindurch blicken kann auf das untere? Wer denkt, dass hier bereits Schluss sei mit dem Blending, der ist schief gewickelt, denn hier beginnt erst die Welt des kreativen Geistes! Lasst Euch verzaubern von wunderschönen OpenGL-Effekten und heizt Euren Programmen grafisch ordentlich ein. Ich hoffe ihr habt Verständnis, dass ich nur auf Teilbereiche eingehen kann, denn die Palette von Möglichkeiten ist keine Grenze gesetzt ;)

Ich hoffe dennoch, dass auch das Tutorial gefallen wird und es wenigstens halbwegs verständlich rüberkommt. Auch gibt es für viele Dinge besser Lösungsmöglichkeiten, vieles hängt von der benutzen Textur ab oder aber auch wie viele davon verwendet werden. Um ein ausgiebiges Testen der Funktionen wird man nicht herum kommen. Also ran an die Arbeit und viel Spaß dabei ;)

Die vorhandene Welt und was darüber liegt

Was ist den nun blenden?

Wichtig beim Blending ist es zu wissen, was genau eigentlich beim rendern geschieht. OpenGL erhält eine Anweisung etwas zu zeichnen. Dieses wird dann in den so genannten "Frame-Buffer" gespeichert, das ist der Buffer,den wir dann auch meistens bei jedem Rendering mit glClear "löschen". (Wir erinnern uns, führen wir kein glClear aus, sehen wir nach jedem Rendering den alten Inhalt… habe versprochen, dass wir darauf zurück kommen ;) ). Das heißt eigentlich, dass wir bei jedem Rendern eines Objektes immer wieder etwas in den Frame-Buffer schreiben und dann wenn wir denken, dass alles fertig ist diesen Inhalt auf dem Bildschirm projizieren.

Das Blending setzt nun genau an der Stelle an, an dem etwas in den Frame-Buffer geschrieben werden soll. OpenGL hat praktisch bereits ein 3D-Objekt im Frame-Buffer und zeichnet dann das neue 3D Objekt. Nun können wir OpenGL mitteilen, was in einer solchen Situation geschehen soll. Standard gemäß würde das obere Objekt, dass darunter liegende verdecken.

  glBlendFunc(source,destination);

Mit dieser Anweisung können wir dieses Verhalten ändern. Wir geben zwei Faktoren an, den für die Quelle und den für das Ziel. Ich versuche das mal abstrakt zu erklären. Wie bereits gesagt, handelt es sich dabei um Faktoren. Nehmen wir mal an Source (Textur) wäre 1 und Destination (Framebuffer) 0, dann würde OpenGL beim zusammensetzen der Szene, das was bereits im Frame-Buffer ist total vernachlässigen und das neue, was darüber gezeichnet werden soll komplett drauf zeichnen. Je höher der Faktor einer dieser Beiden Werte ist, desto stärke werden die Farben beim Blending berücksichtigt.Das ganze funktioniert natürlich auch nur, wenn wir OpenGL dazu anhalten das Blending zu verwenden…

  glEnable(GL_BLEND);

Das ganze klingt sicherlich sehr komplex, (ist es auch *sg*), aber an Hand einiger Beispiele sollte es einem klar werden. Nehmen wir folgende Bilder:

Tutorial lektion7 rock tex.jpg Tutorial lektion7 gras tex.jpg

Diese beiden Texturen zeichnen wir jeweils auf einem Quad und zwar exakt aufeinander. Wir zeichnen zunächst die steinige Fläche und erhalten:

Tutorial lektion7 stone.jpg

Soweit so gut… nun setzen wir folgenden Blend-Faktor:

  glBlendFunc(GL_ONE,GL_ZERO);

und zeichnen die Gras-Textur darüber:


Tutorial lektion7 gras.jpg

Hmm… scheint nicht, als ob das Blending wirklich gut funktioniert hat, da wir nur die Textur sehen, die wir als letztes gezeichnet haben. Scheint soweit alles wie bisher zu sein. :-/

  glBlendFunc(GL_ONE,GL_ZERO);

Quatsch! Natürlich hat alles funktioniert wie wir es erwartet haben! Wir haben schließlich OpenGL dazu angewiesen von der neuen Textur "alles" zu nehmen und von dem, was im Frame-Buffer ist "nichts". Auf neudeutsch, wir haben ihn angewiesen die alte Textur über die andere zu zeichnen. Würden wir hingegen:

  glBlendFunc(GL_ZERO,GL_ONE);

Verwenden, so würden wir OpenGL dazu anhalten vom Frame-Buffer "alles" zu übernehmen und von der neuen Textur "nichts", das würde bedeuten dass wir ins leere Rendern (sehr gute Methode seine Engine zu optimieren… *sg*)

Ach so noch etwas… nehmt das mit dem GL_ZERO und GL_ONE und meinem "nichts und alles" nicht ganz so wörtlich. Es ist mehr eine kleine Eselsbrücke, die einem am Anfang hilft nicht total zu verwirren. Technisch macht OpenGL nichts anderes als bei GL_ONE die Farbe der Texture auf (1,1,1,1) zu setzen bei GL_ZERO auf (0,0,0,0); Und sobald es "schwarz" ist, sieht man sie halt nicht mehr, klingt logisch oder?

Claim for the World Order

Wie alles hat auch das Blending einen kleinen Schönheitsfehler. Viele Leute denken immer wieder, dass auch beim Blending praktisch eine 3D-Welt existiert, d.h. man hinrendern kann wo man will. Das ist jedoch falsch, da die Quelle ja im Frame-Buffer bereits vorliegt. Praktisch bereits fertig gerendert. Es macht also für das Blending einen enormen Unterschied, in welcher Reihenfolge die Objekte zur Kamera hin gerendert werden.

Würden wir zum Beispiel ein Haus mit einem Fenster render wollen und das Fensterglas mit Hilfe von Blending darstellen und wir würden zunächst das Glas rendern, so würde OpenGL dieses mit dem Hintergrund (vermutlich dem Himmel) blenden und dann anschließend das Haus drum herum zeichnen. Der ahnungslose Programmierer wird dann mit großen Augen vor dem Fenster stehen und sich wundern, wieso er durch die hintere Wand des Hauses ins Freie sehen kann.

Kurz: Auch durchsichtige Objekte sind Objekte. Auch sie schreiben Werte in den Tiefenpuffer. Deshalb kann man nicht "hinter" durchsichtige Objekte etwas rendern.

Ohne Blending, würde so etwas nicht weiter auffallen, mit Blending muss man Acht geben in welcher Reihenfolge wir die Objekte rendern.

Darum gilt: alle undurchsichtigen Objekte rendern, bevor man zum Blending greift. Man sollte sich also durchaus Gedanken dazu machen, wie man seine Objekte rendert. Es gibt verschiedene Verfahren seine Objekte so ordnen zu lassen, dass das hier beschrieben Problem nicht auftritt, darauf möchte ich nicht näher eingehen, ihr seid gewarnt worden ;D Eigentlich ist es ein recht logischer Vorgang, allerdings erliegt man doch recht schnell dem kleinen Irrglauben hinter eine Scheibe rendern zu können. ;)

Gleichheit für Alles!

Das war ja nun auch ein interessanter Ausflug… gelernt haben wir immer noch nichts wirklich Neues, zumindest nichts, was man auch sehen kann. Üben wir uns doch nun einmal richtig ein und versuchen zwei Texturen zu gleichen Maßen zusammen zu blenden, so dass es aussieht, als hätten wir eine Textur. Sicherlich gibt es viele Möglichkeiten dies zu realisieren, allerdings möchte ich an dieser Stelle einen Alpha-Farbwert verwenden. Wie ihr sicherlich wisst, werden Farben meist im RGB-Werten gespeichert, sprich Rot, Grün und Blau! Gesetzt wird dieses mit glColor3f. Ebenfalls gibt es noch die Funktion glColor4f mit der wir einen RGBA-Wert vergeben können. Dieser wird uns vor allem beim Blending nützliche Dienste erweisen.

Weil beide Texturen zu gleichen Prozentsätzen geblendet werden sollen ergibt sich daraus folglich ein Aufruf von "glColor4f(1,1,1,0.5)";

Wie ihr sehen könnt setzen wir diesmal glBlendFunc(GL_SRC_ALPHA,GL_ONE); ein. Von der Quelle wird der Alpha-Wert als Blend-Faktor genommen, dass Ziel wird übernommen.

Tutorial lektion7 stonegras.jpg

Wenn das nicht aussieht wie eine Textur, die wie aus dem gleichen Guss kommt! Würde noch jemand erahnen, dass es sich dabei eigentlich um zwei Texturen handelt? Für alle, interessierten noch einmal kurz eine Tabelle zur Übersicht der Funktionen, die wir bisher eingesetzt haben und wir noch einsetzen werden, sowie der mathematischen Erklärung ihrer Bedeutung:

Konstante Auf Parameter anwendbar Berechneter Blend-Faktor
GL_ZERO source or destination (0, 0, 0, 0)
GL_ONE source or destination (1, 1, 1, 1)
GL_DST_COLOR source (Rd, Gd, Bd, Ad)
GL_SRC_COLOR destination (Rs, Gs, Bs, As)
GL_ONE_MINUS_DST_COLOR source (1, 1, 1, 1)-(Rd, Gd, Bd, Ad)
GL_ONE_MINUS_SRC_COLOR destination (1, 1, 1, 1)-(Rs, Gs, Bs, As)
GL_SRC_ALPHA source or destination (As, As, As, As)
GL_ONE_MINUS_SRC_ALPH source or destination (1, 1, 1, 1)-(As, As, As, As)
GL_DST_ALPHA source or destination (Ad, Ad, Ad, Ad)
GL_ONE_MINUS_DST_ALPHA source or destination (1, 1, 1, 1)-(Ad, Ad, Ad, Ad)
GL_SRC_ALPHA_SATURATE source (f, f, f, 1); f=min(As, 1-Ad)

Sieht anfangs etwas verwirrend aus, aber der Mensch kann sich an vieles gewöhnen. Immerhin lest ihr bereits seit einiger Zeit Texte, die mit meinen Rechtschreibfehlern und meiner Sprache angereichert sind ;D

Um sicher zu gehen, dass auch wirklich alles verstanden ist nehmen wir uns noch einmal kurz folgenden Aufruf zu herzen:

  glBlendFunc(GL_ONE, GL_SRC_COLOR); // Zur Info: GL_SRC_COLOR darf nur auf 
                                     // den Paramter destination angewendet 
                                     // werden, siehe dazu obenstehende Tabelle

Wieder lassen wir den Frame-Buffer wie er ist, verwenden diesmal jedoch keinen Alpha-Wert zu blenden, sondern jeweils die RGB-Werte der Textur. OpenGL macht also praktisch nichts anderes als die Farbwerte zu addieren und anschließend durch zwei zu teilen. Wie man der Tabelle entnehmen kann spielt bei einem solchen Aufruf nur die Destination eine Rolle. Folglich sieht das Ergebnis wie folgt aus:

Tutorial lektion7 stonegras2.jpg

Soweit nun zum einfachen Blending. Ihr könnt Euch vorstellen, dass wir damit bereits eine Menge sehr interessanter Effekte erzeugen können, aber nun wollen wir einige davon auch gleich einmal testen und einsetzen ;)

Blending-Verfahren

Die Welt unter einer Maske

Ein Verfahren, welches sehr häufig zum Einsatz kommt ist das "Masking-Verfahren". Aus dem Namen kann man sich bereits grob vorstellen, worum es geht. Wenn der Mensch sich eine Maske aufsetzt, so versucht er etwas damit zu verdecken (zum Beispiel sein Gesicht). Genauso machen wir es auch mit OpenGL um etwas zu verdecken. In diesem Fall jedoch, das Objekt welches wir unter der Maske liegen haben. Es gibt einen Haufen von sinnvollen Verwendungs-Möglichkeiten. Beim Masking an sich geht es darum, nur einen bestimmten Bereich sichtbar zu machen. Nehmen wir also einmal wieder unsere schön Stein-Textur und erzeugen in einem Malprogramm unserer Wahl eine Maske. Diese wird in diesem Fall komplett weiß sein und die eigentliche Masken-Bereiche sind schwarz.

Tutorial lektion7 dgl.gif

Diese Maske zeichnen wir nun selbstredend über der Steintextur und verwenden dabei folgende Blending-Funktion:

  glBlendFunc(GL_DST_COLOR, GL_ONE); // Zur Info: GL_DST_COLOR darf nur 
                                     // auf den Parameter source angewendet
                                     // werden. Siehe dazu obenstehende Tabelle.

Als Ergebnis erhalten wir folgendes:

Tutorial lektion7 whitemask.jpg

Alles was an der Quelle weiß ist, wird bei dem Source-Faktor enorm hoch bewertet also sehr "weiß" gemacht. Da Schwarz das Gegenteil von Weiß ist, wird dieses eben total vernachlässigt. Sprich wir können hindurch sehen. Da der Destination-Faktor auf die Farbe des "Ziel-Pixels" gesetzt ist sehen wir dann an den entsprechenden Stellen auch den Hintergrund. Wollen wir das Spielchen mal umdrehen, so dass man nur noch die schwarze Schrift sieht und ansonsten durch die zweite Textur komplett hindurch blicken kann? Kein Problem!

Tutorial lektion7 blackmask.jpg

Und die dazugehörige Blend-Funktion :

glBlendFunc(GL_ZERO,GL_SRC_COLOR);

Besonders hoch bei der Quelle werten wir in diesem Fall das Schwarz. Bei der Zielfarbe bewerten wir die Farbe sehr hoch die die zweite Textur hat. D.h. alles was schön weiß ist wird entsprechend geblendet. Wer sich nun am Kopf kratzt sollte sich ein wenig Code schnappen und ein wenig damit herum spielen. Recht schnell merkt man, wie es funktioniert, denn Blending ist unter OpenGL gar nicht so schwer! Am besten auch gleich die obere Tabelle ausdrucken und daneben liegen haben ;)

Der Überläufer

Ich hoffe sehr, dass ich Euch jetzt hier nicht zu sehr langweile in dem ich andauernd die eine oder andere Funktion immer wieder und wieder in anderer Form zeige. Ich möchte schlicht und ergreifend damit erreichen, dass ihr ein wenig für die Möglichkeiten die man hat sensilibisiert werdet. Die Vielfalt an Effekten, die man erzielen kann ist nämlich wirklich groß, vor allem, wenn man einzelne Effekte miteinander kombiniert!

Nehme wir uns noch einmal unsere Stein- und Gras-Textur zur Hand. Diesmal wollen wir versuchen, dass auf der linken Seite die Stein-Textur ist und auf der Rechten die Gras-Textur. Das ganze soll einen flüssigen Übergang ergeben.

Tutorial lektion7 graswall.jpg

Auch ein solcher Effekt ist nicht sonderlich schwer. Alles was wir hierfür brauchen sind unterschiedliche Alpha-Werte an den Eckpunkten der Quads. Beim ersten müssen diese links 1 sein und rechts 0, beim anderen entgegengesetzt. Setzen tun wir die Alpha-Werte mit glColor4f:

  Rock.SetTexture;
  glBegin(gl_Quads);
    glColor4f(1,1,1,1);
    glTexCoord2f(0,0);
    glVertex3f(-1,-1,0);

    glColor4f(1,1,1,0);
    glTexCoord2f(1,0);
    glVertex3f(1,-1,0);
    glTexCoord2f(1,1);
    glvertex3f(1,1,0);

    glColor4f(1,1,1,1);
    glTexCoord2f(0,1);
    glvertex3f(-1,1,0);
  glEnd;

  glDisable(GL_DEPTH_TEST); // Wichtig! Da wir sonst das erste Quad verwerfen
                            // und damit auch die Textur, der Aufruf könnte auch 
                            // in einer Init Funktion stehen.
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA,GL_DST_ALPHA);

  gras.SetTexture;
  glBegin(gl_Quads);
    glColor4f(1,1,1,0);
    glTexCoord2f(0,0);
    glVertex3f(-1,-1,0);

    glColor4f(1,1,1,1);
    glTexCoord2f(1,0);
    glVertex3f(1,-1,0);
    glTexCoord2f(1,1);
    glvertex3f(1,1,0);

    glColor4f(1,1,1,0);
    glTexCoord2f(0,1);
    glvertex3f(-1,1,0);
  glEnd;

Während das erste Quad ohne Blending gezeichnet wird, wird das zweite geblendet, jeweils mit den Alpha-Werten der Quelle und des Zieles. Um dies zu verdeutlichen:


Tutorial lektion7 redbluealpha.jpg

Je blauer die Farbe, desto weniger wird die Wand gefadet, je roter, desto mehr Gras wird angezeigt. In der Mitte verlaufen beide Texturen zusammen. Natürlich kann man nun anfangen zu spielen und gibt den Seiten andere Alpha-Werte. Genauso wäre es möglich drei Texturen ineinander einzublenden etc. Seit kreativ ;D

Der Überläufer - Teil 2

Der eben beschriebene Effekt lässt sich auch noch sehr schön erweitern. Haben wir dort eine gerade Trennlinie zwischen den beiden Texturen gehabt, so werden wir uns nun mit einer Technik befassen, die es einem ermöglicht unregelmäßige Übergange zu erzeigen. Dieses Verfahren wird besondern oft bei Terrains angewandt, um zum Beispiel eine Schneelandschaft langsam ins Geröll überwandern zu lassen. Je nach Verfahren benötigen wir hierfür drei oder vier Texturen. Wir beschränken uns hierbei einfach auf drei.

Würden wir zwei Texturen übereinander zeichnen, so würde die obere die untere Verdecken, würden wir bei einem Quad mit Alpha-Werten arbeiten würden die Übergange nicht flüssig sein. Würden wir die Farben addieren, würde so ziemlich alles dabei rauskommen, nur nicht einen Tetxuren-Verlauf, wie wir ihn erreichen wollen.

Tutorial lektion7 blendmap.jpg

Auf diesem Bild erkennt man sehr schön den Texturen-Verlauf, während im oberen Bereich herum bis nach links unten und rechts oben die steinige Felstextur ist, ist im unteren Bereich mehr die Gras-Textur, mit einer Ausnahme mitten in der Gras-Textur ist wiederum ein kleiner Abschnitt mit einer felsigen Textur (hier lila). Auch erkennt man auf dem zweiten Blick, dass an den Grenz-Bereichen ein wenig das Grüne auf dem Felsen durchschimmert, wir haben einen fließenden Übergang zu beiden Texturen erzeugt und können sogar im "Gebiet des Anderen" eine Textur durchscheinen lassen. Die Lösung ist wiederum eine Art von Masking:

Tutorial lektion7 blendmap2.jpg

Vergleicht man dieses Bild mit dem oberen wird man erkennen, dass die hellern Flecken "Stein-Textur" und die dunklen "Gras" bedeuten. Man redet von solchen Texturen meist von "Blend-Maps". Technisch braucht man also nur eine Blend-Map für eine Textur und ein invertiertes Bild davon. An diesem Punkt können wir uns das invertieren bereits sparen, weil wir nur zwei Texturen blenden wollen und das Gegenteil von weiß, schwarz ist ;)

Lichterkarten

Und es ward Licht…

Ein wirklich tolles Spielzeug, welches einem mit Blending in die Hand gelegt wird sind "Lightmaps". Vielleicht hat jemand von Euch bereits einmal versucht in seinem Projekt so schöne Lichtquellen einzubauen, wie er es aus bekannten kommerziellen Spielen kennt und hat sofort anschließend ein langes Gesicht gezogen als die Augen auf die Framerate blickten. Dazu kommt, dass das OpenGL-Licht meist nicht einmal besonders ansehnlich ist. Ärgerlich…

Doch es gibt natürlich eine Möglichkeit schnell und effizient mit wenig Aufwand genauso schöne Lichter in seinem Projekt einzubauen. Das Geheimnis ist schlicht und ergreifend, dass wir nicht auf OpenGL-Lichter aufbauen, sondern einen kleinen Trick auf unsere Benutzer spielen. Wir zeichnen vor dem Zielobjekt eine neue Texture, die nur aus Graustufen besteht. Je heller ein Punkt ist, desto mehr soll er anschließend leuchten.


Tutorial lektion7 lightmap.jpg Tutorial lektion7 wall tex.jpg

Macht grafisch doch schon einiges mehr her als die ursprüngliche Textur oder? Wie bereits gesagt, dass Geheimnis sind die Lightmaps, die wir vor der Wand zeichnen:

Tutorial lektion7 light.jpg Tutorial lektion7 light2.jpg

Mit etwas Fantasie kann man sie sogar immer noch erkennen. Man beachte, dass die erste Lightmap vor allem im oberen Bereich besonders hell ist und vor allem einen sehr hellen Punkt im oberen Bereich hat. Blicken wir auf das fertige Bild, so sehen wir dort einen roten Punkt. Die zweite Lightmap ist eher gleichmäßig und matt, nicht sonderlich hell, zumindest nicht auffällig. Sie sorgt dafür, dass ein gleichmäßiger Grünton auf der Wand liegt. Doch wie kann aus der weißen Lightmap z.B. eine Rote werden? Das ist relativ leicht, wir zeichnen das Quad vor der Wand einfach rot mit Hilfe von glColor3f(1,0.7,0.7); Damit die Farbe auch richtig zur Geltung kommt müssen diese natürlich auch beim Blending entsprechend einarbeiten!

  glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);

Bei der Quelle wird vor allem die Farbe berücksichtigt, bei der Destination die Zielfarbe. Jeweils zwei Quads werden nach diesem Verfahren über die Wand gezeichnet, um den Effekt zu erreichen. Bin mir durchaus bewusst, dass man schönere Lightmaps machen kann, auch ist es zum Beispiel sehr interessant bei der Zielfarbe GL_SRC_COLOR zu verwenden, wodurch die gesamte Wand verdunkelt wird und man nur noch um das rote leuchten oben etwas sehen könnte (in diesem Fall nur eine Lightmap!)

Tutorial lektion7 lightmap2.jpg

Der Kreativität sind eigentlich keine Grenzen gesetzt. Alle modernen 3D-Engines berechnen bei einem Level an Hand der Lichtquellen solche Lightmaps und projizieren diese dann vor den Wänden, um sie aussehen zu lassen, wie sie aussehen sollen. Keiner dieses wunderschönen Effekts wird man mit einem OpenGL-Licht realisieren können. Wozu auch, wenn es so doch viel schneller geht ;)

Die Kraft des Gemeinsamen!

Wir haben uns hier sehr viel mit Blending auseinander gesetzt und sicherlich juckt es Euch bereits unter den Fingern Wände mit wunderschönen Lichteffekten an die Wand zu zaubern. So mindestens 300 Lichter sollten sicherlich zum Minimum zählen, oder ;D

Nun schnell werdet ihr vor allem in größeren Projekten merken, dass Euch das auf die Rechenleistung schlägt. Das Problem ist logisch! Nicht das Blending an sich ist die größte CPU-Last, sondern das Transformieren der Quads, auf denen sich die Lightmaps befinden. Eigentlich sind es fast nur die Transformationen, die so richtig mit Leistung zu Buch schlagen. Es gilt also so wenig wie mögliche Quads hintereinander zu erzeugen und somit die Anzahl der Durchläufe zu minimieren (Renderpass). Das ließe sich zum Beispiel damit erreichen, dass man versucht möglichst viele Lichtinformationen auf einer Lightmap zu setzen. So würde es keinen Sinn machen zwei rote Lichter einmal oben und einmal unten.

Dies sind jedoch nur logische Einsparungen, die jeder Programmierer in seinem Projekt selbst entscheiden muss. Seit einiger Zeit gibt es jedoch auch ein Hardware-Verfahren, welches uns helfen kann ein Spiel zu optimieren. Zu Beginn der 3D-Beschleunigung verlief das Rendering wie folgt:

# Texture setzen
# Objekt transformieren
# Texture setzen
# Objekt transformieren

Klingt soweit auch alles logisch! Wie bereits jedoch erwähnt frisst vor allem das transformieren die Rechenpower und in unserem Fall liegt das Objekt übereinander, es wäre eine Transformation der Vertizen eigentlich gar nicht nötig. Wir müssten also nur die beiden Texturen auf ein transformiertes Objekt bekommen. Würden wir einfach nur zwei Texturen hintereinander setzen, so würde OpenGL die zuletzt gewählte verwenden und die andere schlichtweg ignorieren. Die meisten heutigen Grafikkarten (z.B: GeForce8800 GTX) haben 64 Textur-Units. Das muss man sich vorstellen wie 64 Schubladen. In die ersten beiden legen wir eine Textur rein und wenn OpenGL rendert wird reingeschaut und beide werden auf das Ziel gepappt. Somit haben wir uns eine Transformation gespart, was vor allem wenn man sehr viel mit Lightmaps arbeitet schon ein beachtlicher Geschwindigkeitsgewinn von knapp 50% ist! Einige sehr moderne Grafikkarten haben bereits 128 Texturen-Units und die Zeit bis es welche mit 256 gibt ist auch schon bereits voraussehbar ;)

Wie funktioniert das ganze nun für uns? OpenGL verwendet als Standard die Unit 0. Binden wir eine Textur oder definieren das Blending so beziehen sich alle diese Angaben immer auf die momentan selektierte Textur-Unit. Alles was wir machen müssen ist, die gewünschte auszuwählen:

  glActiveTextureARB(GL_TEXTURE0_ARB);

Ich denke der Parameter erklärt sich praktisch von selbst! GL_TEXTURE1_ARB wäre entsprechend die zweite Textur-Unit. Nun setzen wir alle Einstellungen für diese Unit:

  glEnable(GL_TEXTURE_2D);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  wall.SetTexture;

Zunächst teilen wir der Unit mit, dass sie bei der 2D-texturierung zur Verfügung stehen soll. Anschließend setzen wir die Textur-Envroiment auf die "Standard-Einstellung". Wenn mich jetzt jemand fragt, wofür die Parameter gut sind, dann zitiere ich das RedBook "es muss so sein" ;D Wichtig ist eigentlich nur, dass hinten GL_MODULATE steht, damit die Textur ordnungsgemäß, so wie wir sie sehen auf das Quad wandern.

  glActiveTextureARB(GL_TEXTURE1_ARB);
  glEnable(GL_TEXTURE_2D);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glEnable(gl_BLEND);
  glBlendFunc(GL_ONE,GL_ONE);
  light.SetTexture;

Wir wechseln danach zur zweiten Textur-Unit und machen ähnliche Einstellungen, aktivieren zusätzlich jedoch Blending für diese Funktion. Diesmal etwas sehr simples.

Wer nun gehofft hat, dass dies alles gewesen wäre, was man für das MultiTexturing benötigt, der wird enttäuscht sein. Eine Kleinigkeit gibt es noch zu beachten. Da wir zwei Texturen verwenden kann es ja auch sein, dass die UV-Koordinaten unterschiedlich sein sollen. In unserem Fall ist dies nicht so, aber es kann durchaus passieren. Deswegen müssen wir auch für jeden Eckpunkt eine UV-Koordinate vergeben. Hierfür verwenden wir nicht glTexCoord2f, sondern die Funktion:

  glMultiTexCoord2fARB(GL_TEXTURE0_ARB,0,0);
  glMultiTexCoord2fARB(GL_TEXTURE1_ARB,0,0);

Zunächst geben wir bei der Funktion den Parameter ein gefolgt von der eigentlichen UV-Koordinate. So gehen wir dann bei jedem Punkt vor und vorausgesetzt, dass unsere Grafikkarte auch Multitexturing unterstütz sollten wir uns an folgendem Bild auf dem Bildschirm ergötzen können (natürlich nur, wenn man auch die gleichen Texturen verwendet! ;D)

Tutorial lektion7 multitex.jpg

Nachwort

Oh je, ich weiß, ich habe inzwischen einen Ruf dafür, dass ich immer gleich so große Portionen liefere und nicht in der Lage bin, einmal kurz ein kleines Tutorial zu schreiben. Das liegt einfach daran, dass wenn ich einmal angefangen bin und mich für etwas begeistern konnte nicht mehr aufhören kann. Und gerade bei solchen Themen, wo man so richtig schön mit Effekten experimentieren kann fällt es einem besonders schwer einen Punkt zu finden.

Ich denke spätestens nach diesem Tutorial sollte man gelernt haben wie leicht man mit OpenGL einige wunderschöne Effekte zaubern kann ohne wirklich großen Aufwand zu betreiben. Viel leichter wird es kaum möglich sein. Sollte jemand nun sich am Kopf kratzen und sich fragen, ob es einen Grund hat, warum die ganzen hier vorgestellten Beispiele 2D sind? Nein, eigentlich nicht, denn alle der hier vorgestellten Techniken lassen sich auch prima auf 3D-Objekte übertragen. So wäre es zum Beispiel auch Möglich einen 3D-Soldaten mit einer Lightmap und Multitexturing zu überziehen und somit unterschiedliche Helligkeit auf ihn zu projizieren. Aber darauf will ich nicht weiter eingehen, nun kommt wieder Euer Part. ;)

Auch bin ich sehr froh zu sehen, dass die Community wächst und auch endlich einen Punkt erreicht an der man gezielt auf Probleme einwirken kann. Ich hoffe sehr, dass dies auch weiterhin so bleibt und bin auch sehr froh, dass zunehmend Feedback von Eurer Seite kommt, gar einige damit beginnen Kontent für uns zur Verfügung zu stellen oder uns auf kleinere Fehler aufmerksam macht oder aber auch einfach nur auf eine gute Seite aufmerksam machen, die wir bisher nicht gelinkt haben. Wenn jemand von Euch einmal ein kleines OpenGL-Projekt vorstellen möchte, etwas an dem er arbeitet, schreibt uns doch ruhig auch einmal an. Mehr als Ablehnen könnten wir auch nicht ;D

In diesem Sinne viel Spaß und auf Wiedersehen.

Euer Phobeus

Dateien


Vorhergehendes Tutorial:
Tutorial Lektion 5
Nächstes Tutorial:
Tutorial Lektion 8

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