Tutorial StencilSpiegel: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Link korrigiert)
K
 
(10 dazwischenliegende Versionen von 7 Benutzern werden nicht angezeigt)
Zeile 4: Zeile 4:
 
Ave!
 
Ave!
  
Herzlich willkommen zu meinem ersten Tutorial für DGL.Einige Leser dürften schon die von mir auf meiner Seite veröffentlichen Tutorials kennen, von nun an werde ich jedoch verstärkt Tutorials für die DGL veröffentlichen.
+
Herzlich willkommen zu meinem ersten Tutorial für DGL. Einige Leser dürften schon die von mir auf meiner Seite veröffentlichen Tutorials kennen, von nun an werde ich jedoch verstärkt Tutorials für die DGL veröffentlichen.
  
In dieser Lektion geht es um die realistische Darstellung von Spiegelungen mit Hilfe des Stencil-Puffers.<br>
+
In dieser Lektion geht es um die realistische Darstellung von Spiegelungen mit Hilfe des [[Schablonenpuffer|Stencilpuffers]].<br>
Diese Technik findet langsam aber sicher Verbreitung in der Spieleindustrie.Das hat zum einen den Grund, das spiegelnde Flächen in der realen Welt sehr häufig vorkommen, und ihre Nachahmung einer 3D-Welt noch ein Stück mehr an Realitätsnähe verleiht.Der weitere Grund, warum realistische Spiegelungen erst seit kurzem verwendet werden, ist die Tatsache das die Szene mindestens zwei mal gerendert werden muß, und das die Grafikkarte einen Stencil-Puffer mitbringen muß.Moderne 3D-Beschleuniger besitzen ihn natürlich, aber die damals so weit verbreitete Vodoo-Serie besaß diesen nicht.
+
Diese Technik findet langsam aber sicher Verbreitung in der Spieleindustrie. Das hat zum einen den Grund, das spiegelnde Flächen in der realen Welt sehr häufig vorkommen, und ihre Nachahmung einer 3D-Welt noch ein Stück mehr an Realitätsnähe verleiht. Der weitere Grund, warum realistische Spiegelungen erst seit kurzem verwendet werden, ist die Tatsache das die Szene mindestens zwei mal gerendert werden muß, und das die Grafikkarte einen [[Schablonenpuffer|Stencilpuffer]] mitbringen muß. Moderne 3D-Beschleuniger besitzen ihn natürlich, aber die damals so weit verbreitete Vodoo-Serie besaß diesen nicht.
  
 
==Der Stencil-Puffer==
 
==Der Stencil-Puffer==
  
Wie schon erwähnt ist der Stencil-Puffer das wichtigste Mittel um realistische Spiegelungen (und noch viele andere interessante Dinge) zu realisieren.Wie das Wort Stencil (=Schablone) schon andeutet, kann man in diesem Puffer eine Schablone ablegen, die später bestimmt welcher Bereich des Bildschirms überzeichnet, und welcher Bereich unberührt bleibt.<br>
+
Wie schon erwähnt ist der [[Schablonenpuffer|Stencilpuffer]] ein wichtigste Mittel um realistische Spiegelungen (und noch viele andere interessante Dinge) zu realisieren. Wie das Wort Stencil (=Schablone) schon andeutet, kann man in diesem Puffer eine Schablone ablegen, die später bestimmt welcher Bereich des Bildschirms überzeichnet, und welcher Bereich unberührt bleibt.<br>
Die Tiefe des Stencil-Puffers ist je nach Hardware unterschiedlich.Kyro-Karten liefern einen 4-Bittigen Puffer, während nVidia mal wieder etwas schlauer waren, und einen 8-Bit Stencil-Puffer zusammen mit einem 24-Bit Tiefenpuffer zu einem DWORD kombinieren.Dadurch kann der Stencil-Puffer auf nVidia-Karten "ohne Geschwindidkeitsverlust" genutzt werden.Das stimmt zwar, aber bei dieser Aussage haben die Marketingexperten die zeitaufwendige doppelte Berechnung der Geometrie natürlich aussen vor gelassen.
+
Die Tiefe des [[Schablonenpuffer|Stencilpuffers]] ist je nach Hardware unterschiedlich, inzwischen kann man aber auf jeder aktuelleren Karte von mindestens 8-Bit für diesen Puffer (oft werden dann die 24-Bit für den Tiefenpuffer mit diesen kombiniert um ein DWORD zu erhalten) ausgehen.
  
Ein praktisches Anwendungsbeispiel für die Nutzung des Stencil-Puffers zur Begrenzung der zu überzeichnenden Fläche wäre z.B. die Cockpitansicht einer Flugsimulation.Zuerst wird der Stencil-Puffer aktiviert, und dann wird das Cockpit in diesen Stencil-Puffer "hineingezeichnet" und quasi als Ausschlussmaske benutzt.Überall dort wo das Cockpit im Stencil-Puffer einen Pixel hinterlassen hat wird später im Farb-Puffer nicht gezeichnet.Dadurch muss das Cockpit also nur einmal gezeichnet werden.Ich hoffe die untere Bildreihe verdeutlicht dies :
+
Ein praktisches Anwendungsbeispiel für die Nutzung dieses Puffers zur Begrenzung der zu überzeichnenden Fläche wäre z.B. die Cockpitansicht einer Flugsimulation. Zuerst wird der [[Schablonenpuffer|Stencilpuffer]] aktiviert, und dann wird das Cockpit in diesen Puffer "hineingezeichnet" und als Ausschlussmaske benutzt. Überall dort wo das Cockpit im [[Schablonenpuffer|Stencilpuffer]] einen Pixel hinterlassen hat wird später im [[Farbpuffer|Farbpuffer]] nicht gezeichnet.Dadurch muss das Cockpit also nur einmal gezeichnet werden. Die folgende Bildreihe verdeutlicht dies :
  
 
<div align="center">
 
<div align="center">
 
{|{{Prettytable}}
 
{|{{Prettytable}}
| [[Bild:Tutorial_Stencil_cockpit1.jpg]]  
+
|width="25%" |[[Bild:Tutorial_Stencil_cockpit01.jpg|center]]  
| [[Bild:Tutorial_Stencil_cockpit1.jpg]]
+
|width="25%" |[[Bild:Tutorial_Stencil_cockpit02.jpg|center]]
| [[Bild:Tutorial_Stencil_cockpit1.jpg]]
+
|width="25%" |[[Bild:Tutorial_Stencil_cockpit03.jpg|center]]
| [[Bild:Tutorial_Stencil_cockpit1.jpg]]
+
|width="25%" |[[Bild:Tutorial_Stencil_cockpit04.jpg|center]]
 
|-
 
|-
| Cockpit-Textur (oder 3D-Modell)<br>(wird in den Stencil Puffer gezeichnet)
+
|align="center" | Cockpit-Textur (oder 3D-Modell)<br>(wird in den Stencilpuffer gezeichnet)
| Inhalt des Stencil-Puffers <br>(Schwarz : Stencil-Wert = 0)<br>(Weiß : Stencil-Wert = 1)
+
|align="center" | Inhalt des Stencil-Puffers <br>(Schwarz : Stencil-Wert = 0)<br>(Weiß : Stencil-Wert = 1)
| Auf dem Bildschirm sichtbarer Teil der Szene
+
|align="center" | Auf dem Bildschirm sichtbarer Teil der Szene
| Endergebnis
+
|align="center" | Endergebnis
 
|}
 
|}
 
</div>
 
</div>
  
 
Jetzt fragt ihr euch bestimmt, was dieses Cokpit-Beispiel mit unserer Spiegelung zu tun hat?
 
