14 Rasterisation und Verändern des Framebuffers
14.010 Wie bekomme ich die Speicheradresse des OpenGL Framebuffers heraus,
so dass ich diesen direkt beschreiben kann ?
OpenGL bietet keinen direkten Zugriff auf die Adresse des Framebuffers. Es kann
allerdings möglich sein, dass einige Implementationen den Zugriff über eine
entsprechende Extension erlauben.
Normalerweise benötigten nur diejenigen Programmierer einen direkten Zugriff
auf den Framebuffer, die nur für ein spezielles Betriebssystem und ein spezielles
Grafik-Hardwareformat entwickeln (z.B. MS Windows VGA Standard). OpenGL verfolgt
allerdings ein völlig anderes Konzept, da es unabhängig vom verwendeten
Betriebssystem und der verfügbaren Hardware einen unveränderten Quelltext
bei gleichem Endergebnis ermöglicht.
Darüber hinaus kann es durchaus sein, dass spezielle OpenGL Hardware keinen
für die CPU addressierbaren Framebuffer bereitstellt.
Man kann den Inhalt des Color, Tiefen und Stencil Buffers mit glReadPixels() auslesen.
Zum Verändern des Inhaltes stehen auch die Funktionen glDrawPixels() und glCopyPixels()
zur Verfügung.
14.015 Wie benutze ich glDrawPixels() und glReadPixels() ?
Die Befehle glDrawPixels() und glReadPixels() schreiben und lesen rechteckige
Bereiche aus dem Framebuffer aus bzw. schreiben in diese. Über den Parameter
format ist auch der Zugriff auf den Stencil und Tiefenbuffer möglich.
Einzelne Pixel lassen sich dabei mit einer Breite und Höhe von jeweils 1.0 des angegebenen
Bereichs ansprechen.
glDrawPixels() startet an der "Current Raster Position" im oberen linken
Eck. Unbeabsichtigte Ergebnisse sind dabei oftmals auf eine falsch gesetzte Rasterposition
zurückzuführen. Wird die Rasterposition mit glRasterPos*() gesetzt, erfolgt
die Transformation genauso wie die jedes anderen Vertex. Danach schreibt glDrawPixels()
seine Daten an die berechnete Rasterposition im Device Koordinatensystem (dadurch ist es
möglich, Bilddaten im Koordinatensystem der 3D-Szene anzugeben).
Liegt die angegebene Rasterposition ausserhalb des Viewing Volumes, wird das Bild geclippt und
somit nicht dargestellt. Das geschieht also auch, wenn ein Teil des Bildes im sichtbaren
Bereich liegen würde.
Hier gibt es weitere Informationen dazu, wie man ein
teilweise geclipptes Bild trotzdem darstellen kann.
glReadPixels() nutzt dagegen nicht die angegebene Rasterposition. Es greift stattdessen
auf die X,Y Werte des Device Koordinatensystems zurück, die mittels der ersten
beiden Parameter von glReadPixels() angegeben werden können.
Genauso wie bei glDrawPixels() beginnt der auszulesende Bereich im linken oberen Eck.
Fehler können hierbei auftreten, wenn:
-
Der auszulesende Bereich in einem teilweise verdeckten Fenster oder ausserhalb
des Bildschirms liegt. Dann gibt glReadPixels() undefinierte Daten zurück
(Weitere Informationen).
-
Kein Speicherplatz für die ausgelesenen Daten reserviert wurde (der 7. Parameter
also ein NULL Zeiger ist). Als Folge entsteht ein SEG_FAULT, Core Dump oder ein
Programmabsturz. Falls diese Fehler auftreten, obwohl eigentlich ausreichend Speicher
bereitgestellt wurde, sollte man zunächst diesen Speicherbereich verdoppeln. Funktioniert
die Aktion jetzt, wurde der Speicherbereich vermutlich falsch abgeschätzt.
Für beide Funktionen, glDrawPixels() und glReadPixels(), sollte man folgendes
im Hinterkopf behalten:
-
Die Höhe und Breite wird in Pixeln angegeben.
-
Scheinen die gezeichneten bzw. gelesenen Daten auf den ersten Blick korrekt, aber
irgendwie leicht verschoben zu sein, sollte man die Ausrichtung der Daten kontrollieren (z.B. BGR statt RGB etc.)
Die Struktur der Daten wird durch die glPixelStore*() Funktionen kontrolliert, wobei die
PACK/UNPACK Werte den Datenaustausch mit der OpenGL regeln.
14.020 Wie kann ich während der Laufzeit des Programms zwischen dem
Double- und SingleBuffer Modus wechseln ?
Grundsätzlich ist es mit OpenGL nicht möglich, aus einem einmal erzeugten
Single Buffer Modus in den Double Buffer Modus zu wechseln.
Umgekehrt ist es allerdings möglich, aus einem Double Buffer Modus heraus
nur noch mit einem Single Buffer zu arbeiten. Hierzu muss glDrawBuffer() zu GL_FRONT
gesetzt und swapBuffer() durch glFlush() ersetzt werden. Zum Zurückschalten wird
glDrawBuffer() wieder auf GL_BACK gesetzt und swapBuffer() aufgerufen.
14.030 Wie kann ich ein einzelnes Pixel aus dem Framebuffer zurücklesen ?
Das lässt sich mittels glReadPixels() und einer Höhe und
Breite von jeweils 1.0 realisieren.
14.040 Wie bekomme ich die Tiefeninformation für ein bereits gezeichnetes Primitive heraus ?
Für ein einzelnes Pixel erhält man auch dessen Tiefeninformation mittels
glReadPixels(), allerdings im Bildschirmkoordinatensystem.
Da es notwendig sein kann, diesen Wert im Weltkoordinatensystem zu kennen, lässt
sich dieser Wert unter Angabe von X,Y,Z mittels gluUnProject() umrechnen.
Mehr Informationen gibt es hier.
14.050 Wie zeichne ich ein Muster in den Stencilbuffer ?
Der Stencil Buffer von OpenGL lässt sich wie folgt aktivieren:
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 0x1, 0x1);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
Alle nachfolgend gezeichneten Objekte aktivieren für jedes veränderte
Pixel den Stencil Buffer.
14.060 Wie kopiere ich vom Front- in den Backbuffer bzw. umgekehrt ?
Hierfür gibt es die Funktion glCopyPixels(). Die Quelle bzw. das Ziel dieser
Funktion werden durch die Aufrufe von glReadBuffer() bzw. glDrawBuffer() angegeben.
Um z.B. vom Front in den Back Buffer zu kopieren, wäre folgender Code möglich:
glReadBuffer (GL_BACK);
glDrawBuffer (GL_FRONT);
glCopyPixels (GL_COLOR);
14.070 Warum erhalte ich mit glReadPixels() keine gültigen Pixelwerte für mein
OpenGL-Fenster, wenn es teilweise von einem anderen Fenster überlappt wird ?
Das liegt am Ergebnis des OpenGL "Pixel Ownership Test". Wird ein OpenGL Fenster
von einem anderen Fenster verdeckt, braucht es für diesen verdeckten Bereich keine
Pixel zu speichern. Deshalb kann glReadPixels() in diesem Fall undefinierte Werte
zurückliefern.
Der Pixel Ownership Test wird von den meisten OpenGL Implementationen unterschiedlich
realisiert. Einige speichern den verdeckten Bereich in einem Off-Screen Buffer und sind
deshalb auch in der Lage, auf glReadPixels() auch gültige Werte zu liefern.
Die meisten Implementationen arbeiten allerdings direkt aus dem Framebuffer heraus
auf den Bildschirm. Verdeckte Bereiche werden dann nicht berücksichtigt und stehen
daher für glReadPixels() nicht zur Verfügung.
Um gültige Daten zu erhalten, kann man das OpenGL Fenster kurzzeitig in
den Vordergrund bringen und dann glReadPixels() auszuführen. Hier besteht aber
das Risiko, das der Anwender eingreift und das OpenGL Fenster schon während der
Operation wieder verdeckt.
Ein Verfahren, dass unter einigen Betriebssystemen arbeiten könnte, ist das
Zeichnen in ein nicht sichtbares Fenster wie z.B. als Pixmap unter X Windows (Unix).
Dieser Typ einer Zeichenoberfläche kann nicht durch den Anwender beeinflusst werden,
so dass sein Inhalt den Ownership Test absolvieren wird. Das Herauslesen sollte also
immer gültige Daten ergeben, wird allerdings oftmals nur in Software, also ohne
Beschleunigung durch die OpenGL Hardware, realisiert sein.
14.080 Warum ändert sich das Aussehen meines smooth-shaded Poylogons,
wenn ich es nach verschiedenen Transformationen betrachte ?
Eine OpenGL Implementation kann ein als QUAD gezeichnetes Polygon in zwei Dreiecke
aufsplitten, bevor die Rasterisation erfolgt. Erfolgt diese Aufsplittung, so ergeben
sich aufgrund anderer Interpolationsmethoden möglicherweise auch veränderte
Farbwerte.
Viele OpenGL Anwendungen vermeiden die Nutzung von QUAD Primitiven aufgrund der damit
verbundenen Rasterisationsproblemen. Da ein QUAD auch ohne zusätzlichen Aufwand
als GL_TRIANGLE_STRIP dargestellt werden kann, stellt diese Lösung auch einen einfachen
Weg dar, um Darstellungsfehler zu vermeiden.
14.090 Wie erhalte ich eine pixelgenaue Darstellung meiner Linien ?
Die OpenGL Spezifikation fordert zugunsten einer breiten Unterstützung
keine pixelgenaue Darstellung eines 3D Objektes.
Man sollte sich einmal die OpenGL Spezifikation durchlesen, um die Darstellung von
Linien nach der "Diamond Exit" Regel zu verstehen.
Kurzgefasst sagt diese Regel aus, dass innerhalb eines jeden Pixels eine Fläche in Form eines
Parallelograms (Diamond) existiert. Ein derartiges Pixel wird nur dann angezeigt, wenn
die darzustellende Linie gemäss der Geradengleichung durch dieses Parallelogram
verläft. Wird die Fläche des Parallelograms dagegen nicht berührt
(Diamond Exit), erfolgt auch keine Anzeige dieses Pixels.
14.100 Wie kann ich bei breiteren Linien die Endpunkte korrekt darstellen ?
OpenGL zeichnet breite Linien, indem mehrere Linien (der gewünschten Breite -1)
jeweils aneinander gefügt werden. Je nach Lage der endgültigen Linie werden
bei einer (überwiegenden)Ausdehnung nach Y die Einzelstücke mit einem Offset in X bzw. bei einer
Ausdehnung in X mit einem Offset in Y zusammengefügt.
Diese Technik kann hässliche Effekte an den Verbindungsstücken der Linienelemente
und Unterschiede in der erkennbaren Breite der Linie produzieren, abhängig von
der Schräglage der endgültigen Linie.
Die OpenGL verfügt leider über keinen Mechanismus, mehrere Linien mit
gleichen Endpunkten sauber zu verbinden.
Eine mögliche Lösung ist, geglättete (smooth antialiased)
Linien anstatt normaler Linien zu zeichnen. Um eine saubere Verbindung zwischen
den Linien zu erhalten, sollte der Tiefentest deaktiviert bzw. der Depth Test
mit GL_ALWAYS durchgeführt werden.
Die Frage zu geglätteten Linien
gibt noch mehr Informationen.
Eine zweite Lösung besteht darin, das Zusammenfügen und die Darstellung
der Linien weitgehend durch das Programm selbst zu kontrollieren.
Das kann man dadurch realisieren, indem statt Linien Polygone gezeichnet werden.
Hier sind die erforderlichen Eckpunkte allerdings selbst zu berechnen.
14.110 Wie kann ich rubber band Linien zeichnen bzw. nur einzelne Elemente
des Frames löschen, ohne den Rest zu verändern ?
Um "rubber-band" Linien (gestrichelte oder entfernbare Linien) in ein
Frame aufzunehmen, gibt es zwei Möglichkeiten.
Die erste besteht darin, Overlay Planes zu nutzen. Man zeichnet die Linien in eine
Overlay Plane (zweite Zeichenebene bzw. Folie), die unabhängig vom restlichen Frame
verändert oder gelöscht werden kann. Allerdings stehen diese Overlay Planes
nicht auf jedem System zur Verfügung, diese Lösung ist daher nicht immer
geeignet.
Die zweite Möglichkeit besteht in der Nutzung von logischen Operationen, indem
diese im XOR Modus aktiviert wird. Für ein RGBA Fenster kann der Code z.B.
so aussehen:
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(GL_XOR);
Nun kann man die aktuelle Farbe auf Weiss setzen und die Linien zeichnen. Im Bereich des
Linienverlaufs wird nun der Farbwert des Framebuffers invertiert. Ein nochmaliges
Zeichnen der Linie stellt den Ausgangszustand wieder her.
Die logischen Operationen für RGBA Fenster sind erst ab OpenGL 1.1 verfügbar, zuvor
arbeitete diese Funktion nur in Color Index Fenstern (mit GL_LOGIC_OP als Parameter von
glEnable).
14.120 Wenn ich ein Polygon einmal als Drahtgitter- und einmal als Flächenmodell
zeichne, treffen die Linien nicht exakt die gleichen Pixel. Warum ?
Ausgefüllte Polygone werden anders rasterisiert als deren Drahtgitterdarstellung.
Für das Flächenmodell eines Polygons wird ein zugehöriges Pixel genau dann
gezeichnet, wenn dessen Mittelpunkt innerhalb der Grenzen des Polygons liegt.
Dagegen wird die Linie der Drahtgitterdarstellung so gezeichnet, dass ein Pixel immer dann
zur Anzeige kommt, wenn die parallelogramähnliche Fläche innerhalb des Pixels
auch innerhalb des Polygons liegt (Diamond Exit Rule).
Damit kann es durchaus passieren, dass im Drahtgittermodus mehr Pixel im Kantenverlauf
dargestellt werden als im Flächenmodus. Bestimmte durch die OpenGL Spezifikation
geregelte Ausnahmen treffen hier nicht zu.
14.130 Wie zeichne ich ein Polygon über das gesamte Fenster ?
Diese Frage wird hier beantwortet.
14.140 Wie initialisiere oder lösche ich den Framebuffer, ohne dafür glClear() zu nutzen ?
Diese Frage wird hier beantwortet.
14.150 Wie kann ich Antialiasing auf Linien oder Polygone anwenden ?
Um einen weichen Linienverlauf (antialiased) zu realisieren, muss das Programm folgendes
beinhalten:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
Falls alle Linien der Szene weich gezeichnet werden sollen, muss der Tiefentest
abgeschaltet oder auf GL_ALWAYS gesetzt werden.
Enthält die Szene allerdings nicht nur weich gezeichnete Linien, kann der
Tiefentest nicht deaktiviert werden. Um eine nahezu ideale Darstellung zu erreichen,
kann man die weichen Linien als transparente Primitive zeichnen, wobei zunächst die
normalen und danach die Polygone mit Antialiasing abzuarbeiten sind. Als Reihenfolge
des Zeichnens sollte man die Anordnung von hinten nach vorn einhalten.
Im Bereich Transformationen findet man weitere Informationen.
Auch die Berücksichtigung der angesprochenen Ideen wird nicht in jedem Fall
vermeiden können, dass Artefakte an den Verbindungsstellen von weich gezeichneten
Linien auftreten, solange der Tiefentest aktiviert werden muss.
Darüber hinaus muss eine OpenGL Implementierung nicht zwingend das Antialiasing
für Polygone unterstützen, auch wenn der Parameter GL_POLYGON_SMOOTH
gesetzt wurde.
14.160 Wie aktiviere ich Antialiasing für das gesamte Fenster ?
Das Red Book erläutert eine
Multi-Pass Accumulation Buffer Technik, die bei Unterstützung durch die
Hardware auch sehr schnell arbeiten kann.
Dann gibt es seit OpenGL 1.2 auch eine zusätzliche Imaging Extension, die
das erzeugte Frame vor der Anzeige nochmals filtert. Eine ähnliche Technik, das Multisampling,
wird meist als Extension bereitgestellt.
|