OpenGL.org 3Dsource.de: Infos rund um 3D-Computergrafik, OpenGL und VRML
space Telefon-Tarife space VDI - Arbeitskreis Fahrzeugtechnik space Beschallungsanlagen und Elektroakustik space Heise-News space

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.

Seite durchsuchen nach:

Fragen oder Ideen an:
Thomas.Kern@3Dsource.de
Linux-Counter
(C) Thomas Kern