Jetzt fragt ihr euch bestimmt, was dieses Cokpit-Beispiel mit unserer Spiegelung zu tun hat?
Ganz einfach.Zum einen erleichtert es das Verständnis der Funktionsweise des Stencil-Puffers, was unbedingt notwendig ist, und zum anderen funktioniert die Sache mit dem Spiegel genau umgekehrt.<br>
+
Ganz einfach : Zum einen erleichtert es das Verständnis der Funktionsweise des [[Schablonenpuffer|Stencilpuffers]], was unbedingt notwendig ist, und zum anderen funktioniert die Sache mit dem Spiegel genau umgekehrt.<br>
Während der Stencil-Puffer im Cockpit-Beispiel dazu genutzt wird, den Teil an dem sich das Cockpit befindet vor dem Überschreiben durch andere Farbwerte zu bewahren, wird der Stencil-Puffer bei einer Spiegelung dazu genutzt, den Bereich ausserhalb des Spiegels vorm Überschreiben zu schützen.So wird also nur der Teil der Szene gezeichnet, die sich auch "im" Spiegel befindet.<br>
+
Während der [[Schablonenpuffer|Stencilpuffer]] im Cockpit-Beispiel dazu genutzt wird, den Teil an dem sich das Cockpit befindet vor dem Überschreiben durch andere Farbwerte zu bewahren, wird der Puffer bei einer Spiegelung dazu genutzt, den Bereich ausserhalb des Spiegels vorm Überschreiben zu schützen. So wird also nur der Teil der Szene gezeichnet, die sich auch "im" Spiegel befindet.<br>
Wozu der Stencilpuffer also gut ist, lässt sich anhand von folgenden zwei Screenshots wohl am besten verdeutlichen.Während der Spiegel im linken Bild korrekt ist, und das Spiegelbild ausserhalb der Spieglfläche aufgrund des Stenciltests nicht gezeichnet wird, ist das Spiegelbild rechts falsch.Hier wurde der Stenciltest deaktiviert.
+
Wozu der [[Schablonenpuffer|Stencilpuffer]] also gut ist, lässt sich anhand der folgenden zwei Screenshots wohl am besten verdeutlichen. Während der Spiegel im linken Bild korrekt ist, und das Spiegelbild ausserhalb der Spieglfläche aufgrund des Stenciltests nicht gezeichnet wird, ist das Spiegelbild rechts falsch. Hier wurde der Stenciltest deaktiviert :
 
 
{{center| [[Bild:Tutorial_Stencil_Trooper04.jpg]] [[Bild:Tutorial_Stencil_Trooper03.jpg]] }}
 
  
 +
<center> [[Bild:Tutorial_Stencil_Trooper04.jpg]] [[Bild:Tutorial_Stencil_Trooper03.jpg]] </center>
  
 
==Clipping Plane==
 
==Clipping Plane==
Ein weiterer, für unseren Zweck notwendiger Begriff sind die [[Clipping Plane]]s (zu Deutsch : Schnittflächen).Das ist aber nichts mathematisch unmögliches, sondern eine ganz einfache Fläche, an der die Geometrie "abgeschnitten" wird.D.h., das alles was auf der per Vorzeichen definierten Seite dieser Schnittfläche liegt einfach nicht gezeichnet wird.<br>
+
Ein weiterer, für unseren Zweck notwendiger Begriff sind die [[Clipping Plane]]s (zu Deutsch : Schnittflächen). Das ist aber nichts mathematisch unmögliches, sondern eine ganz einfache Fläche, an der die Geometrie "abgeschnitten" wird. D.h., dass alles was auf der per Vorzeichen definierten Seite dieser Schnittfläche liegt einfach nicht gezeichnet wird.<br>
 
OpenGL bringt von Haus aus schonmal sechs solcher Clipping-Planes mit, die zusammen das Frustum ergeben, also der Quader, an dem die Geometrie aufgrund ihrer Sichtbarkeit "abgeschnitten" wird.
 
OpenGL bringt von Haus aus schonmal sechs solcher Clipping-Planes mit, die zusammen das Frustum ergeben, also der Quader, an dem die Geometrie aufgrund ihrer Sichtbarkeit "abgeschnitten" wird.
  
Darüberhinaus kann man jedoch selbst noch zusätzliche Schnittflächen definieren.Laut OpenGL-Vorgaben muß jeder OpenGL-Treiber mindestens sechs zusätzliche Schnittflächen anbieten können.Für unseren Spiegel reicht jedoch eine.
+
Darüberhinaus kann man jedoch selbst noch zusätzliche Schnittflächen definieren. Laut OpenGL-Vorgaben muß jeder OpenGL-Treiber mindestens sechs zusätzliche Schnittflächen anbieten können. Für unseren Spiegel reicht jedoch eine.
  
Eine Clippingplane wird mittels der Funktion {{INLINE_CODE|[[glClipPlane]](plane: TGLEnum; equation: PGLdouble)}}, wobei die Fläche mittles eines {{INLINE_CODE|array[0..3] of Double}} über ihre Gleichung ''Ax+By+Cz+D = 0'' definiert wird.Aktiviert bzw. deaktiviert wird eine Schnittfläche mittels der Konstante '''GL_CLIP_PLANE'''i, wobei i für die Nummer der Schnittfläche steht und zwischen 0 und 5 liegt.
+
Eine Clippingplane wird mittels der Funktion {{INLINE_CODE|[[glClipPlane]](plane: TGLEnum; equation: PGLdouble)}}, wobei die Fläche mittles eines {{INLINE_CODE|array[0..3] of Double}} über ihre Gleichung ''Ax+By+Cz+D = 0'' definiert wird. Aktiviert bzw. deaktiviert wird eine Schnittfläche mittels der Konstante '''GL_CLIP_PLANE'''i, wobei i für die Nummer der Schnittfläche steht und zwischen 0 und 5 liegt.
  
Das hört sich jetzt erstmal leicht trocken an, und dürfte ohne praktisches Beispiel auch nicht hängen bleiben.Deshalb hier mal zwei Screenshots aus dem Beispielprogramm zu diesem Tutorial, die verdeutlichen wozu die Schnittfläche benötigt ist.Der Spiegel liegt hier im Koordinatensystem am Ursprung (x:0,y:0,z:0) und "liegt" auf der Y-Achse.<br>
+
Das hört sich jetzt erstmal leicht trocken an, und dürfte ohne praktisches Beispiel auch nicht hängen bleiben. Deshalb hier zwei Screenshots aus dem Beispielprogramm zu diesem Tutorial, die verdeutlichen wozu die Schnittfläche benötigt wird. Der Spiegel liegt hier im Koordinatensystem am Ursprung (x:0,y:0,z:0) und "liegt" auf der Y-Achse.<br>
Definiert wird dieser Spiegel deshalb als {{INLINE_CODE|array[0..3] of Double = (0, 0, -1, 0)}}.Das Minus vor der Eins gibt also quasi an, auf welcher Seite der Spiegelfläche geclippt werden soll.Würde der Spielerraum also auf der anderen Seite liegen, wäre das Vorzeichen positiv.
+
Definiert wird dieser Spiegel deshalb als {{INLINE_CODE|array[0..3] of Double = (0, 0, -1, 0)}}. Das Minus vor der Eins gibt also quasi an, auf welcher Seite der Spiegelfläche geclippt werden soll. Würde der Spielerraum also auf der anderen Seite liegen, wäre das Vorzeichen positiv.
  
 
<center>
 
<center>
Zeile 59: Zeile 58:
 
==Programmiertechnische Umsetzung==
 
==Programmiertechnische Umsetzung==
  
Nachdem wir nun mit der Begriffserklärung abgeschlossen haben und jedem klar sein sollte, was ein Stencil-Puffer macht und wozu eine Clipping-Plane gut ist, widmen wir uns nun der Implementation eines Spiegels in unser Programm.Der Quelltext zu diesem Thema ist zwar relativ kurz, bietet aber dennoch einiges an Erklärungsspielraum.<br>
+
Nachdem wir nun mit der Begriffserklärung abgeschlossen haben und jedem klar sein sollte, was ein [[Schablonenpuffer|Stencilpuffers]] macht und wozu eine [[Clipping Plane]] gut ist, widmen wir uns nun der Implementation eines Spiegels in unser Programm. Der Quelltext zu diesem Thema ist zwar relativ kurz, bietet aber dennoch einiges an Erklärungsspielraum.<br>
Deshalb : Nicht einfach nur abtippen, sondern auch sorgfältig meine mehr oder weniger ausführlichen Erklärungen mitlesen.
+
''Deshalb : Nicht einfach nur abtippen, sondern auch sorgfältig meine mehr oder weniger ausführlichen Erklärungen mitlesen.''
  
