Home Index Search Links About Us
[LinuxFocus Bild]
[Navegation Steuerleiste]
  Neuigkeiten   Archiv   Firmen   Tips  

OpenGL Programmierung: Mehr Über Linien

von Miguel Angel Sepúlveda

Aus dem Englischen ins Deutsche übersetzt von Joachim Wieland

../../common/March1998/example2.c

../../common/March1998/Makefile

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): 

[Click here to see the image]
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......


Mehr Informationen:
© 1998 Miguel Angel Sepulveda
Diese Webseite wird unterhalten von Miguel A. Sepulveda und Franz-Josef Lücke.