| Linien zeichnenBeim 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]](../../common/March1998/ogl-thumb.gif)  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 erstellenIch 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 LinienDas 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...... |