Die Renderprozedur sowohl für die normale Szene als auch für den Spiegelraum befindet sich im Beispielprogramm in der Prozedur DrawScene_Stencil.Im folgenden Kapitel werden alle relevante Teile dieser Prozedur besprochen, unwichtige Grundlage lasse ich jedoch extra weg.Jeder der sich mit Spiegelungen beschäfigen will, sollte sich mit den OpenGL-Grundlagen auskennen.
+
Die Renderprozedur sowohl für die normale Szene als auch für den Spiegelraum befindet sich im Beispielprogramm in der Prozedur ''DrawScene_Stencil''. Im folgenden Kapitel werden alle relevante Teile dieser Prozedur besprochen, unwichtige Grundlagen lasse ich jedoch weg. Jeder der sich mit Spiegelungen beschäftigen will, sollte sich mit den OpenGL-Grundlagen auskennen.
  
 
===Definition der Schnittfläche===
 
===Definition der Schnittfläche===
  
Zu allererst definieren wir die Schnittfläche an der das Spiegelbild abgeschnitten wird.Würden wir diesen Teil weglassen, dann würden spätestens beim Durchschreiten eines Spiegels (auch wenn das in einem Ego-Shooter nicht vorkommen sollte) hässlich falsche Spiegelbilder entstehen...und das wollen wir ja nicht, schliesslich soll unser Spiegel ja die reale Welt nachstellen.Wie die Schnittfläche definiert wird, haben wir ja oben bereits besprochen.Wichtig wäre hier vielleicht noch, das als Datentyp unbedingt Double verwendet werden muß.Andere Datentype (z.B. Single) führen zu unvorhersehbaren Effekten.
+
Zu allererst definieren wir die Schnittfläche an der das Spiegelbild abgeschnitten wird. Würden wir diesen Teil weglassen, dann würden spätestens beim Durchschreiten eines Spiegels (auch wenn das in einem Ego-Shooter nicht vorkommen sollte) hässlich falsche Spiegelbilder entstehen...und das wollen wir ja nicht, schliesslich soll unser Spiegel ja die reale Welt nachstellen. Wie die Schnittfläche definiert wird, haben wir ja oben bereits besprochen. Wichtig wäre hier vielleicht noch, dass als Datentyp unbedingt Double verwendet werden muß. Andere Datentypen (z.B. Single) führen zu unvorhersehbaren Effekten.
  
 
  const
 
  const
Zeile 72: Zeile 71:
  
 
===Den Stencil-Puffer löschen===
 
===Den Stencil-Puffer löschen===
Wie bekannt müssen (oder sollten) die verwendeten OpenGL-Puffer vor jedem neuen Zeichendurchgang gelöscht werden.Dies geschieht ja bekannterweise mit der Funktion [[glClear]]() und als Parameter oderverknüpft die zu löschenden Puffer.Neben den Bits für den Farbpuffer ('''GL_COLOR_BUFFER_BIT''') und dem Tiefenpuffer ('''GL_DEPTH_BUFFER_BIT''') kommt nun ein neues Puffer-Bit dazu, nämlich '''GL_STENCIL_BUFFER_BIT''', um den Stencil-Puffer zu "löschen".
+
Wie bekannt müssen (oder sollten) die verwendeten OpenGL-Puffer vor jedem neuen Zeichendurchgang gelöscht werden. Dies geschieht ja bekannterweise mit der Funktion [[glClear]]() und als Parameter oderverknüpft die zu löschenden Puffer. Neben den Bits für den [[Farbpuffer]] ('''GL_COLOR_BUFFER_BIT''') und dem [[Tiefenpuffer]] ('''GL_DEPTH_BUFFER_BIT''') kommt nun ein neues Puffer-Bit dazu, nämlich '''GL_STENCIL_BUFFER_BIT''', um den [[Schablonenpuffer|Stencilpuffer]] zu "löschen".
  
Löschen habe ich deshalb in Anführungszeichen gesetzt, weil der Stencil-Puffer nicht einfach wie z.B. der Farbpuffer geleert wird, sondern durch {{INLINE_CODE|glClear(GL_STENCIL_BUFFER_BIT)}} mit einem vorher definierten Wert gefüllt wird.Dieser Wert wird (am besten bei der OpenGL-Initialisierung) mittels {{INLINE_CODE|[[glClearStencil]](s:Integer)}} festgelegt.In unserem Falle übergeben wir als hier Parameter den Wert 0.
+
Löschen habe ich deshalb in Anführungszeichen gesetzt, weil der [[Schablonenpuffer|Stencilpuffer]] nicht einfach wie z.B. der [[Farbpuffer]] geleert wird, sondern durch {{INLINE_CODE|glClear(GL_STENCIL_BUFFER_BIT)}} mit einem vorher definierten Wert gefüllt wird. Dieser Wert wird (am besten bei der OpenGL-Initialisierung) mittels {{INLINE_CODE|[[glClearStencil]](s:Integer)}} festgelegt. In unserem Falle übergeben wir als hier Parameter den Wert 0.
  
 
  // Löschen des Stencil-Puffers zum Beginn der Szene
 
  // Löschen des Stencil-Puffers zum Beginn der Szene
Zeile 82: Zeile 81:
 
===Den Stencil-Puffer vorbereiten und die Maske erstellen===
 
===Den Stencil-Puffer vorbereiten und die Maske erstellen===
  
Kommen wir nun also zum schwierigsten Teil dieses Tutorials.Die folgenden Zeilen aktiveren den Stencil-Puffer und erstellen die Maske unseres Spiegels in diesem Puffer.Leider lassen sich die jetzt kommenden Befehle und Funktionen nicht ganz so einfach beschreiben, aber ich gebe mein bestes!
+
Kommen wir nun also zum schwierigsten Teil dieses Tutorials. Die folgenden Zeilen aktiveren den [[Schablonenpuffer|Stencilpuffer]] und erstellen die Maske unseres Spiegels in diesem Puffer. Leider lassen sich die jetzt kommenden Befehle und Funktionen nicht ganz so einfach beschreiben.
  
 
  glColorMask(False, False, False, False);
 
  glColorMask(False, False, False, False);
  
Mit der Funktion {{INLINE_CODE|[[glColorMask]](R,G,B,A : ByteBool)}} teilen wir OpenGL mit, welche Farbkomponenten auf den Bildschirm gezeichnet werden.In unserem Falle deaktivieren wir alle Farbkomponenten, es wird also nichts auf den Bildschirm gezeichnet.<br>
+
Mit der Funktion {{INLINE_CODE|[[glColorMask]](R,G,B,A : ByteBool)}} teilen wir OpenGL mit, welche Farbkomponenten auf den Bildschirm gezeichnet werden. In unserem Falle deaktivieren wir alle Farbkomponenten, es wird also nichts auf den Bildschirm gezeichnet.<br>
Dies ist insofern nötig, da wir jetzt erstmal unsere Maske in den Stencilpuffer zeichnen wollen, und dies kein Puffer ist in den man direkt hineinschreiben kann (wie das beim Farbpuffer der Fall ist).Wir müssen also den Weg über den Farbpuffer gehen.
+
Dies ist insofern nötig, da wir jetzt erstmal unsere Maske in den [[Schablonenpuffer|Stencilpuffer]] zeichnen wollen, und dies kein Puffer ist in den man direkt hineinschreiben kann (wie das beim Farbpuffer der Fall ist). Wir müssen also den Weg über den Farbpuffer gehen.
  
 
  glEnable(GL_STENCIL_TEST);
 
  glEnable(GL_STENCIL_TEST);
  
Zu diesem Befel muss ich wohl kaum Worte verlieren.Kurz und schmerzlos : Er aktviviert den Stencil-Test und damit verbunden auch den Stencil-Puffer.
+
Zu diesem Befehl muss ich wohl kaum Worte verlieren. Kurz und schmerzlos : Er aktiviert den Stencil-Test und damit verbunden auch den [[Schablonenpuffer|Stencilpuffer]].
  
 
  [[glStencilFunc]](GL_ALWAYS, 1, 1);
 
  [[glStencilFunc]](GL_ALWAYS, 1, 1);
  
