Linien zeichnen
Beim letzten Mal haben wir uns mit den grundlegenden Methoden der
Polygonkonstruktion unter OpenGL beschäftigt. OpenGL unterstützt nur eine kleine
Anzahl primitiver geometrischer Objekte: Punkte, Linien, Polygone und
Oberflächen, die durch viele kleine Drei- oder Vierecke beschrieben werden.
Die Grundidee hinter der Einfachheit von OpenGL ist, daß es die
Aufgabe des Entwicklers sein soll, mit Hilfe dieser einfachen Objekte komplexere
geometrische Modelle zu implementieren.
Die Pixelgröße kann zum Beispiel bestimmt werden, indem man
glPointSize benutzt:
void glPointSize(GLfloat size)
Die Standardgröße für Punkte beträgt 1.0 und size muß stets größer als
Null sein. Weiterhin ist zu beachten, daß die Punktgröße durch eine Fließkommazahl
angegeben wird; Punkte- und Liniengrößen mit Fließkommazahlen sind erlaubt. OpenGL
interpretiert Dezimalbrüche bei Punktgrößen gemäß der Umgebung beim Rendern. Wenn
der anti-aliasing Modus eingeschaltet ist, verändert OpenGL in Frage kommende
Nachbarpunkte einer Linie, um ihr so den Anschein einer Bruchzahl als Linienstärke
zu geben. Anti-Aliasing ist eine Technik, die auch benutzt wird, um diese häßlichen
Sterne zu entfernen, die auf Bildschirmen mit einer geringen Auflösung
auftauchen. Wenn anti-aliasing abgeschalten ist, rundet glPointSize
size auf die nächste Ganzzahl ab.
Die physikalische Pixelgröße ist jedoch geräteabhängig. Auf einem Monitor mit
geringer Auflösung beispielsweise erscheint ein Pixel recht groß. Gleichermaßen kann es
vorkommen, daß auf sehr hochauflösenden Geräten, wie einem Plotter zum Beispiel,
die Standardlinie mit einer Stärke von einem Pixel schon fast nicht mehr erkennbar
ist. Um die wahre Breite einer Linie einschätzen zu können, muß man also die
tatsächlichen physikalischen Dimensionen der Pixel auf dem Ausgabegerät kennen.
Die Linienstärke wird durch die glLineWidth-Funktion angegeben, die vor
dem glBegin() - glEnd() Paar aufgerufen werden muß, die die Linie(n) zeichnen.
Hier die komplette Syntax der Funktion:
void glLineWidth(GLfloat width)
Es kann passieren, daß OpenGL Implementationen die Stärke von Linien ohne
anti-aliasing auf die maximalen Stärken von Linien mit anti-aliasing, auf den
nächsten Ganzzahlwert gerundet, beschränken. Desweiteren ist zu beachten, daß
Linienstärken nicht senkrecht zur Linie gemessen werden, sondern in die y-Richtung,
falls der absolute Wert der Steigung kleiner als 1 ist; entsprechend in die x-Richtung,
wenn die Steigung absolut größer als 1 ist.
In diesem Monat haben wir eine andere einfache, aber hoffentlich nützliche
2D Animation vorbereitet, die aufzeigt, wie man verschiedene Linienstärken
in OpenGL Applikationen verwendet. (../../common/March1998/example2.c,
../../common/March1998/Makefile). Ich habe ein Beispiel aus der Quantenphysik
gewählt, ein Quantenteilchen, das in einem Doppelmuldenpotential eingeschlossen
ist. Warum? Hmmh, eigentlich weiß ich das auch nicht mehr. Jedenfalls
könnte ich mir vorstellen, daß es nützlich für Physik-
und Ingenieursstudenten sein kann, um zu sehen, wie man die zeitabhängige
Schrödinger Gleichung integriert. Alle anderen mögen es einfach
einmal genießen, die nicht-intuitive Natur der Quantenmechanik zu betrachten.
In der Quantenmechanik wird ein Teilchen nicht durch eine Position und
eine Geschwindigkeit repräsentiert, sonden durch eine "Welle", eine
Quantenwelle (die durchgehende purpurrote Linie in unserer Animation),
deren Betragsquadrat die Wahrscheinlichkeit angibt, das Teilchen an einer
gegebenen Position vorzufinden (die weiße gestrichelte Linie):
Bild 1. Screenshot der Quantensimulation
Für diejenigen mit etwas Erfahrung in gewöhnlichen Differentialgleichungen
kann ich sagen, daß die Wellengleichung durch einen FFT (Fast Fourier
Transform) Split-Operatormethode integriert ist. Diese Methode ist weitaus
genauer und schneller als irgendeine endliche Differenzenmethode. Sie ist
auf nicht-lineare Wellenausbreitung anwendbar; der Operator für die
Zeitentwicklung wird in zwei (oder mehr) Operatoren aufgeteilt, die entweder
vom Ort oder vom Impuls (der Frequenz) abhängig sind. Dann wird die
Wellenfunktion zeitlich entwickelt indem man diese Operatoren abwechselnd
anwendet und dabei zwischen Orts- und Impuls- (Frequenz) Raum hin und her
springt.
Das Grundgerüst des Quellprogramms kann für viele andere Applikationen benutzt
werden. Man kann meine Quantensimulation durch eine andere zeitabhängige Funktion
austauschen und schon hat man eine nette Animation des neuen Systems. Man könnte
natürlich auch versuchen, ein vereinfachtes OpenGL-basiertes gnuplot zu schreiben,
um Funktionen oder Datendateien graphisch darzustellen.
Wenn der Leser die vorherigen Artikel über GLUT und OpenGL verfolgt hat, wird er
diesen Quellcode sehr leicht verstehen können (wenn man die Quantenmechanik beiseite
läßt, selbstverständlich). Es handelt sich um nichts Außergewöhnliches. In der main() Funktion
öffnen wir ein einfaches Fenster im double-buffer Modus, dann definieren wir display()
und idle() Callbackfunktionen, die das Zeichnen bzw. die Integration der
Wellenfunktion übernehmen. Man sollte sich nochmals Gedanken über die idle() Funktion
machen, obwohl es ein sehr raffinierter Trick ist, der jedoch nicht unbedingt
vollständig verstanden werden muß, um den Inhalt dieses Artikels zu begreifen. Die
wirklich neue OpenGL Thematik ist die display() Callbackfunktion:
void
display (void)
{
static char label[100];
float xtmp;
/* Zeichenfläche löschen */
glClear (GL_COLOR_BUFFER_BIT);
/* Fußzeile schreiben */
glColor3f (0.0F, 1.0F, 1.0F);
sprintf (label, "(c)Miguel Angel Sepulveda 1998");
glRasterPos2f (-1.1, -1.1);
drawString (label);
/* Feines Koordinatensystem zeichnen */
glLineWidth (0.5);
glColor3f (0.5F, 0.5F, 0.5F);
glBegin (GL_LINES);
for (xtmp = -1.0F; xtmp < 1.0F; xtmp += 0.05)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Äußeres Rechteck zeichnen */
glColor3f (0.1F, 0.80F, 0.1F);
glLineWidth (3);
glBegin (GL_LINE_LOOP);
glVertex2f (-1.0F, -1.0F);
glVertex2f (1.0F, -1.0F);
glVertex2f (1.0F, 1.0F);
glVertex2f (-1.0F, 1.0F);
glEnd ();
/* Koordinatensystem zeichnen */
glLineWidth (1);
glColor3f (1.0F, 1.0F, 1.0F);
glBegin (GL_LINES);
for (xtmp = -0.5; xtmp < 1.0; xtmp += 0.50)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Koordinatenachsen zeichnen */
glLineWidth (2);
glBegin (GL_LINES);
glVertex2f (-1.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, -1.0);
glVertex2f (0.0, 1.0);
glEnd ();
/* Achsenbeschriftungen */
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, "Position");
glRasterPos2f (0.80F, 0.025F);
drawString (label);
glColor3f (1.0F, 0.0F, 1.0F);
sprintf (label, " Quantum Probability ");
glRasterPos2f (0.025F, 0.90F);
drawString (label);
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, " Real(Psi) ");
glRasterPos2f (0.025F, 0.85F);
drawString (label);
/* Wellenfunktion zeichnen */
psiDraw (NR_POINTS, psi, x);
/* Potentialfunktion zeichnen */
potentialDraw (NR_POINTS, potential, x);
glutSwapBuffers ();
};
Die erste Aktion ist das Löschen des color buffer bit, was uns eine freie
(schwarze) Zeichenfläche liefert. Dann fügen wir eine Fußnote mit glRasterPos und
glutBitmapCharacter ein (drawString ist nichts anderes als ein Wrapper für
das clut utility). In zukünfigen Kursen werden wir glRasterPos nochmals als
Hilfsfunktion zum Rendern von Text antreffen. Weder OpenGL noch GLUT bieten eine
einfache und leistungsstarke Möglichkeit, Text in einer Grafik darzustellen. Der
glutBitmapCharacter rastert grob gesagt eine Schriftbitmap auf den Farbpuffer.
Nach der Fußnote kommt eine weitere Anzahl von Anweisungen: der Rahmen, das
Hintergrundkoordinatensystem, die Koordinatenachsen und natürlich die Kurven dieser Ausgabe, die
durch psiDraw und potentialDraw gezeichnet werden. Bevor jede Linie gerendert wird,
steht ein glLineWidth, das die Anzahl der Pixel festsetzt, die die Linie
haben soll. Bild 1 zeigt das Ergebnis auf einem X window System (Linux Alpha). Aus
einem für mich unerschließlichen Grund sieht der Output des selben Programms unter
Windows 95 sehr besch...eiden aus, es scheint so, als wäre das antialiasing Feature
nicht sehr gut vom SGI OpenGL Treiber unterstützt; es ist schwer, Linien zu
unterscheiden, die eigentlich verschiedene Stärken haben sollten, und das
Koordinatensystem im Hintergrund sieht auch recht gleich aus. Diese Fehler treten
auf, wenn die Anzeige auf eine hohe Auflösung eingestellt wird, es ist also kein
Artefakt eines Monitors mit geringer Auflösung. Ich bin froh darüber, sagen zu
können, daß das Linux X window System Win95/NT einmal mehr um Längen schlägt.
Es gibt zwei Arten des Linienrenderings in der display() Funktion, und zwar den
GL_LINES Modus, der die Eckpunkte eines Polygonzugs (sog. "Vertices") durch eine durchgehende
Linie verbindet und den QL_LINE_LOOP Modus, der am Ende diese Punkte zu einer Schleife schließt.
Linien im Antialiasing
Modus erstellen
Ich habe antialiasing für die Linien in der reshape()
Callbackfunktion aktiviert,
void
reshape (int w, int h)
{
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
/* Linien im Antialiasing-Modus ermöglichen: */
glEnable (GL_LINE_SMOOTH);
glEnable (GL_LINE_STIPPLE);
};
Was hat es mit GL_LINE_STIPPLE auf sich? OpenGL gibt uns nicht nur die Kontrolle
über die Stärke einer Linie, sondern auch über ihren Stil. Durch das Aktivieren von
GL_LINE_STIPPLE wird es uns ermöglicht, gestrichelte oder Linien irgendeinen anderen
Stils zu zeichnen. Die einzige gestrichelte Linie in der Animation kommt in der psiDraw()
Funktion vor:
glLineWidth (1);
glPushAttrib (GL_LINE_BIT);
glLineStipple (3, 0xAAAA);
glBegin (GL_LINE_STRIP);
for (i = 0; i < nx; i++)
{
xs = ratio1 * (x[i] - XMIN) - 1.0;
ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
glVertex2d (xs, ys);
};
glEnd ();
glPopAttrib ();
Gestrichelte Linien
Das glLineStipple gibt den für gestrichelte Linien zu verwendenden Stil
an, in unserem Beispiel haben wir den Stil 0xAAAA. Binär geschrieben wäre das die
Zahlenfolge 0000100010001000 und OpenGL interpretiert dieses Muster als 3 Bits aus, 1
Bit an, 3 Bits aus, 1 Bit an, 3 Bits aus, 1 Bit an und schließlich 4 Bits aus. Ja,
richtig, das Muster wird rückwärts gelesen, da die niederwertigen Bits zuerst
verwandt werden. Nun bekommt glLineStipple zwei Parameter und zwar das
gestrichelte Muster, das eine hexadezimale Zahl sein sollte und ein Vielfaches einer
Ganzzahl, das dazu dient, das Muster zu skalieren. Mit einem Faktor von 3 würde
unsere gestrichelte Linie dargestellt als 9 Bits aus, 3 Bits an, 9 Bits aus, 3 Bits
an, 9 Bits aus, 3 Bits an und schließlich 12 Bits aus. Durch Herumspielen mit den
Faktoren und binären Mustern kann man sämtliche Arten komplizierter gestrichelter
Linien zeichnen.
Ein weiteres Detail: Ich habe dieses Rendern der gestrichelten Linie zwischen
einem push und einem pop Attribute Ausdruck eingeschlossen. Im ersten Artikel haben
wir ja gesagt, daß OpenGL eine Statusmaschine ist, richtig? Nun, in zukünftigen
Artikeln werden wir uns diese push und pop Operationen genauer ansehen, aber kurz
gesagt tun wir mit dem ersten glPushAttrib (GL_LINE_BIT) nichts anderes als
den momentanen Wert der GL_LINE_BIT Statusvariable, die über
den Stil der gestrichelten Linien entscheidet, auf einen Stack zu speichern. Danach können wir
GL_LINE_BIT mit unserem glLineStipple Ausdruck verändern und wenn wir fertig
sind, rufen wir ein glPopAttrib auf, das uns unsere alte GL_LINE_BIT
Variable zurückbringt. Dieser Mechanismus ist eine effektive Art, die Statusvariable
der OpenGL Maschine lokal zu verändern. Würden wir das nicht tun, hätten alle Linien,
die wir nach glLineStipple zeichnen würden, den selben Stil und wir wären
gezwungen, ein glLineStipple für jede Linie auszuführen, die wir jemals
in unserer Applikation zeichnen. Push & Pop erspart uns diese nervende Arbeit.
Beim nächsten Mal ....
OpenGL ist für seine fantastische 3D API Schnittstelle berühmt. Bis jetzt haben
wir einige elementare Möglichkeiten des 2D Renderings mit OpenGL erkundet. Beim
nächsten Mal werden wir OpenGL in 3D, das Einstellen einer Perspektive, das
Koordinatensystem, Clipping von Ebenen und Projektionsmatrizen unter die Lupe nehmen.
Bis dann noch viel Spaß mit OGL......
|