10 Clipping, Culling (Sichtbarkeitstest)
10.010 Wie kann ich feststellen, ob ein Vertex sichtbar ist oder nicht ?
Man kann den OpenGL Feedback Buffer nutzen, um festzustellen ob ein Vertex geclippt wird
oder nicht. Nachdem man mittels glRenderMode(GL_FEEDBACK) in den Modus geschaltet hat, kann
das Vertex an den Feedback Buffer als GL_POINTS übermittelt werden.
Dann schaltet man mit glRenderMode(GL_RENDER) zurück und testet die Grösse
des Feedback Buffers. Eine Grösse von Null kennzeichnet ein geclipptes Vertex.
Normalerweise sind die OpenGL Feedback Methoden langsam. Es kann deshalb sinnvoll
sein, den Clipping Test manuell durchzuführen. Um das zu realisieren,
sind zunächst 6 Flächengleichungen entsprechend den Begrenzungen des Clipping Volumes zu ermitteln.
Diese werden dann durch die aktuelle ModelView Matrix transformiert. Ist der Abstand
des Punktes zu einer oder mehreren Flächen kleiner Null, liegt er ausserhalb des
Viewing Volumes und wird geclippt.
Dieses GLUT-Beispiel zeigt, wie sich dieser Test
programmieren lässt.
Ein weiteres Beispiel nennt sich
Frustum Culling in OpenGL.
10.020 Wie kann ich den Sichtbarkeitstest beschleunigen ?
OpenGL bietet keine direkte Unterstützung zur Ermittlung, ob ein bestimmtes
Objekt von einem festgelegten Augpunkt sichtbar ist oder nicht. Braucht man diese
Information (z.B. um komplexe Objekte nicht unnötig zu berechnen), muss man
diesen Test manuell durchführen.
Die vorhergehende Frage zeigt, wie man das realisieren kann.
Das dort zu findende Beispiel wurde mit Nate Robin's hervorragendem Tutorials
kombiniert.
Abstraktere API's, wie z.B. Fahrenheits Large Model, können diese Funktionen
unter Umständen bereits enthalten.
Die HP OpenGL Implementation enthält einen Clipping Test für einen umschliessenden
Körper als Extension. Um diese Funktion zu nutzen, ist der Occlusion Test zu
aktivieren, ein umschliessender Körper zu zeichnen und durch den Aufruf von
glGetBooleanv() die Sichtbarkeit festzustellen.
10.030 Kann ich auch einen anderen als rechteckigen Viewport nutzen ?
Der OpenGL Stencil Buffer kann genutzt werden, um einen Bereich ausserhalb eines
nicht-rechteckigen Viewports zu maskieren. Mit einem aktivierten Stencil Buffer
und entsprechend gesetztem Stencil Test kann dann nur noch in den beliebig geformten
Viewport gezeichnet werden. Normalerweise wird der Stencil (Schablonen-) Bereich nur
einmal gezeichnet und die wechselnden Frames in den unmaskierten Bereich.
Genauso wie auch beim Tiefenbuffer muss das Programm auch beim Stencil Buffer
nach dem Anlegen eines OpenGL Contexts zunächst die Verfügbarkeit
prüfen. Als Beispiel:
glEnable (GL_STENCIL_TEST); /* Stencil Test aktivieren */
/* Vorbereitung, um ein einzelnes Bit in den Stencil Buffer ausserhalb des
Viewports zu zeichnen */
glStencilFunc (GL_ALWAYS, 0x1, 0x1);
/* Die Geometrie ausserhalb des Viewports zeichnen */
/* Der Stencil Buffer enthält nun ein gesetztes Bit im Bereich ausserhalb des Viewports */
/* Vorbereitung, um die Szene innerhalb des Viewports zu zeichnen */
glStencilFunc (GL_EQUAL, 0x0, 0x1);
/* jetzt kann die Szene innerhalb des Viewports so oft wie notwendig gezeichnet werden */
Sobald das erste Bit im Bereich ausserhalb des Viewports gezeichnet wurde, kann
das Programm die nächsten Objekte an beliebiger Stelle zeichnen. Liegt das
Objekt ausserhalb des Viewports, ist der Stencil Buffer auf glStencilFunc(GL_EQUAL,0x1,0x1)
zu setzen, liegt es innen, wird glStencilFunc(GL_EQUAL,0x0,0x1) benötigt.
Vergleichbare Resultate lassen sich auch nur mit Hilfe des Tiefenbuffers erzielen.
Nachdem die gesamte Szene gezeichnet wurde, kann man den Tiefenbuffer zurücksetzen
und den Bereich ausserhalb des Viewing Volumes durch weitere Primitive überdecken.
10.040 Wenn ein Vertex eines OpenGL Primitives ausserhalb des Fensters liegt,
sind die Farben oder Texturen plötzlich verfälscht. Was passiert hier ?
Dafür gibt es zwei potentielle Ursachen.
Wenn ein Objekt teilweise ausserhalb des Fensters liegt, schneidet es oft auch
die Grenzen des Viewing Volumes. OpenGL muss alle Objekte, die auch nur teilweise
ausserhalb des Viewing Volumes liegen, clippen. Dieses betrifft nicht nur die geometrische
Form, sondern auch den Farbverlauf, der im Clipping-Fall interpoliert wird.
Diese Interpolation (Berechnung des vermutlichen Farbverlaufs) berücksichtigt
zwar auch die Perspektive auf das Objekt, kann allerdings beim Rasterisieren zu
Abweichungen führen. Die Ursache hierfür findet man darin, dass die
Interpolation noch vor der Rasterisation durchgeführt wird.
Bei einigen OpenGL Implementationen werden die Texturkoordinaten perspektivisch
nicht korrekt rasterisiert. Man kann diese (performancebringende) Einschränkung
aber umgehen, indem man OpenGL dazu mit glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
veranlasst. Dagegen treten Farbverfälschungen durch das Clippen in fast jeder
OpenGL Implementation auf. Sind diese nicht hinnehmbar, kann also eine Textur
als Ausweichlösung genutzt werden.
Ein zweiter Grund, dass Color oder Texture Mapping nicht korrekt bei geclippten
Objekten arbeiten, kann darin zu finden sein, dass die ursprünglichen Farbverläufe
nicht plan sind. "Nicht plan" bedeutet bei Farben, dass die 3 RGB Komponenten
der Farbwerte aller beteiligten Vertices nicht in einer Ebene des 3D Farbraumes liegen
(fasst man RGB Werte als Koordinaten auf, zeichnet sie als XYZ und kann man keine
gemeinsame Ebene für alle Koordinaten finden, sind die Farbwerte nicht plan).
2D Texturkoordinaten liegen in diesem Sinne immer in einer Ebene.
Hier bezeichnet "nicht plan" den Umstand, dass eine angegebene Texturkoordinate
nicht zur Form des Objektes passt.
Nicht plane Farben oder Texturkoordinaten treten bei Dreiecken nicht auf, aber
sind bei GL_QUADS, GL_QUAD_STRIP und GL_POLYGON leicht möglich. Es gibt keinen
"richtigen" Weg, um nicht auf einer Ebene liegende Werte einfach zu interpolieren.
Spätestens die perspektivisch korrekte Interpolation kann Unterschiede zwischen
der vollen und geclippten Ansicht bewirken. Um diese Probleme zu umgehen, sollten
immer nur plan zueinander liegende Werte Verwendung finden.
10.050 Mir ist bekannt, dass das gesamte Objekt im sichtbaren Bereich liegt.
Wie kann ich das Clipping hierfür vermeiden und dadurch die Performance erhöhen?
Die OpenGL Spezifikation beinhaltet keinen Mechanismus, um das Clipping
im Viewing Volume abzuschalten. Dieses Clipping wird immer für jedes Vertex
durchgeführt, das an die OpenGL übergeben wird.
Einige Implementationen unterstützen die GL_EXT_clip_volume_hint Extension.
Ist diese Erweiterung verfügbar, setzt OpenGL nach dem Aufruf von
glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT,GL_FASTEST) voraus, dass das nachfolgende
Objekt komplett im Viewing Volume liegt und ein Clipping Test nicht erforderlich ist.
Das normale Clipping findet wieder statt, nachdem man den Aufruf mit dem Parameter
GL_DONT_CARE durchführt. Ist das Clipping deaktiviert und liegt ein Objekt
trotzdem ausserhalb des Viewing Volumes, ist das Verhalten undefiniert.
10.060 Wenn ich den Augpunkt nahe an ein Objekt verschiebe, beginnt dieses
zu verschwinden. Wie kann ich die vordere Clippingfläche deaktivieren ?
Diese Möglichkeit besteht nicht. Falls ihr darüber nachdenkt, sollte
man zunächst die Ursache des Problems erfassen:
Was ist, wenn der Blick auf einen Punkt in der Mitte der Szene gerichtet ist ?
Vielleicht liegen dann einige Objekte hinter dem Betrachter und muss deshalb
geclippt werden. Das Zeichnen der Szene unter diesen Bedingungen kann dann auch
unerwünschte Resultate zur Folge haben.
Damit brauchbare Ergebnisse aus der Projektion und dem Tiefenbuffervergleich
entstehen, darf die vordere Clippingebene zNear auch nicht auf Null gesetzt
werden (durch diese wird definiert und Divison durch Null oder sehr kleine Werte
funktioniert das nicht. zNear sollte also nicht kleiner als 1.0 sein).
Um Artifakte durch das Clipping zu vermeiden, muss das Programm die Position
des Augpunkts relativ zur Szene ständig kontrollieren. Dadurch kann man
sicherstellen, dass der Augpunkt nicht zu nahe an die Objekte der Szene gelangt.
Eine Möglichkeit zur Kontrolle besteht in einer Kollisionsberechnung, Informationen
findet man im Kapitel Verschiedenes, Abschnitt
Kollisonen.
Falls sichergestellt ist, das kein Objekt ausserhalb des Viewing Volumes liegt, kann
man auch die OpenGL Extensions zum Abschalten des Clipping Tests
nutzen.
10.070 Wie kann ich ein glBitmap() oder glDrawPixels(), deren ursprüngliche
Rasterposition ausserhalb des Fensters liegt, in der linken oder unteren Ecke zeichnen ?
Liegt die Rasterposition ausserhalb des Fensters, ist das auch oftmals ausserhalb
des Viewing Volumes und damit zumindest teilweise unzulässig.
Mit glBitmap und glDrawPixels dargestellte Primitive werden aber nur gezeichnet, wenn
die angegebene Rasterposition im gültigen Bereich liegt.
Da glBitmap/glDrawPixels von der angegebenen Position nach oben und rechts hin zeichnen,
erscheint es unmöglich, an die untere oder linke Kante angelehnte Primitive
zu verwirklichen.
Es gibt dafür aber einen Trick: Zunächst wird die Rasterposition auf
einen gültigen Wert innerhalb des Viewing Volumes gesetzt. Dann den folgenden
Aufruf:
glBitmap (0, 0, 0, 0, xMove, yMove, NULL);
Das veranlasst die OpenGL, ein kein Bitmap zu zeichnen sondern nur die Rasterposition
um xMove,yMove zu verschieben. Da nicht beachtet wird, ob die
Rasterposition danach ausserhalb des Viewing Volumes liegt, kann nun mit glBitmap()
oder glDrawPixels() das gewünschte Bild gezeichnet werden.
10.080 Warum arbeitet glClear() für Flächen ausserhalb des Scissor-Bereichs nicht ?
Die OpenGL Implementation legt fest, dass glClear() bei aktiviertem Scissor Test
nur den Bereich innerhalb des Scissor Rechtecks löscht. Um das gesamte Fenster
zu löschen, kann der folgende Code genutzt werden:
glDisable (GL_SCISSOR_TEST);
glClear (...);
glEnable (GL_SCISSOR_TEST);
10.090 Wie arbeitet das Flächen-Culling ? Warum greift es nicht
auf die Oberflächennormalen zurück ?
OpenGL's Face Culling berechnet die Ausrichtung der Fläche (die Drehrichtung
anhand der Reihenfolge der angegebenen Vertices) im Bildschirmkoordinatensystem.
Die Drehrichtung ist positiv, wenn die Vertices entgegen dem Uhrzeigersinn angegeben
wurden (CCW counter clockwise) bzw. negativ im Uhrzeigersinn (CW clockwise).
Das Programm kann aber glFrontFace() nutzen, um die Interpretation der Reihenfolge
zu erzwingen (front facing oder back facing festlegen). Mit glCullFace() kann auch
festgelegt werden, ob nur Vorder- und/oder auch Rückseite des Objektes
bei abgewandter Orientierung gezeichnet werden soll. Aktiviert wird das performancebringende
Culling mit glEnable(GL_CULL_FACE).
OpenGL berechnet das Culling aus zwei Gründen erst im Bildschirmkoordinatensystem.
Um vorteilhafte Lichteffekte zu erhalten, werden oft Normalenvektoren angegeben, die
nicht zwangsweise orthogonal zur Fläche liegen. würden diese Normalen
zur Berechnung des Culling genutzt, könnten einige Flächen irrtümlich
ausgeblendet werden.
Ausserdem könnte ein aus dem Kreuzprodukt zweier Vektoren berechneter Normalenvektor
für das Culling eine Matrixinversion benötigen, was nicht immer
realisiert werden kann (z.B. singuläre Matrix). Dagegen ist die Drehrichtung im
Bildschirmkoordinatensystem immer definiert.
Es gibt aber auch einige OpenGL Implementationen, die die GL_EXT_ cull_vertex
Extension unterstützen. Ist diese Extension vorhanden und legt man den Augpunkt
im Modellkoordinatensystem mit homogenen Koordinaten fest, werden alle Flächen
ausgeblendet, bei denen das Kreuzprodukt von Normalenvektor und Vertex-Augpunkt-Vektor
in Blickrichtung zeigt (also vom Betrachter weg).
Zeigen alle so berechneten Vektoren eines Objekts vom Betrachter weg, wird dieses
nicht mehr zur Anzeige gebracht. Damit kann ein deutlicher Geschwindigkeitsvorteil
erzielt werden, weil diese Berechnung frühzeitig in der Rendering Pipeline
durchgeführt wird.
|