Mit diesem Befehl teilen wir OpenGL mit, welcher Test auf jeden gezeichneten Pixel angewendet werden soll.Der erste Parameter '''GL_ALWAYS''' gibt an, das der Test immer erfolgreich ist.Der zweite Parameter gibt den Referenzwert für diesen Test an (den wir gleich noch brauchen werden).Wenn der Test z.B. '''GL_LESS''' wäre, dann würde das Pixelfragment den Test bestehen, wenn es kleiner als der im Referenzparameter angegebene Wert wäre.<br>
+
Mit diesem Befehl teilen wir OpenGL mit, welcher Test auf jeden gezeichneten Pixel angewendet werden soll. Der erste Parameter '''GL_ALWAYS''' gibt an, dass der Test immer erfolgreich ist. Der zweite Parameter gibt den Referenzwert für diesen Test an (den wir gleich noch brauchen werden). Wenn der Test z.B. '''GL_LESS''' wäre, dann würde das Pixelfragment den Test bestehen, wenn es kleiner als der im Referenzparameter angegebene Wert wäre.<br>
Der letzte Parameter gibt die Maske an, mit der unsere Referenzparameter beim Gelingen des Tests verunded wird.Wenn der Test also gelingt, wird eine 1 in den Stencil-Puffer geschrieben (Test gelungen->Stencil-Wert = Refernzwert UND Maskenwert -> 1 UND 1 = 1).
+
Der letzte Parameter gibt die Maske an, mit der unsere Referenzparameter beim Gelingen des Tests verunded wird. Wenn der Test also gelingt, wird eine 1 in den [[Schablonenpuffer|Stencilpuffer]] geschrieben (Test gelungen->Stencil-Wert = Refernzwert UND Maskenwert -> 1 UND 1 = 1).
  
 
  glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
 
  glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
  
Nach obigem OpenGl-Vorschlaghammer kommt jetzt wieder etwas leichter verdauliche Kost.Mit der Funktion {{INLINE_CODE|[[glStencilOp]](fail, zfail, zpass: TGLEnum)}} teilen wir OpenGL mit, wie der Stencilwert verändert, wenn ein Fragment den Test besteht oder nicht besteht.Dabei unterscheidet OpenGL anhand der drei übergebenen Parameter drei verschiedene Szenarien :
+
Nach obigem OpenGl-Vorschlaghammer kommt jetzt wieder etwas leichter verdauliche Kost. Mit der Funktion {{INLINE_CODE|[[glStencilOp]](fail, zfail, zpass: TGLEnum)}} teilen wir OpenGL mit, wie der Stencilwert verändert wird, wenn ein Fragment den Test besteht oder nicht besteht. Dabei unterscheidet OpenGL anhand der drei übergebenen Parameter drei verschiedene Szenarien :
  
 
*Fail  
 
*Fail  
Zeile 109: Zeile 108:
 
: Wird angewendet, wenn das Fragment den '''Test besteht''' (Wichtigste Funktion!)
 
: Wird angewendet, wenn das Fragment den '''Test besteht''' (Wichtigste Funktion!)
  
Die ersten zwei Parameter sind in unserem Falle relativ uninteressant, weshalb wir OpenGL über '''GL_KEEP''' mitteilen das der Stencil-Puffer in diesen beiden Fällen nicht verändert wird.
+
Die ersten zwei Parameter sind in unserem Falle relativ uninteressant, weshalb wir OpenGL über '''GL_KEEP''' mitteilen dass der [[Schablonenpuffer|Stencilpuffer]] in diesen beiden Fällen nicht verändert wird.
Der dritte Parameter ist jedoch sehr wichtig.Er gibt ja an, was OpenGL mit dem Stencil-Puffer macht, wenn das Fragment den Test besteht.In unserem Fall soll der Stencil-Pufferwert über '''GL_REPLACE''' mit dem verundeten Masken- und Referenzwert (=1) ersetzt werden.
+
Der dritte Parameter ist jedoch sehr wichtig : Er gibt an, was OpenGL mit dem Stencil-Puffer macht, wenn das Fragment den Test besteht. In unserem Fall soll der Stencil-Pufferwert über '''GL_REPLACE''' mit dem veränderten Masken- und Referenzwert (=1) ersetzt werden.
 
 
  
 
==Wer die Hoffnung jetzt noch nicht verloren hat, der hats fast geschafft. Das Schwerste liegt bereits hinter uns!==
 
==Wer die Hoffnung jetzt noch nicht verloren hat, der hats fast geschafft. Das Schwerste liegt bereits hinter uns!==
Zeile 119: Zeile 117:
 
  DrawMirror;
 
  DrawMirror;
  
Diese drei Zeilen sind ja schon fast selbsterklärend, aber aufgrund dessen was oben auf das Gehirn zukam auch eher zur Entspannung gedacht.Bevor wir mit ''DrawMirror'' unsere Spiegelfäche in den Stencil-Puffer zeichnen, deaktivieren wir den Tiefentest und das Texturemapping.
+
Diese drei Zeilen sind ja schon fast selbsterklärend, aber aufgrund dessen was oben auf das Gehirn zukam auch eher zur Entspannung gedacht. Bevor wir mit ''DrawMirror'' unsere Spiegelfäche in den Stencil-Puffer zeichnen, deaktivieren wir den Tiefentest und das Texturemapping.
Nun haben wir eine unsichtbare Maske unseres Spiegels im Stencilpuffer.Überall dort, wo unsere Spiegelfläche in den Stencilpuffer gezeichnet wurde, steht eine 1 (=Referenzwert und Maksenwert, da der Test bestanden wurde).
+
Nun haben wir eine unsichtbare Maske unseres Spiegels im [[Schablonenpuffer|Stencilpuffer]]. Überall dort, wo unsere Spiegelfläche in den Stencilpuffer gezeichnet wurde, steht eine 1 (=Referenzwert und Maksenwert, da der Test bestanden wurde).
 
Solange der Stenciltest jetzt also aktiv bleibt, wird nur dort gezeichnet wo unsere Spiegelfläche im Stencilpuffer eine 1 hinterlies.
 
Solange der Stenciltest jetzt also aktiv bleibt, wird nur dort gezeichnet wo unsere Spiegelfläche im Stencilpuffer eine 1 hinterlies.
  
Zeile 128: Zeile 126:
  
 
Da wir nun beginnen die Szene hinter dem Spiegel (also quasi das Spiegelbild) zu zeichnen, aktivieren wir sowohl das Texturemapping als auch den Tiefentest wieder.
 
Da wir nun beginnen die Szene hinter dem Spiegel (also quasi das Spiegelbild) zu zeichnen, aktivieren wir sowohl das Texturemapping als auch den Tiefentest wieder.
Und da wir natürlich auch was von unserem Spiegelbild sehen woll und dies in den Farbpuffer gelangen soll, teilen wir OpenGL mittels {{INLINE_CODE|[[glColorMask]]()}} mit das wieder alle Farbkomponenten in den Farbpuffer gelangen sollen.
+
Und da wir natürlich auch was von unserem Spiegelbild sehen woll und dies in den Farbpuffer gelangen soll, teilen wir OpenGL mittels {{INLINE_CODE|[[glColorMask]]()}} mit, dass wieder alle Farbkomponenten in den Farbpuffer gelangen sollen.
  
 
  glStencilFunc(GL_EQUAL, 1, 1);
 
  glStencilFunc(GL_EQUAL, 1, 1);
  
Mit dieser Zeile teilen wie OpenGL mit, das nur dort gezeichnet werden soll, wo sich im Stencilpuffer eine 1 befindet (siehe oben).Dies ist das "Geheimnis" hinter realistisch aussehenden Reflektionen im Stencilpuffer!
+
Mit dieser Zeile teilen wie OpenGL mit, das nur dort gezeichnet werden soll, wo sich im [[Schablonenpuffer|Stencilpuffer]] eine 1 befindet (siehe oben). Dies ist das "Geheimnis" hinter realistisch aussehenden Reflektionen im Stencilpuffer!
  
 
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
 
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
  
Nachdem wir unseren Stencilpuffer ja bereits mit einer unsichtbaren Spiegelmaske gefüllt haben, soll er von nun an nicht mehr verändert werden.In allen drei Testfällen wird der aktuelle Stencilwert deshalb also mittels '''GL_KEEP''' nicht mehr verändert.
+
Nachdem wir unseren [[Schablonenpuffer|Stencilpuffer]] ja bereits mit einer unsichtbaren Spiegelmaske gefüllt haben, soll er von nun an nicht mehr verändert werden. In allen drei Testfällen wird der aktuelle Stencilwert deshalb also mittels '''GL_KEEP''' nicht mehr verändert.
  
 
  glEnable(GL_CLIP_PLANE0);
 
  glEnable(GL_CLIP_PLANE0);
 
  glClipPlane(GL_CLIP_PLANE0, @ClipPlane);
 
  glClipPlane(GL_CLIP_PLANE0, @ClipPlane);
  
Bevor wir jetzt dazu übergehen unsere gespiegelte Szene zu zeichnen, aktivieren wir noch die Schnittfläche unseres Spiegels.Die Erklärung dazu gabs ja bereits zum Beginn dieses Tutorials.
+
Bevor wir jetzt dazu übergehen unsere gespiegelte Szene zu zeichnen, aktivieren wir noch die Schnittfläche unseres Spiegels. Die Erklärung dazu gabs ja bereits zum Beginn dieses Tutorials.
  
 
===Die gespiegelte Szene zeichnen===
 
===Die gespiegelte Szene zeichnen===
  
So.Nachdem die Stencilpuffer-Tortur überstanden ist und mindestens die Hälfte der Leser eines unnatürlichen Todes gestorben sind, darf ich die anderen beglückwünschen.Wir sind fast fertig!
+
So. Nachdem die Stencilpuffer-Tortur überstanden ist und mindestens die Hälfte der Leser eines unnatürlichen Todes gestorben sind, darf ich die anderen beglückwünschen. Wir sind fast fertig!
  
Nachdem unser Stencilpuffer also inzwischen die unsichtbare Spiegelfläche enthält und er auch schon für das Zeichnen der gespiegelten Szene vorbereitet wurde, müssen wir die Szene nur noch gespiegelt zeichnen.Nichts leichter als das, denn mit [[glScalef]]() kann man eine Szene ja bekannterweise auf einfachste Art und Weise an einer Achse spiegeln.
+
Nachdem unser [[Schablonenpuffer|Stencilpuffer]] also inzwischen die unsichtbare Spiegelfläche enthält und er auch schon für das Zeichnen der gespiegelten Szene vorbereitet wurde, müssen wir die Szene nur noch gespiegelt zeichnen. Nichts leichter als dass, denn mit [[glScale|glScalef]]() kann man eine Szene ja bekannterweise auf einfachste Art und Weise an einer Achse spiegeln.
In unserem Falle müssen wir die Szene an der Z-Achse spiegeln, also der Achse an der unser Spiegel spiegelt.Das geht dann kurz und schmerzlos :
+
In unserem Falle müssen wir die Szene an der Z-Achse spiegeln, also der Achse an der unser Spiegel spiegelt. Das geht dann kurz und schmerzlos :
  
 
  glScalef(1,1,-1)
 
  glScalef(1,1,-1)
  
Wie gesagt...einfacher gehts also nimmer.Wenn wir unsere Szene z.b. auf dem Fussboden spiegeln wollten, dann würe ein einfaches {{INLINE_CODE|glScalef(1,-1,1)}} reichen.Das sieht in der Praxis dann im Endergebnis so aus :
+
Wie gesagt...einfacher gehts nimmer. Wenn wir unsere Szene z.b. auf dem Fussboden spiegeln wollten, dann würe ein einfaches {{INLINE_CODE|glScalef(1,-1,1)}} reichen. Dass sieht in der Praxis dann im Endergebnis so aus :
  
{{center| [[Bild: Tutorial_Stencil_Trooper05.jpg]]}}
+
{{Center| [[Bild: Tutorial_Stencil_Trooper05.jpg]]}}
  
Anschliessend wird dann die Szene wie gewohnt gezeichnet.Wenn die Szene etwas komplexer ist, sollte man das Zeichnen in eine externe Prozedur auslagern, die man dann zweimal aufrufen muss.
+
Anschliessend wird dann die Szene wie gewohnt gezeichnet. Wenn die Szene etwas komplexer ist, sollte man das Zeichnen in eine externe Prozedur auslagern, die man dann zweimal aufrufen muss.
 
Nicht vergessen : Vor dem skalieren der Matrix sollte man diese mit [[glPushMatrix]] auf den Matrizenstack legen, und vor dem Zeichnen der normalen Szene wieder mit '''glPopMatrix''' wiederherstellen!
 
Nicht vergessen : Vor dem skalieren der Matrix sollte man diese mit [[glPushMatrix]] auf den Matrizenstack legen, und vor dem Zeichnen der normalen Szene wieder mit '''glPopMatrix''' wiederherstellen!
  
Zeile 166: Zeile 164:
 
===Den Spiegel zeichnen===
 
===Den Spiegel zeichnen===
  
<pascal>glEnable(GL_BLEND);
+
<source lang="pascal">glEnable(GL_BLEND);
 
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
glColor4f(1,1,1,0.45);
 
glColor4f(1,1,1,0.45);
 
MirrorTex.Bind;
 
MirrorTex.Bind;
 
DrawMirror;
 
DrawMirror;
glDisable(GL_BLEND);</pascal>
+
glDisable(GL_BLEND);</source>
  
Nachdem wir nun unsere gespiegelte Szene hinter dem Spiegel gezeichnet haben, sollten wir noch unsere sichtbare Spiegelfläche zeichnen, um dem Spieler auch den Eindruck zu vermitteln das er vor einem Spiegel steht.Dazu benutze ich eine recht helle Glastextur, die ich mittels Alphablending über die gespiegelte Szene zeichne.Welchen Unterschied das macht, lässt sich auf folgenden beiden Screenshots (links ohne Spiegeltextur, rechts mit) sehr gut sehen :  
+
Nachdem wir nun unsere gespiegelte Szene hinter dem Spiegel gezeichnet haben, sollten wir noch unsere sichtbare Spiegelfläche zeichnen, um dem Spieler auch den Eindruck zu vermitteln, dass er vor einem Spiegel steht.Dazu benutze ich eine recht helle Glastextur, die ich mittels Alphablending über die gespiegelte Szene zeichne. Welchen Unterschied das macht, lässt sich auf folgenden beiden Screenshots (links ohne Spiegeltextur, rechts mit) sehr gut sehen :  
  
{{center| [[Bild: Tutorial_Stencil_Trooper06.jpg]] [[Bild: Tutorial_Stencil_Trooper07.jpg]]}}
+
{{Center| [[Bild: Tutorial_Stencil_Trooper06.jpg]] [[Bild: Tutorial_Stencil_Trooper07.jpg]]}}
  
  
 
===Und die Moral von der Geschicht...===
 
===Und die Moral von der Geschicht...===
...ohne die normale Szene dann zu rendern gehts nicht!Also nicht vergessen nach dem rendern des Spiegels auch noch die normale Spielszene zu zeichnen!
+
...ohne die normale Szene dann zu rendern gehts nicht! Also nicht vergessen nach dem rendern des Spiegels auch noch die normale Spielszene zu zeichnen!
  
  
 
==Nachwort==
 
==Nachwort==
  
So...das war also mein erstes Tutorial für die DGL.Für mich wars halb so schlimm, während es für einige von euch wohl doch einem Vorschlaghammer auf den Hinterkopf gleich kam.Das macht aber nix, denn wenn irgendjemand etwas nicht verstanden hat, ich bin ja auch als Mod im Forum tätig und antworte gerne auf eure Fragen!
+
So...das war also mein erstes Tutorial für die DGL.Für mich wars halb so schlimm, während es für einige von euch wohl doch einem Vorschlaghammer auf den Hinterkopf gleich kam. Das macht aber nix, denn wenn irgendjemand etwas nicht verstanden hat, ich bin ja auch als Mod im Forum tätig und antworte gerne auf eure Fragen!
  
Abschliessend sei noch zu sagen, das diese Technik einen Nachteil hat : Der Raum hinter der Spiegelfläche ist tabu, da man sonst einen Teil des illusionären Spiegelraumes dahinter sehen würde.Dieser Nachteil lässt sich jedoch recht leicht mit einigen Sichtbarkeitstechniken umschiffen, und der Nutzung solcher Spiegelungen sollte damit keine Grenzen mehr gesetzt sein!
+
Abschliessend sei noch zu sagen, dass diese Technik einen Nachteil hat : Der Raum hinter der Spiegelfläche ist tabu, da man sonst einen Teil des illusionären Spiegelraumes dahinter sehen würde.Dieser Nachteil lässt sich jedoch recht leicht mit einigen Sichtbarkeitstechniken umschiffen, und der Nutzung solcher Spiegelungen sollte damit keine Grenzen mehr gesetzt sein!
  
 
Hoffe das Tut hat euch gefallen und vor allem auch geholfen!
 
Hoffe das Tut hat euch gefallen und vor allem auch geholfen!
Zeile 192: Zeile 190:
 
Euer
 
Euer
  
Son of Satan (alias '''Sascha Willems''')
+
'''[[User:Sascha Willems|Sascha Willems]]''' (webmaster AT delphigl.de)
 +
 
 +
 
 +
==Nachtrag (Oktober 2005)==
 +
 
 +
Das Tutorial hat bereits einige Jahre auf dem Buckel, und in einer so schnellen Industrie wie der Grafikkartenindustrie ist das von größerer Bedeutung. Die Technik Spiegelungen mittels des Stencilpuffers zu realisieren ist heute kaum noch von Relevanz. Heute benutzt man so gut wie immer Render-To-Texture (z.B. via Pixelpuffer oder EXT_FBO) um Spiegelungen aller Art zu realisieren. Dadurch hat man diverse Vorteile :
 +
 
 +
* Die Spiegelung kann u.a. über Verschiebung der Mip-Maps schärfer bzw. dumpfer gemacht werden, so lassen sich auch einfach Reflektionen auf dumpfen Materialien wie z.B. verrostetes Metall darstellen.
 +
* Die Spiegelung liegt in einer Textur vor. Damit verbunden sind jede Menge Manipulationen möglich, sei es nun so was grundlegendes wie Multitexturing oder erweitertes Sachen wie Shader. Mit Shadern kann man dann recht einfach solche Sachen wie Verwirbelung oder Refraktion auf der Oberflächen (u.a. für Wasser genutzt) realisieren.
 +
 
 +
Von daher sollte man heutzutage auf die Spiegelungsmethode im Stencilpuffer '''verzichten''' und die Render-To-Texture Methode verwenden.
 +
 
 +
'''[[User:Sascha Willems|Sascha Willems]]''' (webmaster AT delphigl.de)
 +
 
 +
== Dateien ==
 +
* {{ArchivLink|file=tut_spiegelung_delphi_vcl|text=Delphi-VCL-Quelltext zum Tutorial}}
 +
* {{ArchivLink|file=tut_spiegelung_exe|text=Windows-Binary zum Tutorial}}
 +
 
 +
 
  
{{TUTORIAL_NAVIGATION|[[Tutorial_Bumpmaps_mit_Blender]]|[[Tutorial_StereoSehen]]}}
+
{{TUTORIAL_NAVIGATION|[[Tutorial_MultiTexturing]]|[[Tutorial_StereoSehen]]}}
 +
[[Kategorie:Tutorial|StencilSpiegel]]

Aktuelle Version vom 17. Mai 2009, 16:59 Uhr

Realistische Spiegelungen - Spiel, Spaß und Spannung mit dem Stencil-Puffer

Vorwort

Ave!

Herzlich willkommen zu meinem ersten Tutorial für DGL. Einige Leser dürften schon die von mir auf meiner Seite veröffentlichen Tutorials kennen, von nun an werde ich jedoch verstärkt Tutorials für die DGL veröffentlichen.

In dieser Lektion geht es um die realistische Darstellung von Spiegelungen mit Hilfe des Stencilpuffers.
Diese Technik findet langsam aber sicher Verbreitung in der Spieleindustrie. Das hat zum einen den Grund, das spiegelnde Flächen in der realen Welt sehr häufig vorkommen, und ihre Nachahmung einer 3D-Welt noch ein Stück mehr an Realitätsnähe verleiht. Der weitere Grund, warum realistische Spiegelungen erst seit kurzem verwendet werden, ist die Tatsache das die Szene mindestens zwei mal gerendert werden muß, und das die Grafikkarte einen Stencilpuffer mitbringen muß. Moderne 3D-Beschleuniger besitzen ihn natürlich, aber die damals so weit verbreitete Vodoo-Serie besaß diesen nicht.

Der Stencil-Puffer

Wie schon erwähnt ist der Stencilpuffer ein wichtigste Mittel um realistische Spiegelungen (und noch viele andere interessante Dinge) zu realisieren. Wie das Wort Stencil (=Schablone) schon andeutet, kann man in diesem Puffer eine Schablone ablegen, die später bestimmt welcher Bereich des Bildschirms überzeichnet, und welcher Bereich unberührt bleibt.
Die Tiefe des Stencilpuffers ist je nach Hardware unterschiedlich, inzwischen kann man aber auf jeder aktuelleren Karte von mindestens 8-Bit für diesen Puffer (oft werden dann die 24-Bit für den Tiefenpuffer mit diesen kombiniert um ein DWORD zu erhalten) ausgehen.

Ein praktisches Anwendungsbeispiel für die Nutzung dieses Puffers zur Begrenzung der zu überzeichnenden Fläche wäre z.B. die Cockpitansicht einer Flugsimulation. Zuerst wird der Stencilpuffer aktiviert, und dann wird das Cockpit in diesen Puffer "hineingezeichnet" und als Ausschlussmaske benutzt. Überall dort wo das Cockpit im Stencilpuffer einen Pixel hinterlassen hat wird später im Farbpuffer nicht gezeichnet.Dadurch muss das Cockpit also nur einmal gezeichnet werden. Die folgende Bildreihe verdeutlicht dies :

Tutorial Stencil cockpit01.jpg
Tutorial Stencil cockpit02.jpg
Tutorial Stencil cockpit03.jpg
Tutorial Stencil cockpit04.jpg
Cockpit-Textur (oder 3D-Modell)
(wird in den Stencilpuffer gezeichnet)
Inhalt des Stencil-Puffers
(Schwarz : Stencil-Wert = 0)
(Weiß : Stencil-Wert = 1)
Auf dem Bildschirm sichtbarer Teil der Szene Endergebnis

Jetzt fragt ihr euch bestimmt, was dieses Cokpit-Beispiel mit unserer Spiegelung zu tun hat? Ganz einfach : Zum einen erleichtert es das Verständnis der Funktionsweise des Stencilpuffers, was unbedingt notwendig ist, und zum anderen funktioniert die Sache mit dem Spiegel genau umgekehrt.
Während der Stencilpuffer im Cockpit-Beispiel dazu genutzt wird, den Teil an dem sich das Cockpit befindet vor dem Überschreiben durch andere Farbwerte zu bewahren, wird der Puffer bei einer Spiegelung dazu genutzt, den Bereich ausserhalb des Spiegels vorm Überschreiben zu schützen. So wird also nur der Teil der Szene gezeichnet, die sich auch "im" Spiegel befindet.
Wozu der Stencilpuffer also gut ist, lässt sich anhand der folgenden zwei Screenshots wohl am besten verdeutlichen. Während der Spiegel im linken Bild korrekt ist, und das Spiegelbild ausserhalb der Spieglfläche aufgrund des Stenciltests nicht gezeichnet wird, ist das Spiegelbild rechts falsch. Hier wurde der Stenciltest deaktiviert :

Tutorial Stencil Trooper04.jpg Tutorial Stencil Trooper03.jpg

Clipping Plane

Ein weiterer, für unseren Zweck notwendiger Begriff sind die Clipping Planes (zu Deutsch : Schnittflächen). Das ist aber nichts mathematisch unmögliches, sondern eine ganz einfache Fläche, an der die Geometrie "abgeschnitten" wird. D.h., dass alles was auf der per Vorzeichen definierten Seite dieser Schnittfläche liegt einfach nicht gezeichnet wird.
OpenGL bringt von Haus aus schonmal sechs solcher Clipping-Planes mit, die zusammen das Frustum ergeben, also der Quader, an dem die Geometrie aufgrund ihrer Sichtbarkeit "abgeschnitten" wird.

Darüberhinaus kann man jedoch selbst noch zusätzliche Schnittflächen definieren. Laut OpenGL-Vorgaben muß jeder OpenGL-Treiber mindestens sechs zusätzliche Schnittflächen anbieten können. Für unseren Spiegel reicht jedoch eine.

Eine Clippingplane wird mittels der Funktion glClipPlane(plane: TGLEnum; equation: PGLdouble), wobei die Fläche mittles eines array[0..3] of Double über ihre Gleichung Ax+By+Cz+D = 0 definiert wird. Aktiviert bzw. deaktiviert wird eine Schnittfläche mittels der Konstante GL_CLIP_PLANEi, wobei i für die Nummer der Schnittfläche steht und zwischen 0 und 5 liegt.

Das hört sich jetzt erstmal leicht trocken an, und dürfte ohne praktisches Beispiel auch nicht hängen bleiben. Deshalb hier zwei Screenshots aus dem Beispielprogramm zu diesem Tutorial, die verdeutlichen wozu die Schnittfläche benötigt wird. Der Spiegel liegt hier im Koordinatensystem am Ursprung (x:0,y:0,z:0) und "liegt" auf der Y-Achse.
Definiert wird dieser Spiegel deshalb als {{{1}}}. Das Minus vor der Eins gibt also quasi an, auf welcher Seite der Spiegelfläche geclippt werden soll. Würde der Spielerraum also auf der anderen Seite liegen, wäre das Vorzeichen positiv.

Ohne Clipping
Mit Clipping

Programmiertechnische Umsetzung

Nachdem wir nun mit der Begriffserklärung abgeschlossen haben und jedem klar sein sollte, was ein Stencilpuffers macht und wozu eine Clipping Plane gut ist, widmen wir uns nun der Implementation eines Spiegels in unser Programm. Der Quelltext zu diesem Thema ist zwar relativ kurz, bietet aber dennoch einiges an Erklärungsspielraum.
Deshalb : Nicht einfach nur abtippen, sondern auch sorgfältig meine mehr oder weniger ausführlichen Erklärungen mitlesen.

Die Renderprozedur sowohl für die normale Szene als auch für den Spiegelraum befindet sich im Beispielprogramm in der Prozedur DrawScene_Stencil. Im folgenden Kapitel werden alle relevante Teile dieser Prozedur besprochen, unwichtige Grundlagen lasse ich jedoch weg. Jeder der sich mit Spiegelungen beschäftigen will, sollte sich mit den OpenGL-Grundlagen auskennen.

Definition der Schnittfläche

Zu allererst definieren wir die Schnittfläche an der das Spiegelbild abgeschnitten wird. Würden wir diesen Teil weglassen, dann würden spätestens beim Durchschreiten eines Spiegels (auch wenn das in einem Ego-Shooter nicht vorkommen sollte) hässlich falsche Spiegelbilder entstehen...und das wollen wir ja nicht, schliesslich soll unser Spiegel ja die reale Welt nachstellen. Wie die Schnittfläche definiert wird, haben wir ja oben bereits besprochen. Wichtig wäre hier vielleicht noch, dass als Datentyp unbedingt Double verwendet werden muß. Andere Datentypen (z.B. Single) führen zu unvorhersehbaren Effekten.

const
 ClipPlane : array[0..3] of Double = (0, 0, -1, 0);

Den Stencil-Puffer löschen

Wie bekannt müssen (oder sollten) die verwendeten OpenGL-Puffer vor jedem neuen Zeichendurchgang gelöscht werden. Dies geschieht ja bekannterweise mit der Funktion glClear() und als Parameter oderverknüpft die zu löschenden Puffer. Neben den Bits für den Farbpuffer (GL_COLOR_BUFFER_BIT) und dem Tiefenpuffer (GL_DEPTH_BUFFER_BIT) kommt nun ein neues Puffer-Bit dazu, nämlich GL_STENCIL_BUFFER_BIT, um den Stencilpuffer zu "löschen".

Löschen habe ich deshalb in Anführungszeichen gesetzt, weil der Stencilpuffer nicht einfach wie z.B. der Farbpuffer geleert wird, sondern durch glClear(GL_STENCIL_BUFFER_BIT) mit einem vorher definierten Wert gefüllt wird. Dieser Wert wird (am besten bei der OpenGL-Initialisierung) mittels glClearStencil(s:Integer) festgelegt. In unserem Falle übergeben wir als hier Parameter den Wert 0.

// Löschen des Stencil-Puffers zum Beginn der Szene
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT or GL_STENCIL_BUFFER_BIT);


Den Stencil-Puffer vorbereiten und die Maske erstellen

Kommen wir nun also zum schwierigsten Teil dieses Tutorials. Die folgenden Zeilen aktiveren den Stencilpuffer und erstellen die Maske unseres Spiegels in diesem Puffer. Leider lassen sich die jetzt kommenden Befehle und Funktionen nicht ganz so einfach beschreiben.

glColorMask(False, False, False, False);

Mit der Funktion glColorMask(R,G,B,A : ByteBool) teilen wir OpenGL mit, welche Farbkomponenten auf den Bildschirm gezeichnet werden. In unserem Falle deaktivieren wir alle Farbkomponenten, es wird also nichts auf den Bildschirm gezeichnet.
Dies ist insofern nötig, da wir jetzt erstmal unsere Maske in den Stencilpuffer zeichnen wollen, und dies kein Puffer ist in den man direkt hineinschreiben kann (wie das beim Farbpuffer der Fall ist). Wir müssen also den Weg über den Farbpuffer gehen.

glEnable(GL_STENCIL_TEST);

Zu diesem Befehl muss ich wohl kaum Worte verlieren. Kurz und schmerzlos : Er aktiviert den Stencil-Test und damit verbunden auch den Stencilpuffer.

glStencilFunc(GL_ALWAYS, 1, 1);

Mit diesem Befehl teilen wir OpenGL mit, welcher Test auf jeden gezeichneten Pixel angewendet werden soll. Der erste Parameter GL_ALWAYS gibt an, dass der Test immer erfolgreich ist. Der zweite Parameter gibt den Referenzwert für diesen Test an (den wir gleich noch brauchen werden). Wenn der Test z.B. GL_LESS wäre, dann würde das Pixelfragment den Test bestehen, wenn es kleiner als der im Referenzparameter angegebene Wert wäre.
Der letzte Parameter gibt die Maske an, mit der unsere Referenzparameter beim Gelingen des Tests verunded wird. Wenn der Test also gelingt, wird eine 1 in den Stencilpuffer geschrieben (Test gelungen->Stencil-Wert = Refernzwert UND Maskenwert -> 1 UND 1 = 1).

glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

Nach obigem OpenGl-Vorschlaghammer kommt jetzt wieder etwas leichter verdauliche Kost. Mit der Funktion glStencilOp(fail, zfail, zpass: TGLEnum) teilen wir OpenGL mit, wie der Stencilwert verändert wird, wenn ein Fragment den Test besteht oder nicht besteht. Dabei unterscheidet OpenGL anhand der drei übergebenen Parameter drei verschiedene Szenarien :

  • Fail
Diese Funktion wird angewendet, wenn das Fragment den Stencil-Test nicht besteht
  • zFail
Wird angewendet, wenn das Fragment den Tiefentest nicht besteht
  • zPass
Wird angewendet, wenn das Fragment den Test besteht (Wichtigste Funktion!)

Die ersten zwei Parameter sind in unserem Falle relativ uninteressant, weshalb wir OpenGL über GL_KEEP mitteilen dass der Stencilpuffer in diesen beiden Fällen nicht verändert wird. Der dritte Parameter ist jedoch sehr wichtig : Er gibt an, was OpenGL mit dem Stencil-Puffer macht, wenn das Fragment den Test besteht. In unserem Fall soll der Stencil-Pufferwert über GL_REPLACE mit dem veränderten Masken- und Referenzwert (=1) ersetzt werden.

Wer die Hoffnung jetzt noch nicht verloren hat, der hats fast geschafft. Das Schwerste liegt bereits hinter uns!

glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
DrawMirror;

Diese drei Zeilen sind ja schon fast selbsterklärend, aber aufgrund dessen was oben auf das Gehirn zukam auch eher zur Entspannung gedacht. Bevor wir mit DrawMirror unsere Spiegelfäche in den Stencil-Puffer zeichnen, deaktivieren wir den Tiefentest und das Texturemapping. Nun haben wir eine unsichtbare Maske unseres Spiegels im Stencilpuffer. Überall dort, wo unsere Spiegelfläche in den Stencilpuffer gezeichnet wurde, steht eine 1 (=Referenzwert und Maksenwert, da der Test bestanden wurde). Solange der Stenciltest jetzt also aktiv bleibt, wird nur dort gezeichnet wo unsere Spiegelfläche im Stencilpuffer eine 1 hinterlies.

glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glColorMask(True, True, True, True);

Da wir nun beginnen die Szene hinter dem Spiegel (also quasi das Spiegelbild) zu zeichnen, aktivieren wir sowohl das Texturemapping als auch den Tiefentest wieder. Und da wir natürlich auch was von unserem Spiegelbild sehen woll und dies in den Farbpuffer gelangen soll, teilen wir OpenGL mittels glColorMask() mit, dass wieder alle Farbkomponenten in den Farbpuffer gelangen sollen.

glStencilFunc(GL_EQUAL, 1, 1);

Mit dieser Zeile teilen wie OpenGL mit, das nur dort gezeichnet werden soll, wo sich im Stencilpuffer eine 1 befindet (siehe oben). Dies ist das "Geheimnis" hinter realistisch aussehenden Reflektionen im Stencilpuffer!

glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

Nachdem wir unseren Stencilpuffer ja bereits mit einer unsichtbaren Spiegelmaske gefüllt haben, soll er von nun an nicht mehr verändert werden. In allen drei Testfällen wird der aktuelle Stencilwert deshalb also mittels GL_KEEP nicht mehr verändert.

glEnable(GL_CLIP_PLANE0);
glClipPlane(GL_CLIP_PLANE0, @ClipPlane);

Bevor wir jetzt dazu übergehen unsere gespiegelte Szene zu zeichnen, aktivieren wir noch die Schnittfläche unseres Spiegels. Die Erklärung dazu gabs ja bereits zum Beginn dieses Tutorials.

Die gespiegelte Szene zeichnen

So. Nachdem die Stencilpuffer-Tortur überstanden ist und mindestens die Hälfte der Leser eines unnatürlichen Todes gestorben sind, darf ich die anderen beglückwünschen. Wir sind fast fertig!

Nachdem unser Stencilpuffer also inzwischen die unsichtbare Spiegelfläche enthält und er auch schon für das Zeichnen der gespiegelten Szene vorbereitet wurde, müssen wir die Szene nur noch gespiegelt zeichnen. Nichts leichter als dass, denn mit glScalef() kann man eine Szene ja bekannterweise auf einfachste Art und Weise an einer Achse spiegeln. In unserem Falle müssen wir die Szene an der Z-Achse spiegeln, also der Achse an der unser Spiegel spiegelt. Das geht dann kurz und schmerzlos :

glScalef(1,1,-1)

Wie gesagt...einfacher gehts nimmer. Wenn wir unsere Szene z.b. auf dem Fussboden spiegeln wollten, dann würe ein einfaches glScalef(1,-1,1) reichen. Dass sieht in der Praxis dann im Endergebnis so aus :

Tutorial Stencil Trooper05.jpg

Anschliessend wird dann die Szene wie gewohnt gezeichnet. Wenn die Szene etwas komplexer ist, sollte man das Zeichnen in eine externe Prozedur auslagern, die man dann zweimal aufrufen muss. Nicht vergessen : Vor dem skalieren der Matrix sollte man diese mit glPushMatrix auf den Matrizenstack legen, und vor dem Zeichnen der normalen Szene wieder mit glPopMatrix wiederherstellen!

glDisable(GL_CLIP_PLANE0);
glDisable(GL_STENCIL_TEST);

Nachdem wir unsere gespiegelte Szene gezeichnet haben, diese an der definierten Schnittfläche (wenn nötig) abgeschnitten wurde und aufgrund der Stencilmaske nur in die Spiegelfläche gezeichnet wurden, können sowohl die Schnittfläche als auch der Stenciltest wieder deaktiviert werden.

Den Spiegel zeichnen

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1,1,1,0.45);
MirrorTex.Bind;
DrawMirror;
glDisable(GL_BLEND);

Nachdem wir nun unsere gespiegelte Szene hinter dem Spiegel gezeichnet haben, sollten wir noch unsere sichtbare Spiegelfläche zeichnen, um dem Spieler auch den Eindruck zu vermitteln, dass er vor einem Spiegel steht.Dazu benutze ich eine recht helle Glastextur, die ich mittels Alphablending über die gespiegelte Szene zeichne. Welchen Unterschied das macht, lässt sich auf folgenden beiden Screenshots (links ohne Spiegeltextur, rechts mit) sehr gut sehen :

Tutorial Stencil Trooper06.jpg Tutorial Stencil Trooper07.jpg


Und die Moral von der Geschicht...

...ohne die normale Szene dann zu rendern gehts nicht! Also nicht vergessen nach dem rendern des Spiegels auch noch die normale Spielszene zu zeichnen!


Nachwort

So...das war also mein erstes Tutorial für die DGL.Für mich wars halb so schlimm, während es für einige von euch wohl doch einem Vorschlaghammer auf den Hinterkopf gleich kam. Das macht aber nix, denn wenn irgendjemand etwas nicht verstanden hat, ich bin ja auch als Mod im Forum tätig und antworte gerne auf eure Fragen!

Abschliessend sei noch zu sagen, dass diese Technik einen Nachteil hat : Der Raum hinter der Spiegelfläche ist tabu, da man sonst einen Teil des illusionären Spiegelraumes dahinter sehen würde.Dieser Nachteil lässt sich jedoch recht leicht mit einigen Sichtbarkeitstechniken umschiffen, und der Nutzung solcher Spiegelungen sollte damit keine Grenzen mehr gesetzt sein!

Hoffe das Tut hat euch gefallen und vor allem auch geholfen!

Euer

Sascha Willems (webmaster AT delphigl.de)


Nachtrag (Oktober 2005)

Das Tutorial hat bereits einige Jahre auf dem Buckel, und in einer so schnellen Industrie wie der Grafikkartenindustrie ist das von größerer Bedeutung. Die Technik Spiegelungen mittels des Stencilpuffers zu realisieren ist heute kaum noch von Relevanz. Heute benutzt man so gut wie immer Render-To-Texture (z.B. via Pixelpuffer oder EXT_FBO) um Spiegelungen aller Art zu realisieren. Dadurch hat man diverse Vorteile :

  • Die Spiegelung kann u.a. über Verschiebung der Mip-Maps schärfer bzw. dumpfer gemacht werden, so lassen sich auch einfach Reflektionen auf dumpfen Materialien wie z.B. verrostetes Metall darstellen.
  • Die Spiegelung liegt in einer Textur vor. Damit verbunden sind jede Menge Manipulationen möglich, sei es nun so was grundlegendes wie Multitexturing oder erweitertes Sachen wie Shader. Mit Shadern kann man dann recht einfach solche Sachen wie Verwirbelung oder Refraktion auf der Oberflächen (u.a. für Wasser genutzt) realisieren.

Von daher sollte man heutzutage auf die Spiegelungsmethode im Stencilpuffer verzichten und die Render-To-Texture Methode verwenden.

Sascha Willems (webmaster AT delphigl.de)

Dateien



Vorhergehendes Tutorial:
Tutorial_MultiTexturing
Nächstes Tutorial:
Tutorial_StereoSehen

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