Lehrheft OpenGL
Transcription
Lehrheft OpenGL
Lehrheft OpenGL Zur Veranstaltung Computergraphik I (Grundlagen) Prof. Dr. Stefan Schlechtweg-Dorendorf Hochschule Anhalt Fachbereich Informatik Inhaltsverzeichnis 1 Grundlegendes zu OpenGL..............................................................................................................3 2 Einrichten des Arbeitsplatzes...........................................................................................................4 2.1 Microsoft Visual C++ 2008 Express Edition............................................................................4 2.2 Eclipse für Java.........................................................................................................................4 3 Arbeitsweise von OpenGL...............................................................................................................5 4 OpenGL-Rahmenprogramm.............................................................................................................6 4.1 C-Rahmenprogramm................................................................................................................6 4.2 JAVA-Rahmenprogramm..........................................................................................................7 5 Einfache 3D-Ausgabe.......................................................................................................................9 6 Transformationen............................................................................................................................12 6.1 Sichttransformation.................................................................................................................13 6.2 Modellierungstransformation(en)...........................................................................................14 6.3 Projektionstransformation.......................................................................................................16 6.4 Bildschirmtransformation.......................................................................................................18 6.5 Zusammenfassung Transformationen.....................................................................................18 7 Erzeugen eigener Geometrien........................................................................................................20 8 Animationen...................................................................................................................................23 9 Interaktion.......................................................................................................................................27 9.1 Tastatur....................................................................................................................................28 9.2 Maus........................................................................................................................................29 10 Beleuchtung und Materialien.......................................................................................................33 10.1 Oberflächennormalen............................................................................................................33 10.2 Lichtquellen..........................................................................................................................34 10.3 Materialbeschreibungen........................................................................................................35 1 Grundlegendes zu OpenGL OpenGL ist eine Bibliothek zur Programmierung von zwei- und dreidimensionalen graphischen Anwendungen. OpenGL ist plattformunabhängig, das heißt in diesem Fall, daß der Quellcode eines OpenGL-Programms auf verschiedenen Plattformen (Windows, Apple, UNIX) übersetzt werden kann und daß – die Installation der kor rekten Bibliotheken vorausgesetzt – die Programme sich auf den verschiedenen Plattformen gleich verhalten. OpenGL ist eine der ältesten Graphikbibliotheken. Ihre Spezifikation wurde bereits 1992 veröffentlicht. OpenGL hat sich zu einem Standard für die Graphikprogrammierung etabliert und wird heute von allen gängi gen Betriebssystemen unterstützt. Zur Vereinfachung der Programmierung gibt es zwei weitere Bibliotheken, die OpenGL erweitern und zusam men mit OpenGL häufig benutzt werden: • Die OpenGL Utility Library GLU, die OpenGL um etwa 50 Befehle erweitert, insbesondere um Model lierungs-Funktionen • Das OpenGL Utility Toolkit GLUT, einen Aufsatz auf OpenGL und GLU, der insbesondere Funktio nen des Fenstersystems kapselt und für eine vereinfachte Ein- und Ausgabebehandlung sorgt. Weitere Informationen zu den hier verwendeten Graphik bibliotheken findet man in Internet unter http://ww w.opengl.org. Hier werden ständig aktuelle Informa tionen zu Entwicklung und Erweiterung von OpenGL bereitgestellt. Weiterhin können hier Dokumentationen eingesehen werden. Besonders wichtig sind hier das soge nannte „Red Book“ (OpenGL Programming Guide) und das „Blue Book“ (OpenGL Refernce Manual). Beide Titel sind auch online einsehbar. Die Adressen lauten: • • http://www.glprogramming.com/red/ http://www.glprogramming.com/blue/ Abbildung 1: OpenGL-Dokumentationen (siehe Abbildung 1). Weitere Dokumentationen findet man unter den entsprechenden Links auf der Seite, die Dokumente zu GLUT beispielsweise unter http://www.opengl.org/resources/libraries/ oder http://www.opengl.org/resources/libraries/glut/spec3/spec3.html. Die Namensgebung der (OpenGL-)Funktionen erfolgt nach einem einheitlichen Schema. Die Funktionsnamen beginnen mit einem Präfix, der die Bibliothek kennzeichnet, in der sie definiert sind. Dies sind gl für OpenGLBasisfunktionen, glu für Funktionen aus der OpenGL Utility Library GLU und glut aus dem OpenGL Utility Toolkit GLUT. Darauf folgt der Name der eigentlichen Funktion. Bei einigen Funktionen, die es in verschiede nen Ausführungen gibt, schließt sich hier eine Zahl an, die die Anzahl der zu übergebenden Parameter bezeich net. Möglich sind hier: • 2 für zweidimensionale Koordinaten (x, y) • 3 für dreidimensionale Koordinaten oder Farbangaben (x, y, z) oder (r, g, b) • 4 für vierdimensionale Koordinaten oder Farbangaben (x, y, z, w) oder (r, g, b, a) Im Falle der Angabe der Argumentanzahl folgt darauf ein Kürzel, das den Datentyp der Argumente spezifiziert. Aufgabe 1: Erklären Sie den Namen der folgenden Funktionen. Welche Parameter werden bei den ersten drei Funktionen erwartet? • GlVertex2i(...) • glVertex4f(...) • glColor3d(...) 3 Aufgabe 1 • gluPerspective(...) • glutCreateWindow(...) Konstanten werden grundsätzlich groß geschrieben und die Teile ihrer Bezeichner durch Unterstrich voneinan der getrennt. Alle Konstantenbezeichner beginnen mit GL_. OpenGL definiert eigene Datentypen, die die von den Programmiersprachen angebotenen Datentypen kapseln. Diese, mit GL beginnenden Typbezeichner, stellen die Portabilität sicher und sollten verwendet werden. 2 Einrichten des Arbeitsplatzes Die Beispiele in diesem Lehrheft werden sowohl in C(++) als auch in Java mit JOGL präsentiert. Zu Beginn müssen in beiden Fällen die Arbeitsumgebung eingerichtet werden. Im Folgenden wird davon ausgegangen, daß (a) Microsoft Visual C++ (2008) Express Edition und (b) Eclipse als Java Entwicklungsumgebung verwendet wird. Sollte eine andere Umgebung verwendet werden, sind die Ausführungen entsprechend anzupassen. 2.1 Microsoft Visual C++ 2008 Express Edition Nachdem Microsoft Visual C++ 2008 Express Edition gestartet wurde, wird ein neues Projekt angelegt. Unter Datei → Neu → Projekt... erscheint ein Dialog, in dem als Projekttyp „Allgemein“ angegeben und dann die „Leere Vorlage“ ausgewählt wird (siehe Abbildung 2). Danach wird das Projekt entsprechend benannt und ein Speicherort wird ausgewählt (standardmäßig wer den Projekte unter Eigene Dateien\Visual Studio 2008\Projekts\ gespeichert. Microsoft Visual C++ legt daraufhin eine Verzeichnisstruktur an, in die die Dateien des Projektes gespeichert werden. Sollte auf dem Computer GLUT noch nicht installiert sein, kann das nachgeholt werden. Dazu laden ist die Datei glut-3.7.6-bin.zip von http://www.x Abbildung 2: Projekteinstellungen für Microsoft Visual C++ 2008 mission.com/~nate/glut.html herunterzuladen und zu entpacken sie. Administratoren können der Anlei tung in README-win32.txt zur Installation folgen. Sonst können auch alle Dateien einfach in das angelegte Projektverzeichnis kopiert werden. Dann können sie allerdings nur in dem einen Projekt verwendet werden. Damit ist die Arbeitsumgebung unter Microsoft Visual C++ 2008 Express Edition eingerichtet. Da die in die sem Lehrheft vorgestellten Aufgaben alle mit einer Quellcodedatei auskommen, reicht es, eine neu Datei anzule gen (z.B. opengl1.cpp). Dies erfolgt mittels Datei → Neu → Datei... und im dann erscheinenden Dialog mit der Vorlage „C++-Datei (.cpp)“ unter „Visual C++“. Es wird zunächst ein Standardname vergeben ( Quel le1.cpp). Die Datei wird dann mittels Datei → Speichern unter... gespeichert und kann dann mit Datei → Ver schieben in → <Projekt> in das Projekt aufgenommen werden. 2.2 Eclipse für Java Vorausgesetzt wird hier eine funktionierende Installation von Java, Eclipse und JOGL. Mithilfe von JOGL kann ein Java-Programmierer auf OpenGL-Funktionen zugreifen. Es werden spezielle Java-Wrapperklassen bereitge stellt, die Schnittstellen zu den nativen OpenGL-Funktionen bereitstellen. Die angebotenen Methoden führen dabei in der Regel einfach korrespondierenden nativen C-Code aus 1. Die Installation unter Eclipse wird wie folgt vorgenommen. Dabei entsteht am Ende ein Eclipse-Projekt, in dem die JOGL-Klassen korrekt eingebunden sind. Die JOGL-Klassen stehen allerdings nicht systemweit für andere Projekte zur Verfügung. 1 aus Wikipedia: JOGL 4 Zur Installation wird zunächst in Eclipse ein neues leeres Java-Projekt angelegt und entsprechend benannt, z.B. Jogl-Demo. Dann werden im Projektverzeichnis zwei Verzeichnisse angelegt mit Namen lib und native. Unter https://jogl.dev.java.net/#NIGHTLY kann jetzt die aktuelle Distribution heruntergeladen wer den (hier auf die Zielplattform achten, für Windows heißt die Datei jogl-2.0-windows-i586.zip). Aus der heruntergeladenen ZIP-Datei werden alle *.jar-Dateien nach lib kopiert und alle *.dll-Datein nach native. Mit F5 den Workspace aktualisieren und dann im lib-Verzeichnis die beiden *.jar-Dateien markieren und im Kontextmenü mittels Build Path → Add to Buildpath zum Build-Pfad hinzufügen. Danach müssen die beiden Dateien im Projekt unter „Referenced Libraries“ auftauchen. Danach den Eigenschaftendialog des Pro jektes öffnen im Kontextmenü des Projekts unter „Properties“. Dort die Seite Java Build Path → Libraries su chen. Jetzt muß noch die Verbindung zu den nativen Bibliotheken hergestellt werden. Dazu gluegen-rt.jar markieren, den Eintrag mit dem „+“ erweitern, Native Library Location markieren und mit „Edit“ den Eingabe dialog für die Location öffnen. Hier wird das oben angelegte Verzeichnis native ausgewählt. Genauso wird bei jogl.jar vorgegangen. Die Angaben im Properties-Dialog müssen dann in etwa so aussehen, wie in Abbil dung 3, das Projekt wie in Abbildung 4. Abbildung 3: Java-Build-Path für das JOGL-Projekt Abbildung 4: Java-Project für JOGL Jetzt kann JOGL und die entsprechenden Funktionen in diesem Java-Projekt genutzt werden. Weitere Informa tionen zu JOGL sind unter anderem unter folgenden Links erhältlich: • https://jogl.dev.java.net/– die Hauptseite des JOGL-API-Projektes • http://www.jogl.info/index.htm – Informationen und einige Tutorials Weitere Tutorials finden sich unter http://pepijn.fab4.be/software/nehe-java-ports/ – dies sind Portierungen der bekannten NEHE-OpenGL-Tutorials (http://nehe.gamedev.net/). Die herunterladba ren *.jar-Dateien enthalten sowohl den Sourcecode als auch eine kompilierte Version (für den Sourcecode die *.jar-Dateien einfach auspacken). 3 Arbeitsweise von OpenGL OpenGL arbeitet ereignisorientiert. Eingabegeräte oder das Betriebssystem erzeugen Ereignisse, die von OpenGL (bzw. GLUT) in einer Ereigniswarteschlange gesammelt und dann abgearbeitet werden. Dazu werden sogenannte Callbacks registriert. Dies sind spezielle Funktionen, die immer dann aufgerufen werden, wenn ein spezielles Ereignis abgearbeitet werden muß. Der Programmierer kann hier die programmspezifischen Reaktio nen auf die Ereignisse festlegen und damit den Ablauf des Programms steuern. GLUT bietet Registrierungs funktionen für Callbacks für folgende Ereignisse an: • Maus- und Tastatureingaben • Neuzeichnen der Szene 5 • Veränderung von Fensterposition und -größe • Menüaktionen, usw. Weitere Callbacks für spezielle Eingabegeräte sind weniger gebräuchlich. OpenGL arbeitet nach dem Prinzip einer Zustandsmaschine. Die Ergebnisse sind abhängig von einer Reihe von Zustandsvariablen (die global definiert sind), d.h. die Operationen werden aufgrund von Zustandsvariablen inter pretiert. Beispiele für solche Zustandsvariablen sind der Rendermodus, das Beleuchtungsmodell oder Zeichenat tribute wie Farbe und Materialien. Für das Setzen und Löschen einiger boolescher Zustandsvariablen stehen die folgenden Funktionen bereit: • glEnable( attribut) – Hiermit wird ein Zustand gesetzt bzw. eingeschaltet. • glDisable( attribut) – Hiermit wird ein Zustand gelöscht bzw. ausgeschaltet. Andere „Zustandsvariablen“ werden nicht auf diese Art und Weise gesetzt sondern über normale OpenGLFunktionen und gelten dann mit dem gesetzten Wert so lange, bis ein anderer Wert gesetzt wird. Beispiele hierfür sind Materialbeschreibungen oder Farben. 4 OpenGL-Rahmenprogramm 4.1 C-Rahmenprogramm Programm 2 stellt den Rahmen für einfache OpenGL-Programme in C dar. In der main-Funktion werden die erforderlichen Initialisierungen vorgenommen und die Display-Callback registriert. Die eigentliche Callback-Funktion ist in Programm 2 noch nicht ausprogrammiert. Deshalb erscheint auch lediglich ein leeres Fenster mit dem Titel „OpenGL“ auf dem Bildschirm (siehe Abbil dung 5). Die Registrierung weiterer Callbacks erfolgt nach dem gleichen Sche ma in der main-Funktion. Die einzelnen Callbacks und ihre Funktionen wer den im weiteren Verlauf des Heftes gesondert behandelt. Zur besseren Über sicht wurden die beiden globalen Variablen screenHeight und screen Width eingeführt, die die Breite und Höhe des Ausgabefensters beinhalten. Die Funktionsaufrufe in main haben folgende Bedeutung: • glutInit( &argc, argv) initialisiert das GLUT-Toolkit. Dabei Abbildung 5: Ausgabe von Programm 2 wird unter anderem geprüft, ob das Programm auf der vorhandenen Hardware überhaupt lauffähig ist – gegebenenfalls wird das Programm mit einer Fehlermeldung abge brochen. Übergeben wird die Kommandozeile des Programms, da hier unter anderem auch GLUT-spe zifische Parameter gesetzt werden können. • glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA) setzt den initialen Darstellungs-modus für OpenGL. Parameter werden durch ODER-Verknüpfung miteinander kombiniert. • glutInitWindowSize( screenWidth, screenHeight) initialisiert die Fenstergröße auf den Wert der globalen Variablen screenWidth und screenHeight. Hier wird noch kein Fenster erstellt. • glutCreateWindow( "OpenGL") erzeugt und öffnet eine neues Fenster, wobei der Parameter des Funktionsaufrufes als Fenstername übergeben wird. • glutDisplayFunc( myDisplay) registriert die Display-Callback für das aktuelle Fenster. Nach dem Erzeugen des Fensters besitzt dieses noch keine Display-Callback, es liegt also in der Verantwortung des Programmierers, diese bereitzustellen. Die Display-Callback wird immer dann aufgerufen, wenn der Fensterinhalt neu gezeichnet werden muß. In ihr erfolgt typischerweise die Bereitstellung der graphi schen Ausgabe in Form von OpenGL-Funktionsaufrufen. • glutMainLoop() startet das eigentliche Programm und übergibt die Kontrolle an GLUT. Alle Ereig nisse werden hier verarbeitet und mit Hilfe von Callbacks hat der Programmierer die Kontrolle darüber, wie das Programm auf Ereignisse reagieren soll. 6 Dieses Hauptprogramm ist bei nahezu allen OpenGL-Programmen in seinen Grundzügen gleich. Im weiteren Verlauf werden die Registrierung weiterer Callbacks hinzukommen. Programm 1: Schematischer Ablauf der Funktion glutMainLoop() while (1) { if (Graphik wurde verändert) { call DISPLAY Callback; } if (Fenster wurde verändert) { call RESHAPE Callback; } if (Tastatur betätigt oder Maus bewegt) { call KEYBOARD/MOUSE Callback; } call IDLE Callback Funktion; } Der interne Ablauf bei der Ausführung eines nach diesem Muster aufgebauten Programms besteht darin, zu Beginn wichtige Variablen und Zustände zu initialisieren, die ent sprechenden Callbacks zu registrieren und dann in die Hauptschleife zur Ereignisverarbeitung einzusteigen. Diese wird in der GLUT-Bibliothek bereitgestellt und nimmt dem Programmierer die Details der Ereignisverwaltung ab. Pro gramm 1 zeigt schematisch den internen Ablauf in der Funktion glutMainLoop(). Das Programm befindet sich also in einer Endlosschleife, in der Ereignisse entgegenge nommen und diese verarbeitet werden. Der Programmierer muß dafür Sorge tragen, daß die Ereignisverwaltung in den Callbacks entsprechend der Aufgabe des Programms reali siert wird. Neben dem Hauptprogramm ist die Display-Callback die wichtigste Funktion eines OpenGL-Programmes. Hier wird die graphische Ausgabe erzeugt, indem OpenGLFunktionen aufgerufen und damit graphische Primitive erzeugt, Transformationen durchgeführt oder andere Aufgaben erfüllt werden. Die Display-Callback wird immer dann aufgerufen, wenn der Inhalt eines Fensters neu gezeichnet werden muß und sollte rasch abgearbeitet werden. Obiges Programm 2 führt zu keiner graphischen Ausgabe, da die Display-Callback hier keinerlei Funktionsaufrufe enthält, die eine Ausgabe erzeugen. Die beiden vorhandenen Funktionen löschen lediglich den Fensterinhalt ( glClear( GL_COLOR_BUFFER_BIT)) und for cieren die Ausgabe der Zeichenbefehle auf dem Fenster ( glutSwapBuffers()). Die Nutzung der Funktion glutSwapBuffers() ist nur dann angebracht, wenn das Programm im DobleBuffering-Modus arbeitet. An sonsten hat an dieser Stelle ein Aufruf der Funktion glFlush() eine ähnliche Wirkung. Programm 2: OpenGL-Rahmenprogramm #include "glut.h" // globale Variablen -------------------------------------------------------------------------int screenWidth = 600; // Breite des Anzeigefensters int screenHeight = 600; // Hoehe des Anzeigefensters // -------------------------------------------------------------------------------------------/* Zeichenfunktion. Hier stehen die OpenGL-Befehle, die letztendlich die Darstellung auf dem Bildschirm erzeugen */ void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); // Bildschirm (Fensterinhalt) loeschen // Hier kommen die Zeichenbefehle hin glutSwapBuffers(); // "Anzeige" des Bildes (DoubleBuffering) } int main( int argc, char** argv) { glutInit( &argc, argv); glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize( screenWidth, screenHeight); glutCreateWindow( "OpenGL"); glutDisplayFunc( myDisplay); glutMainLoop( ); exit( 0); } // // // // // // Initialisierung von GLUT Einstellen des Anzeigemodus Einstellen der Fenstergroesse Fenster erzeugen Setzen der Display-Funktion glut-Hauptschleife 4.2 Java-Rahmenprogramm Für die Programmierung in Java sieht der Quellcode wie in Java 1 aus. Die folgenden Programme benutzen Swing-Komponenten, daher ist die Klasse auch von JFrame abgeleitet. JOGL kann aber auch in Verbindung 7 mit AWT genutzt werden. Die Import-Deklarationen wurden aus Platzgründen weggelassen, sie ergeben sich aber durch die Verwendung der Klassen und Typen in den Programmen und werden außerdem von Eclipse selbsttätig eingefügt. Im Gegensatz zum nativen OpenGL verwendet JOGL die Java-Datentypen. Dementspre chend sind diese auch in den Programmen zu verwenden. Die in Java 1 angegebene Klasse ist vorerst bei allen JOGL-Programmen gleich. Java 1: Rahmenprogramm für eine JOGL-Anwendung (ohne Viewer-Klasse) public class JOGLMain extends JFrame { final static int screenWidth = 600; final static int screenHeight = 600; public JOGLMain() { GLCapabilities glcaps = new GLCapabilities(); glcaps.setDoubleBuffered( true); GLCanvas canvas = new GLCanvas( glcaps); JOGL0001 view = new JOGL0001(); canvas.addGLEventListener( view); } setSize( screenWidth, screenHeight); setFocusable( true); setTitle( "JOGL – Beispielszene"); getContentPane().add( canvas, BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // enthält Plattformparameter // DoubleBuffering einschalten // Viwer-Instanz erzeugen // und dem Canvas hinzufügen // // // // Fenstergröße setzen Fenster fokussierbar machen Fenstertitel setzen GLCanvas einfügen public static void main(String[] args) { final JOGLMain app = new JOGLMain(); SwingUtilities.invokeLater( new Runnable() { public void run() { app.setVisible(true); } }); } } Die eigentliche Darstellung erfolgt in der Klasse JOGL0001, die das Interface GLEventListener implemen tieren muß2. Im Hauptprogramm (Klasse GLMain) wird zunächst DoubleBuffering eingeschaltet und ein neuer GLCanvas erzeugt. Die Instanz der „Viewer“-Klasse wird erzeugt und zum Canvas hinzugefügt. Danach erfol gen noch einige Initialisierungen für Swing und Funktionen zum Erzeugen des Fensters. Die eigentliche OpenGL-Funktionalität ist in der Klasse JOGL0001 implementiert (siehe Java 2). Das Interface GLEventLis tener stellt einige der Funktionalitäten zur Verfügung, die mit den Callbacks in C realisiert werden können. Dies betrifft die OpenGL-Funktionen, Maus und Tastatureingaben werden über Java-Events abgefangen (dazu später mehr). Dementsprechend sind einige (abstrakte) Methoden zu überschreiben. Sollen die Funktionen nicht genutzt werden, können sie mit leerem Funktionskörper realisiert werden. Für das erste Beispiel ist nur die Methode display() notwendig. Sie entspricht der Display-Callback. Zunächst muß eine Instanz der Klasse GLDrawable bereitstehen. Diese wird bei der Initialisierung erzeugt. GLDrawable bietet Zugang zu GL- und GLU-Objekten, um die OpenGL Routinen aufzurufen. Alle OpenGL-Funktionen stehen in dieser Klasse über ein GL-Objekt zur Verfügung, daher muß der Aufruf mit der Instanzvariablen qua lifiziert werden, also gl.glFlush() anstelle von glFlush(). Das Beispiel ist äquivalent zum entsprechenden C-Programm und führt dementsprechend auch zur gleichen Ausgabe. Zunächst wird der Fensterinhalt gelöscht (glClear()) und dann mit glFlush() der Fensterinhalt angezeigt. Hier können keine GLUT-Funktionen verwendet werden, da kein Zugriff auf ein GLUT-Objekt besteht. Die in der Klasse noch vorhandenen Metho den displayChanged(...), init(...) und reshape(...) werden später besprochen. 2 Die Klassen werden ähnlich den C-Programmen mit Nummern benannt, so daß sie einfach ausgetauscht werden können. 8 Java 2: Viewer-Klasse für das äquivalente Beispiel zu Programm 2 public class JOGL0001 implements GLEventListener { @Override public void display( GLAutoDrawable drawable) { GL gl = drawable.getGL(); // GL-Objekt holen gl.glClear( GL.GL_COLOR_BUFFER_BIT); // Bildschirm (Fensterinhalt) loeschen // Hier kommen die Zeichenbefehle hin gl.glFlush(); // "Anzeige" des Bildes } @Override public void displayChanged( GLAutoDrawable drawable, boolean modeChg, boolean deviceChg){} @Override public void init(GLAutoDrawable drawable) {} @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {} } Aufgabe 2: Informieren Sie sich in der Dokumentation über die Bedeutung der Angaben und ergänzen Sie die folgende Aufzählung um diese Bedeutungen: – GLUT_RGBA – GLUT_RGB – GLUT_SINGLE – GLUT_DOUBLE – GLUT_DEPTH – GLUT_STEREO Aufgabe 3: Informieren Sie sich, was DoubleBuffering bedeutet und was ein Aufruf von glutSwapBuf fers() in diesem Zusammenhang bewirkt. Was ist der Unterschied zu glFlush()? 5 Einfache 3D-Ausgabe Eine einfache Möglichkeit, 3D-Objekte zu erzeugen und auszugeben, ist durch GLUT gegeben, da hier einige Grundkörper bereits vordefiniert sind. Diese können durch einfache Funktionsaufrufe eingebunden werden. Programm 3 zeigt als Beispiel die erweiterte Display-Callback aus Programm 2. Der Aufruf von glutWireS phere( 0.5, 10, 15) erzeugt eine Kugel mit dem Radius von 0.5 Einheiten. Die Kugel setzt sich aus 10 „Segmenten“ zusammen, die wiederum jeweils in 15 Abschnitte unterteilt sind. Der Mittelpunkt der Kugel liegt 9 Aufgabe 2 Aufgabe 3 im Koordinatenursprung, die Achse, um die die Segmenteinteilung erfolgt, stimmt mit der z-Achse überein. Programm 3: Kugel als GLUT-Funktion void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); glutWireSphere( 0.5, 10, 15); glutSwapBuffers(); } // Bildschirm (Fensterinhalt) loeschen // "Zeichnen" der Kugel // "Anzeige" des Bildes (DoubleBuffering) Um die gleiche Funktionalität in Java zu realisieren, benötigen wir Zugriff auf die GLUT-Funktionen. Dazu steht in JOGL die Klasse GLUT zur Verfügung, von der eine Instanz in der display()-Methode benötigt wird. Der Einfach heit halber stellen wir diese Instanz als Variable in der Viewer-Klasse zur Ver fügung. Im Gegensatz zu OpenGL für C implementiert JOGL nicht alle Funktionen der GLUT, da die Fensterbehandlung z.B. direkt von Java über nommen wird. In Java 3 ist die zu Programm 3 äquivalente Java-Klasse ange geben. Zu beachten ist, daß in der Hauptklasse (JOGLMain) die Klasse für den Viewer (jetzt: JOGL0002) ausgetauscht werden muß. Das Programm er zeugt wie oben eine Kugel mit einem Radius von 0.5 Einheiten im Koordina tenursprung. Zu beachten ist auch hier der Aufruf der GLUT-Routinen in der Instanz der Klasse GLUT, also z.B. mittels glut.glutWireSphere(...). Abbildung 6: Ausgabe von Programm 3 Java 3: Kugel als GLUT-Funktion public class JOGL0002 implements GLEventListener { final GLUT glut = new GLUT(); // GLUT-Instanz global für diese Klasse @Override public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); } // GL-Objekt holen gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); glut.glutWireSphere(0.5, 10, 15); gl.glFlush(); // // // // Hintergrundfarbe schwarz Bildschirm (Fensterinhalt) loeschen "Zeichnen" der Kugel "Anzeige" des Bildes @Override public void displayChanged( GLAutoDrawable drawable, boolean modeChg, boolean deviceChg){} @Override public void init(GLAutoDrawable drawable) {} @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {} } Aufgabe 4 Aufgabe 4: Ändern Sie Programm 2 so ab, daß die Display-Callback wie in Programm 3 angegeben aussieht. Führen Sie das Programm aus und vergleichen Sie die Ausgabe mit Abbildung 6. Ausgehend davon, erläutern Sie, wie das „Standard-Koordinatensystem“ bei dreidimensionaler Ausgabe ohne weitere Änderungen der An sicht aussieht: • Position des Koordinatenursprungs: • Verlauf der y-Achse: • Verlauf der x-Achse: • Verlauf der z-Achse: • Koordinaten in x-Richtung gehen von bis • Koordinaten in y-Richtung gehen von bis Informieren Sie Sich in der GLUT-Dokumentation über die Funktionen zum Erstellen weiterer Grundkörper 10 und deren Parameter. Ergänzen Sie folgende Übersicht: • • • • • • • • Würfel – Funktionsaufruf: – Lage zum Koordinatenursprung: – Parameter: Kegel – Funktionsaufruf: – Lage zum Koordinatenursprung: – Parameter: Torus – Funktionsaufruf: – Lage zum Koordinatenursprung: – Parameter: Dodekaeder – Funktionsaufruf: – Lage zum Koordinatenursprung: – Größe: Oktaeder – Funktionsaufruf: – Lage zum Koordinatenursprung: – Größe: Tetraeder – Funktionsaufruf: – Lage zum Koordinatenursprung: – Größe: Ikosaeder – Funktionsaufruf: – Lage zum Koordinatenursprung: – Größe: Teekanne 11 – Funktionsaufruf: – Lage zum Koordinatenursprung: – Parameter: Die verwendeten GLUT-Funktionen sind in zwei Versionen verfügbar: glutWire...(...) erzeugt ein Draht gittermodell der Körper, glutSolid...(...) eine Variante mit definierten Seitenflächen. Die Solid-Versio nen erzeugen momentan Silhouetten als graphische Ausgabe, da noch keine Shading-Funktionalität (Lichtquellen und Beleuchtung) implementiert ist. Aufgabe 5 Aufgabe 5: Experimentieren Sie mit verschiedenen GLUT-Funktionen für geometrische Körper. 6 Transformationen Damit die Szene wie gewünscht auf dem Bildschirm erscheint, müssen die in der Viewing-Pipeline (Abbildung 7) enthaltenen Transformationen durchlaufen werden. Diese sind durch Standard-Werte voreingestellt, die aller dings nur eine sehr eingeschränkte Sicht auf die Objekte der Szene erlauben. Transformationen sind ebenfalls notwendig, um mehrere Objekte in einer Szene zu platzieren und gegeneinander auszurichten. Schließlich wer den die internen Kameraparameter ebenfalls durch Transformationen modelliert und repräsentiert. Bei OpenGL werden Transformationen intern in zwei Transformationsmatrizen gespeichert, mit denen jeder Koordinatenwert vor der Darstellung multipliziert wird. Diese beiden Matrizen, die ModelView-Matrix und die Projection-Matrix können vom Programmierer durch spezielle Funktionen manipuliert werden. Um festzulegen, welche Matrix von den Funktionsaufrufen betroffen ist, wird in OpenGL der MatrixMode verwendet. Mit Hilfe der Funktion glMatrixMode(...) wird festgelegt, auf welche der beiden Transformationsmatrizen sich die folgenden Funktionen zur Manipulation von Transformationsmatrizen auswirken. Dabei sind folgende Parameter möglich: • GL_MODELVIEW stellt die Modellierungstransformation ein. Alle folgenden Funktionen betreffen die ModelView-Matrix. • GL_PROJECTION stellt die Projektionstransformation ein. Alle folgenden Funktionen betreffen die Pro jection-Matrix. MODELVIEW-Matrix PROJECTION-Matrix ModellierungsTransformation SichtTransformation ProjektionsTransformation BildschirmTransformation glTranslate glScale glRotate gluLookAt glFrustum glOrtho gluPerspective glViewport Abbildung 7: Viewing-Pipeline und Transformationen in OpenGL Die Funktion glLoadIdentity() lädt die Einheitsmatrix in die jeweilige Transformationsmatrix und ist daher als Initialisierung nützlich. Jeder Eckpunkt v des darzustellenden Modells wird also zunächst mit der aktuell ein gestellten ModelView-Matrix multipliziert und danach mit der aktuell eingestellten Projection-Matrix: v' = MP·MMV·v. Jede Funktion zur Matrixmanipulation (siehe Abbildung 7, grüne Rechtecke) wirkt sich dabei je nach MatrixMode auf die eingestellte aktuelle Matrix aus und multipliziert diese mit einer entsprechenden Transfor mationsmatrix, so daß eine zusammengesetzte Transformation entsteht. 6.1 Sichttransformation Die Funktion gluLookAt(...) dient dazu, die Kamera an eine bestimmte Position zu setzen und sie auszu richten. Die voreingestellte Standard-Position der Kamera befindet sich im Koordinatenursprung, die Blickrich tung entspricht der negativen z-Achse und die Kamera ist so ausgerichtet, daß die y-Achse nach oben weist. 12 Ein Aufruf der Funktion gluLookAt( posx, posy, posz, atx, aty, atz, upx, upy, upz) platziert die Kamera an die Position (posx, posy, posz). Die Kamera schaut auf den Punkt (atx, aty, atz) und der Vektor (upx, upy, upz) gibt eine Richtung an, die aus der Sicht der Kamera nach oben weist (siehe Abbildung 8). Programm 4 zeigt die Verwendung der Funktion gluLookAt(...). Um das Programm modular zu halten, wird die Kamera in einer separaten Funktion myInit() gesetzt, die an der entsprechenden Stelle in der main-Funktion aufgerufen wird. Damit die Änderungen der Ka mera besser sichtbar sind, wurde eine Teekanne als Modell in den Koordina Abbildung 8: Parameter von gluLookAt tenursprung gesetzt. Die Kamera wird an die Position (0.4, 0.4, 0.4) gesetzt und schaut auf den Koordinatenursprung (0, 0, 0). Die y-Achse wird als Up-Vektor angegeben. Es muß noch be achtet werden, daß nur Objekte, die sich in einer Entfernung unter einer Einheit von der Kamera befinden, auch sichtbar sind. Dies läßt sich später mit den internen Kameraparametern ändern. Momentan wird dies durch ent sprechende Größen der Objekte und eine entsprechende Postion der Kamera sichergestellt. In der Funktion my Init() wird zunächst die ModelView-Matrix als die zu verändernde Matrix festgelegt. Dann wird diese durch die Einheitsmatrix initialisiert und anschließend die Kamera positioniert. Programm 4: Anwendung der Funktion gluLookAt(...) #include <GL/glut.h> // globale Variablen -------------------------------------------------------------------------int screenWidth = 600; // Breite des Anzeigefensters int screenHeight = 600; // Hoehe des Anzeigefensters void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); glutWireTeapot( 0.3); glutSwapBuffers(); } // Bildschirm (Fensterinhalt) loeschen // "Zeichnen" der Teekanne // "Anzeige" des Bildes (DoubleBuffering) void myInit( void) { glMatrixMode( GL_MODELVIEW); // Einstellen der ModelView-Matrix glLoadIdentity(); // Initialisieren mit der Einheitsmatrix gluLookAt( 0.4, 0.4, 0.4, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Kamera positionieren } int main( int argc, char** argv) { glutInit( &argc, argv); glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize( screenWidth, screenHeight); glutCreateWindow( "OpenGL"); glutDisplayFunc( myDisplay); myInit(); glutMainLoop( ); exit( 0); } // // // // // // // Initialisierung von GLUT Einstellen des Anzeigemodus Einstellen der Fenstergroesse Fenster erzeugen Setzen der Display-Funktion Aufrufen der Initialisierungsfunktion glut-Hauptschleife Um in Java das gleiche Ergebnis zu erhalten, wird genauso vorgegangen. Die neue ViewerKlasse ( JOGL0003) ist in Java 4 zu sehen. Hier wird die Methode init() verwendet und genauso wie die GLUT-Instanz zuvor eine Instanz von GLU erzeugt. Darin stehen alle GLU-Funktionen zur Verfügung. Wie bereits in den vorherigen Pro grammen werden auch hier Konstanten genutzt, die in den Klassen GL, GLU und GLUT zur Verfügung gestellt werden. Ihr Zugriff erfolgt auf der Klassenebene, also zum Beispiel GL.GL_COLOR_BUFFER_BIT oder GL.GL_MODELVIEW. Die init-Methode wird beim ersten Initialisieren des OpenGL Kontextes aufgerufen. In ihr werden allgemeine Parameter zum Rendern der Szene eingestellt, die sich nicht verändern sollen. Dies können z. B. Voreinstellungen bezüglich der Lichter oder der Display Lists sein [http://www.jogl.info/tutorial/lektion1.htm]. Von daher ist diese Methode eigentlich nicht der richtige Platz zum Einstellen von Kameraparametern, da es sich hier aber um die initialen Kameraeinstellungen handelt, können diese hier gesetzt werden. Sonst ist die Methode display() oder reshape() der bessere Ort. 13 Java 4: Kameraeinstellungen mit gluLookAt() public class JOGL0003 implements GLEventListener { final GLUT glut = new GLUT(); final GLU glu = new GLU(); // GLU-Instanz holen @Override public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); glut.glutWireTeapot( 0.3); gl.glFlush(); } @Override public void displayChanged( GLAutoDrawable drawable, boolean modeChg, boolean deviceChg) {} @Override public void init(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glMatrixMode( GL.GL_MODELVIEW); // Einstellen der ModelView-Matrix gl.glLoadIdentity(); // Initialisieren mit der Einheitsmatrix glu.gluLookAt( 0.4, 0.4, 0.4, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Kamera positionieren } @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {} } Aufgabe 6 Aufgabe 6: Experimentieren Sie mit verschiedenen Kamerapositionen, d.h. ändern Sie die Parameter der Funkti on gluLookAt(...) und beobachten Sie die Effekte auf die Ausgabe. Beachten Sie dabei, daß sich die Objek te maximal eine Einheit von der Kamera entfernt befinden dürfen, sonst werden sie teilweise oder ganz „abge schnitten“. 6.2 Modellierungstransformation(en) Um Objekte an sich zu bewegen, bietet OpenGL die Standard-Transformationen Translation, Rotation und Ska lierung an. Bei einem Aufruf einer der entsprechenden Funktionen wird die aktuelle Transformationsmatrix (in den meisten Fällen wird dies die ModelView-Matrix sein) mit einer der zu konstruierenden Transformation ent sprechenden Matrix multipliziert und diese zusammengesetzte Matrix wird als neue aktuelle Transformationsma trix verwendet, sei also T die auszuführende Transformation (Skalierung, Rotation, Translation, ...) und MMV die aktuelle ModelView-Matrix, dann ist nach dem Ausführen der Transformation M'MV=MMV·T. Das bedeutet auch, daß die aktuelle Transformationsmatrix solange bestehen bleibt, bis sie erneut verändert wird. Die einzelnen OpenGL-Funktionen für Transformationen existieren in verschiedenen Varianten, die sich durch den Datentyp der Parameter und daher im letzten Buchstaben des Namens unterscheiden ( d für double und f für float): • glTranslate*( x, y, z) multipliziert die aktuelle Transformationsmatrix mit einer Matrix, die eine Translation um den Vektor (x, y, z) beschreibt. • glScale*( x, y, z) multipliziert die aktuelle Transformationsmatrix mit einer Matrix, die eine Skalierung um die Faktoren x, y und z beschreibt. • glRotate*( angle, x, y, z) multipliziert die aktuelle Transformationsmatrix mit einer Matrix, die eine Rotation um die durch den Vektor (x, y, z) beschriebene Achse um den Winkel angle beschreibt. Der Winkel wird in Grad angegeben und die Rotation erfolgt in einem rechtshändigen Koordinatensys tem, das bedeutet, wenn die Achse zum Betrachter zeigt, erfolgt die Rotation egegen den Uhrzeigersinn. Aufgrund der internen Ausführung der Matrixmultiplikation wird die zuletzt spezifizierte Transformation zuerst ausgeführt. Dies ist bei der Programmierung zu beachten, da die Matrixmultiplikation bekanntlich nicht kommu tativ ist. Als Beispiel soll folgender Codeausschnitt dienen: glScalef( 2.0, 2.0, 2.0); glRotatef( 45.0, 0.0, 1.0, 0.0) 14 glTranslatef( 5.0, 1.0, -3.0); glutWireCube( 0.5); Der Würfel wird hier zuerst um den Vektor (5.0, 1.0, -3.0) verschoben, dann um die y-Achse rotiert und dann mit dem Faktor 2 uniform skaliert. Die Transformationsmatrix setzt sich dann wie folgt zusammen: M'MV = MMV·MSkalierung·MRotation·MTranslation. Dies bedeutet, jeder Eckpunkt P des Würfels wird auf die folgende Weise transformiert: P' = MMV·MSkalierung·MRotation·MTranslation·P. Aufgabe 7: Schreiben Sie ausgehend von Programm 4 ein OpenGL-Programm, das die Ausgabe in Abbildung 9 erzeugt. Dazu setzen Sie zunächst die Kamera auf die Position (0, 0, 1), ihre Blickrichtung auf den Ursprung und die y-Achse als UpVektor. Zeichnen Sie dann eine Kugel mit dem Radius 0.1 im Ursprung. Folgende Objekte sollen dann nacheinander gezeich net werden: • ein Würfel mit der Kantenlänge 0.1 an der Position (0.25, 0.25, 0.0) • ein weitere Würfel mit der Kantenlänge 0.1 an der Posi tion (-0.25, 0.25, 0) • eine Teekanne der Größe 0.1 an der Position (-0.25, -0.25, 0) sowie • ein Torus mit den beiden Radien 0.05 und 0.1 an der Po sition (0.25, -0.25, 0) Aufgabe 7 Abbildung 9: Zu erzeugende Ausgabe – Transformationen Beachten Sie dabei, daß die aktuelle Transformationsmatrix wie eine Statusvariable funktioniert und fortgesetzte Transformationen die vorhergehenden Transformationen nicht wieder rückgängig machen. Das Beispiel in obiger Aufgabe läßt sich auf verschiedene Weise lösen. Einerseits können die Transformationen relativ zur Position der vorherigen Objekte angegeben werden. Das bedeutet, daß der zweite Würfel um den Vektor (-0.5, 0, 0) verschoben werden muß usw. Eine zweite Möglichkeit besteht darin, die entsprechenden Transformationen nach dem Zeichnen des Objektes wieder rückgängig zu machen und dann die neue Transfor mation relativ zum Koordinatenursprung anzugeben. Dies ist insbesondere bei komplexen Transformationen aufwendig und fehleranfällig, da die inverse Transformation immer explizit angegeben werden muß. OpenGL bietet hierfür Matrixstacks an, um den aktuellen Status der Transformationsmatrix zu sichern und später wieder herzustellen. • Die Funktion glPushMatrix() speichert den Zustand der aktuellen Transformationsmatrix auf dem Stack und verändert dabei die aktuelle Transformationsmatrix nicht • Die Funktion glPopMatrix() setzt die aktuelle Transformationsmatrix auf die Matrix, die an oberster Stelle auf dem Stack liegt und entfernt diese Matrix vom Stack. Programm 5 zeigt als Beispiel für den Einsatz der beiden Funktionen eine Variante der Lösung obiger Aufgabe. Die Koordinaten können nun direkt angegeben werden, ohne explizit eine inverse Transformation zu definieren und ohne die Transformationen relativ anzugeben. Die Verwendung von glPushMatrix() und glPopMa trix() ist überall angebracht, wo geometrische Modelle oder Animationen hierarchisch definiert werden. Wenn z. B. ein Auto modelliert werden soll, dann kann eine Radmutter und eine Felge in einem eigenen Koordinaten system modelliert werden. Die Modellierung des Autos erfolgt dann auf folgende Weise: Man modelliert das Chassis im Koordinatenursprung, bewegt sich zur Position des linken Vorderrades, modelliert die Felge, bewegt sich zur Position der ersten Radmutter, modelliert die Radmutter, bewegt sich zurück zur Position des Rades, be wegt sich zur Position der zweiten Radmutter, modelliert die Radmutter, bewegt sich zurück zur Position des Ra des, ..., bewegt sich zurück zum Ursprung, bewegt sich zur Position des rechten Vorderrades, ... Mit der Verwen dung von glPushMatrix() und glPopMatrix() kann diese Art der Modellierung sehr einfach durchgeführt werden. Die Anwendung der beiden Funktionen bei Animationen wird später noch beschrieben. 15 Programm 5: Beispiel für glPushMatrix() und glPopMatrix() – nur die Display-Callback void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); glutWireSphere( 0.1, 10, 10); glPushMatrix(); glTranslatef( 0.25, 0.25, 0.0); glutWireCube( 0.1); glPopMatrix(); glPushMatrix(); glTranslatef( -0.25, 0.25, 0.0); glutWireCube( 0.1); glPopMatrix(); glPushMatrix(); glTranslatef( -0.25, -0.25, 0.0); glutWireTeapot( 0.1); glPopMatrix(); glPushMatrix(); glTranslatef( 0.25, -0.25, 0.0); glutWireTorus( 0.05, 0.1, 20, 20); glPopMatrix(); glutSwapBuffers(); } // Bildschirm (Fensterinhalt) loeschen // "Anzeige" des Bildes (DoubleBuffering) Die JOGL-Lösung ist äquivalent. Die entsprechende display()-Methode ist in Java 5 amgegeben. Java 5: Beispiel für glPushMatrix() und glPopMatrix() – nur die display()-Methode public void display( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); glut.glutWireSphere( 0.1f, 10, 10); gl.glPushMatrix(); gl.glTranslatef( 0.25f, 0.25f, 0.0f); glut.glutWireCube( 0.1f); gl.glPopMatrix(); gl.glPushMatrix(); gl.glTranslatef( -0.25f, 0.25f, 0.0f); glut.glutWireCube( 0.1f); gl.glPopMatrix(); gl.glPushMatrix(); gl.glTranslatef( -0.25f, -0.25f, 0.0f); glut.glutWireTeapot( 0.1f); gl.glPopMatrix(); gl.glPushMatrix(); gl.glTranslatef( 0.25f, -0.25f, 0.0f); glut.glutWireTorus( 0.05f, 0.1f, 20, 20); gl.glPopMatrix(); } Aufgabe 8 gl.glFlush(); Aufgabe 8: Erstellen Sie ein Programm, in dem Sie einen Tisch (Platte und Beine jeweils transformierte glut SolidCube()) modellieren, indem die Beine relativ zur Tischplatte positioniert werden und zum Abschluß der gesamte Tisch mit einer Ecke in den Ursprung verschoben wird. Beachten Sie dabei die korrekte Verwendung von glPushMatrix() und glPopMatrix() sowie die Reihenfolge des auszuführenden Transformationen. 6.3 Projektionstransformation Die weiteren Transformationen bestimmen die internen Parameter der „Kamera“ und werden auch in OpenGL getrennt von der Modellierungs- und Sichttransformation behandelt. Die Projektions- und die Bildschirmtrans formation werden typischerweise durch die Projection-Matrix repräsentiert. Dies ist ebenfalls eine 4×4 Matrix, mit der die Koordinatenwerte der Eckpunkte multipliziert werden. Die Manipulation dieser Matrix über entspre chende Funktionen wird mittels glMatrixMode(GL_PROJECTION) eingeschaltet. Danach wirken alle Funk tionen zur Manipulation einer Transformationsmatrix (also auch glTranslate*(...), glScale*(...), glRotate*(...) usw.) auf die Projection-Matrix. Bei der Programmierung ist darauf zu achten, welche der 16 beiden Matrizen gerade manipuliert wird. Der Matrix-Mode bleibt jeweils bis zum nächsten Aufruf der Funktion glMatrixMode(...) bestehen. Zum Einstellen der Kameraparameter bietet OpenGL spezielle Funktionen an: • glOrtho( left, right, bottom, top, near, far) erzeugt einen Sichtkörper für eine Parallelprojekti on. Dabei liegt die linke Begrenzungsebene des Sicht körpers left Einheiten von der Sichtachse entfernt, die rechte Begrenzungsebene right, die obere top und die un tere bottom Einheiten. Die near clipping plane is near Ein heiten entlang der Sichtachse entfernt und die far clip ping plane far Einheiten. Der Sichtkörper hat die Form eines Quaders mit jeweils paarweise parallelen Seitenflä chen. • glFrustum( left, right, bottom, top, near, far) erzeugt einen Sichtkörper für eine per spektivische Projektion. Dabei liegt die linke Begren zungsebene des Sichtkörpers left Einheiten von der Sichtachse entfernt, die rechte Begrenzungsebene right, die obere top und die untere bottom Einheiten. Die near clipping plane is near Einheiten entlang der Sichtachse entfernt und die far clipping plane far Einheiten. • Abbildung 10: Parameter von glOrtho Abbildung 11: Parameter von glFrustum gluPerspective( fovy, aspect, near, far) spezifiziert ebenfalls einen Sichtkörper für eine perspek tivische Projektion. Dabei gibt fovy den vertikalen Öff nungswinkel der Kamera in Grad an und aspect das Sei tenverhältnis. Eine Angabe von 2.0 als aspect erzeugt bei spielsweise einen Sichtkörper der doppelt so breit wie hoch ist. Die near clipping plane is near Einheiten ent lang der Sichtachse entfernt und die far clipping plane far Einheiten. Abbildung 12: Parameter von gluPerspective Ebenso wie die oben behandelten Funktionen zur Modell- und Sichttransformation wird intern die aktuelle Pro jection-Matrix mit den entsprechenden Matrizen multipliziert. Da aber typischerweise die Kameraparameter nur einmal eingestellt werden, spielt die Hintereinanderausführung der Funktionen zur Projektionstransformation weniger eine Rolle. Aufgabe 9: Wie müßten folgende Sichtkörper definiert werden? Geben Sie jeweils den Funktionsaufruf der ent sprechenden OpenGL-Funktion und die zu übergebenden Parameter an. • Sichtkörper einer Parallelprojektion mit einem 7 Einheiten breiten und 5 Einheiten hohen Ausgabebe reich. Die Sichtachse der Kamera geht mittig durch diesen Bereich und alle Koordinaten, die zwischen 10 und 20 Einheiten von der Kamera entfernt sind, sollen dargestellt werden. ................................................................................................................................................................................... • Sichtkörper einer perspektivischen Projektion mit einem 8 Einheiten breiten Ausgabebereich. Der Aus gabebereich soll ein Viertel so hoch sein wie breit und alle Koordinaten, die zwischen 1 und 100 Einhei ten von der Kamera entfernt sind, sollen dargestellt werden. .................................................................................................................................................................................... • Sichtkörper einer perspektivischen Projektion mit einem Ausgabebereich, der sich horizontal von -4 bis 10 erstreckt, vertikal halb so hoch ist und bei dem die Sichtachse vertikal mittig verläuft. Nur der kleine Bereich von Koordinaten zwischen 12 und 13 Einheiten entfernt vom Kamerastandpunkt soll darge stellt werden. ................................................................................................................................................................................... 17 Aufgabe 9 6.4 Bildschirmtransformation Nach der Anwendung der aktuellen Projection-Matrix liegen intern Koordinaten im Bereich zwischen 0 und 1 vor. Die Funktion glViewport(...) bildet diese auf eine Bereich von Pixelkoordinaten auf dem Bildschirm ab. Der Punkt (-1, -1) wird dabei auf (xmin, ymin) abgebildet, der Punkt (1,1) auf (xmin+width, ymin+height). Dabei kann es unter Umständen zu einer Verzerrung kommen, wenn das Ausgabefenster bzw. der Ausgabebereich ein anderes Seitenverhältnis wie das Seitenverhältnis der Kamera hat. 6.5 Zusammenfassung Transformationen Die Transformationen in OpenGL, also Modellierungs-, Sicht-, Projektions- und Bildschirmtransformation sind eng miteinander verbunden und bilden eine Kette, durch die alle Eckpunkte des Modells transformiert werden. Um ein korrektes Bild zu erzeugen, sollte man sich das Zusammenspiel der Matrizen deutlich machen und ent sprechend ausnutzen. Wird die synthetische Kamera (also die Projektions- und die Sichttransformation) im Ver lauf der Anwendung nicht verändert, bietet sich die Reshape-Callback als Platz zur Initialisierung der Matrizen an. Diese wird immer dann aufgerufen, wenn sich die Form, Größe oder Position des Ausgabefensters ändert. In dieser Callback sind auch Informationen über die Größe des Ausgabefensters verfügbar, so daß eine Anpassung des Kameramodells an die Größe und das Seitenverhältnis des Ausgabefensters erfolgen kann. In Programm 6 ist ein Beispiel für eine solche Reshape-Callback gegeben. Da die Kameraparameter jetzt in myReshape(...) geändert werden, müssen die Aufrufe der entsprechenden Funktion in der bisherigen Initialisierungsfunktion myInit() natürlich entfallen. In myReshape(...) wird zunächst die Projection-Matrix eingestellt und mit der Einheitsmatrix initialisiert. Danach erfolgt mit der Funktion glFrustum(...) die Konstruktion eines Sichtkörpers für eine perspektivische Projektion. Im Beispiel sind die linke und rechte Begrenzungsebene des Sichtkörpers jeweils eine Einheit vom Koordinatenursprung entfernt, ebenso die obere und untere Begrenzungs ebene. Die near clipping plane liegt 1 Einheit vom Kamerastandpunkt entlang der Sichtachse gesehen entfernt, die far clipping plane 15 Einheiten. Da die Kamera bisher noch im Ursprung steht und keine weiteren Anpas sungen vorgenommen wurden, wird der Punkt (-1, -1, -1) auf die linke untere Ecke des Ausgabefensters und der Punkt (1, 1, -1) auf die rechte obere Ecke des Ausgabefensters abgebildet. Die Kamera schaut standardmäßig in Richtung der negativen z-Achse und da die near clipping plane eine Einheit vom Kamerastandpunkt entfernt ist, sind alle Punkte mit einer z-Koordinate von -1 die „vordersten“ Punkte, die in der Ausgabe dargestellt werden. Alle Punkte mit einer z-Koordinate von -15 wären dann die am weitesten entfernten Punkte, die noch dargestellt werden würden (Entfernung der far clipping plane beträgt 15 Einheiten). Nach dem Einstellen des Sichtkörpers kann die Kamera platziert werden. Dazu wird auf die ModelView-Matrix gewechselt und diese mit der Einheitsmatrix initialisiert. Die Kamera wird dann an die Position (0, 0, 1) und ihre Blickrichtung auf den Ursprung gelegt. Dabei wird der gesamte oben definierte Sichtkörper in seiner Position und Ausrichtung nicht aber in seiner Größe verändert. Die near clipping plane liegt jetzt also eine Einheit vom Punkt (0, 0, 1) entfernt senkrecht zum Vektor (0, 0, -1), also in der yx-Ebene. Diese Vorgehensweise, zunächst die Kamera, also den Sichtkörper, in Standardposition zu definieren und dann mittels gluLookAt(...) zu po sitionieren ist relativ eingängig und empfehlenswert. Schließlich wird in myReshape(...) der Ausgabebereich auf dem Bildschirmfenster festgelegt Dies erfolgt über glViewport(...). Im Beispiel wird der linke untere Punkt der Ausgabe auf den Fensterkoordinaten (0, 0) und der rechte obere Punkt auf den Fensterkoordinaten (w, h), also in der rechten oberen Fensterecke abgebil det. Das hat bei Größenänderungen des Fensters zur Folge, daß sich die Ausgabe ebenfalls in ihrer Größe – und eventuell auch im Seitenverhältnis – ändert. Aufgabe 10 Aufgabe 10: Wie müßten die Parameter der Kamera in Programm 6 aussehen, wenn die Ausgabe im Fenster doppelt so groß erscheinen soll? Begründen Sie Ihre Antwort. Aufgabe 11 Aufgabe 11: Experimentieren Sie mit verschiedenen Kameras und Sichtkörpern. Beobachten Sie dabei insbeson dere die Effekte, die durch die Clipping planes entstehen. 18 Programm 6: Initialisierung der synthetischen Kamera in der Reshape-Callback #include <GL/glut.h> // globale Variablen -------------------------------------------------------------------------int screenWidth = 600; // Breite des Anzeigefensters int screenHeight = 600; // Hoehe des Anzeigefensters void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); glutWireSphere( 0.1, 10, 10); glTranslatef( 0.25, 0.25, 0.0); glutWireCube( 0.1); glTranslatef( -0.5, 0.0, 0.0); glutWireCube( 0.1); glTranslatef( 0.0, -0.5, 0.0); glutWireTeapot( 0.1); glTranslatef( 0.5, 0.0, 0.0); glutWireTorus( 0.05, 0.1, 20, 20); glutSwapBuffers(); } void myReshape( GLsizei w, GLsizei h) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum( -1, 1, -1, 1, 1.0, 15.0); // Bildschirm (Fensterinhalt) loeschen // "Anzeige" des Bildes (DoubleBuffering) // Projection-Matrix einstellen // Initialisieren mit der Einheitsmatrix // Sichtkörper der perspekt. Projektion glMatrixMode(GL_MODELVIEW); // ModelView-Matrix einstellen glLoadIdentity(); // Initialisieren mit der Einheitsmatrix gluLookAt( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Kameraposition ... glViewport(0, 0, w, h); } // Bildschirmtransformation = Viewport int main( int argc, char** argv) { glutInit( &argc, argv); glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize( screenWidth, screenHeight); glutCreateWindow( "OpenGL"); glutDisplayFunc( myDisplay); glutReshapeFunc( myReshape); glutMainLoop( ); exit( 0); } // // // // // // // Initialisierung von GLUT Einstellen des Anzeigemodus Einstellen der Fenstergroesse Fenster erzeugen Setzen der Display-Funktion Setzen der Reshape-Callback glut-Hauptschleife Das gleiche Beispiel in Java findet sich in Java 6. Die Viewer-Klasse heißt jetzt JOGL0005 und ist noch einmal vollständig angegeben. Die einzelnen im Interface GLEventListener angebenen und damit zu implementie renden Methoden werden hier noch einmal zusammengefasst: • void init( GLAutoDrawable drawable) – wird direkt nach der Initialisierung des OpenGL- Kontextes einmalig aufgerufen. Hier sollten initiale Einstellungen (z.B. für Beleuchtung, etc.) vorgenom men werden. • void display( GLAutoDrawable drawable) – wird immer aufgerufen, wenn das Fenster (der OpenGL-Kontext) neu gezeichnet werden muß. Diese Methode entspricht damit der Display-Callback in C und sollte die gewünschte Ausgabe herstellen. • void displayChanged( GLAutoDrawable drwb, boolean modeChg, boolean devChg) – wird immer dann aufgerufen, wenn sich der Displaymode oder das Ausgabegerät ändert – für die hier angegebenen Beispiele wird diese Methode nicht verwendet. • void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) – wird aufgerufen direkt nach einer Änderung der Position oder der Größe des Fensters während des ersten Neuzeichnens des Inhaltes. Das schließt auch das erstmalige Öffnen des Fensters ein. Hier kön nen und sollten Änderungen des Viewports vorgenommen werden. Die Methode reshape ruft allerdings automatisch glViewport(x, y, width, height) auf, so daß (wenn man keinen anderen View port benötigt und wenn man die Kameraeinstellungen nicht ändert) dieser nicht neu gesetzt werden muß. 19 Java 6: Initialisieren der synthetischen Kamera in der reshape()-Methode public class JOGL0005 implements GLEventListener { final GLUT glut = new GLUT(); final GLU glu = new GLU(); @Override public void display( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); glut.glutWireSphere( 0.1, 10, 10); gl.glTranslatef( 0.25f, 0.25f, 0.0f); glut.glutWireCube( 0.1f); gl.glTranslatef( -0.5f, 0.0f, 0.0f); glut.glutWireCube( 0.1f); gl.glTranslatef( 0.0f, -0.5f, 0.0f); glut.glutWireTeapot( 0.1); gl.glTranslatef( 0.5f, 0.0f, 0.0f); glut.glutWireTorus( 0.05, 0.1, 20, 20); gl.glFlush(); } @Override public void displayChanged( GLAutoDrawable drawable, boolean modeChg, boolean deviceChg) {} @Override public void init( GLAutoDrawable drawable) {} @Override public void reshape( GLAutoDrawable drawable, int x, int y, int width, int height) { GL gl = drawable.getGL(); gl.glMatrixMode( GL.GL_PROJECTION); gl.glLoadIdentity(); gl.glFrustum( -1, 1, -1, 1, 1.0, 15.0); // Projection-Matrix einstellen // Initialisieren mit der Einheitsmatrix // Sichtkörper der perspekt. Projektion gl.glMatrixMode( GL.GL_MODELVIEW); // ModelView-Matrix einstellen gl.glLoadIdentity(); // Initialisieren mit der Einheitsmatrix glu.gluLookAt( 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Kameraposition ... gl.glViewport(0, 0, width, height); // Bildschirmtransformation = Viewport } } 7 Erzeugen eigener Geometrien Graphische Primitive werden in OpenGL durch Punktlisten definiert. Dabei werden die einzelnen Punkte durch Aufrufe der Funktionen glVertex*(...) angegeben. Hier können sowohl zweidimensionale als auch dreidi mensionale Punkte angegeben werden. Im Zweidimensionalen werden die Funktionen glVertex2*(...) ver wendet, die erzeugten Punkte liegen implizit in einer Ebene bei z=0. Für dreidimensionale Geometrien werden die Funktionen glVertex3*(...) verwendet. Der letzte Buchstabe des Funktionsnamens gibt jeweils den Da tentyp der Parameter an: • i für integer, also glVertex2i(...) bzw. glVertex3i(...) • f für float, also glVertex2f(...) bzw. glVertex3f(...) • d für double, also glVertex2d(...) bzw. glVertex3d(...) Desweiteren wird spezifiziert, wie die einzelnen Punkte miteinander verbunden werden. Dies geschieht dadurch, daß die Spezifikation der Punkte durch die Funktionen glBegin(...) und glEnd() geklammert werden. Der Parameter von glBegin(...) gibt dabei an, wie die Punkte miteinander verbunden werden. Aufgabe 12 Aufgabe • 20 12: Wie werden die angegebenen Punkte miteinander bei dem entsprechenden Parameter verbunden? GL_POINTS • GL_LINES • GL_LINE_STRIP • GL_LINE_LOOP • GL_TRIANGLES • GL_TRIANGLE_STRIP • GL_TRIANGLE_FAN • GL_QUADS • GL_QUAD_STRIP • GL_POLYGON Im Programm 7 wird eine nützliche Funktion vorgestellt, die Linien verwendet, um ein Koordinatensystem zu zeichnen. Sie kann in anderen Programmen verwendet werden, um beispielsweise den Effekt von Transformatio nen zu studieren. Die Eigenschaft der mittels glBegin(GL_LINES) ... glEnd() gezeichneten Primitive, aus einzelnen Linien zu bestehen, wird hier explizit ausgenutzt. Die Koordinatenachsen werden als Paare von Eckpunkten in einem glBegin(GL_LINES) ... glEnd() Block angegeben. Ebenso die „Tickmarks“, die die Achsenunterteilung visualisieren sollen. Es werden jeweils Paare von Eckpunkten verbunden, dadurch ent steht das gewünschte Bild. Programm 7: GL_LINES zum Zeichnen eines Koordinatensystems void zeichneKoordinaten() { glColor3f( 0.0, 1.0, 0.0); glBegin( GL_LINES); glColor3f( 1.0, 0.0, 0.0); glVertex3f( -10.0, 0.0, 0.0); glVertex3f( 10.0, 0.0, 0.0); glColor3f( 0.0, 1.0, 0.0); glVertex3f( 0.0, -10.0, 0.0); glVertex3f( 0.0, 10.0, 0.0); glColor3f( 0.0, 0.0, 1.0); glVertex3f( 0.0, 0.0, -10.0); glVertex3f( 0.0, 0.0, 10.0); glEnd(); for( int i = -10; i <= 10; i++) { glBegin( GL_LINES); glColor3f( 1.0, 0.0, 0.0); glVertex3f((float)i, 0.0, 0.0); glVertex3f((float)i, 0.2, 0.0); glColor3f( 0.0, 1.0, 0.0); glVertex3f(0.0, (float)i, 0.0); glVertex3f(0.2, (float)i, 0.0); glColor3f( 0.0, 0.0, 1.0); glVertex3f(0.0, 0.0, (float)i); glVertex3f(0.2, 0.0, (float)i); glEnd(); } glColor3f( 0.0, 0.0, 0.0); } Um die Achsen unterscheidbar zu machen, wird mit Farben gearbeitet. Die Funktionen der Familie glCo lor*(...) setzen die aktuelle Zeichenfarbe auf den als Parameter übergebenen Farbwert. Das ist nicht mit Shading zu verwechseln, es werden hier keinerlei Beleuchtungsberechnungen vorgenommen, lediglich die Zei chenfarbe wird verändert. Diese ist eine Zustandsvariable, gilt also so lange, bis sie erneut verändert wird. Daher bleibt auch nach der Abarbeitung der Funktion zeichneKoordinaten() für weitere Zeichenbefehle die Far be auf weiß gesetzt. 21 Die Funktion zeichneKoordinaten() in Java ist wieder ähnlich (siehe Java 7). Sie muß – genau wie in C – an einer Stelle in der display()-Methode aufgerufen werden Java 7: zeichneKoordinaten() mit JOGL private void zeichneKoordinaten( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glColor3f( 0.0f, 1.0f, 0.0f); gl.glBegin( GL.GL_LINES); gl.glColor3f( 1.0f, 0.0f, 0.0f); gl.glVertex3f( -10.0f, 0.0f, 0.0f); gl.glVertex3f( 10.0f, 0.0f, 0.0f); gl.glColor3f( 0.0f, 1.0f, 0.0f); gl.glVertex3f( 0.0f, -10.0f, 0.0f); gl.glVertex3f( 0.0f, 10.0f, 0.0f); gl.glColor3f( 0.0f, 0.0f, 1.0f); gl.glVertex3f( 0.0f, 0.0f, -10.0f); gl.glVertex3f( 0.0f, 0.0f, 10.0f); gl.glEnd(); for( int i = -10; i <= 10; i++) { gl.glBegin( GL.GL_LINES); gl.glColor3f( 1.0f, 0.0f, 0.0f); gl.glVertex3f((float)i, 0.0f, 0.0f); gl.glVertex3f((float)i, 0.1f, 0.0f); gl.glColor3f( 0.0f, 1.0f, 0.0f); gl.glVertex3f(0.0f, (float)i, 0.0f); gl.glVertex3f(0.1f, (float)i, 0.0f); gl.glColor3f( 0.0f, 0.0f, 1.0f); gl.glVertex3f(0.0f, 0.0f, (float)i); gl.glVertex3f(0.1f, 0.0f, (float)i); gl.glEnd(); } gl.glColor3f( 1.0f, 1.0f, 1.0f); } Die allgemeine Vorgehensweise, um eigene Geometrien zu erzeugen, ist in Programm 8 demonstriert. In der Funktion zeichneQuader(...) werden die sechs Seitenflächen eines Quaders einzeln nacheinander jeweis durch eine GL_LINE_LOOP definiert. Programm 8: Erzeugen eigener Geometrien (nur die Display-Callback und die dafür benötige Funktion zeichneQuader(...) /* Erzeugt einen Quader mit der Ausdehnung von -x bis +x, von -y bis +y und von -z bis +z */ void zeichneQuader( float x, float y, float z) { glBegin( GL_LINE_LOOP); glVertex3f( -x, y, z); glVertex3f( -x, -y, z); glVertex3f( x, -y, z); glVertex3f( x, y, z); glEnd(); glBegin( GL_LINE_LOOP); glVertex3f( x, y, -z); glVertex3f( x, -y, -z); glVertex3f( -x, -y, -z); glVertex3f( -x, y, -z); glEnd(); glBegin( GL_LINE_LOOP); glVertex3f( -x, y, -z); glVertex3f( -x, -y, -z); glVertex3f( -x, -y, z); glVertex3f( -x, y, z); glEnd(); glBegin( GL_LINE_LOOP); glVertex3f( x, y, z); glVertex3f( x, -y, z); glVertex3f( x, -y, -z); glVertex3f( x, y, -z); glEnd(); glBegin( GL_LINE_LOOP); glVertex3f( -x, y, -z); glVertex3f( -x, y, z); glVertex3f( x, y, z); glVertex3f( x, y, -z); glEnd(); glBegin( GL_LINE_LOOP); glVertex3f( x, -y, -z); glVertex3f( x, -y, z); glVertex3f( -x, -y, z); glVertex3f( -x, -y, -z); glEnd(); } void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); zeichneQuader( 0.3, 0.4, 0.2); glutSwapBuffers(); } // Bildschirm (Fensterinhalt) loeschen // eigene Geometrie zeichnen // "Anzeige" des Bildes (DoubleBuffering) Die Umsetzung in Java wird hier nicht angegeben, sie ist fast identisch. 22 Aufgabe 13: Erläutern Sie die Funktion zeichneQuader(...). Wie müßte der Quader in der Ausgabe er scheinen? Aufgabe 13 Aufgabe 14: Welcher Parameter beim Aufruf von glBegin(...) würde zur gleichen Ausgabe führen? Aufgabe 14 Aufgabe 15: Schreiben Sie eine Funktion zeichneObjekt(), die das in Abbildung 13 dargestellte Objekt er stellt. Benutzen Sie dazu die Funktionen glVertex3f() mit GL_LINE_STRIPs oder GL_LINE_LOOPs. Diese Funktion enthält ausschließlich die Zeichenbefehle. Achten Sie darauf, daß die Koordinaten der Eckpunkte so wie angegeben sind und daß es sich um ein geschlossenes Objekt handelt. Die Ausgabe des Programms sollte wie Abbildung 14 aussehen, sofern Standard-Kameraeinstellungen verwendet werden. Aufgabe 15 Aufgabe 16: Wie muß das Programm bzw. die Funktion zeichneObjekt() verändert werden, damit das Ob jekt ausgefüllt dargestellt wird? Aufgabe 16 z x 0.1 -0.5 0.5 -0.1 y y 0.5 0.5 0.3 x -0.5 -0.3 0.3 0.5 z -0.1 0.1 -0.3 -0.5 Abbildung 14: Zu erwartende Ausgabe -0.5 Abbildung 13: Selbst definiertes Objekt 8 Animationen Um Animationen zu erstellen, müssen die Positionen von Objekten und/oder Kamera oder das Aussehen von Objekten in aufeinanderfolgenden Bildern (Frames) geändert werden. In der GLUT-Bibliothek stehen dazu Me thoden zur Verfügung, die diese Vorgehensweise vereinfachen. Die grundlegende Idee ist dabei, Transformatio nen (beispielsweise Rotationen) in Abhängigkeit eines zyklisch veränderten Parameters auszuführen. Anhand von Programm 9 soll diese Vorgehensweise erläutert werden. Die im Programm 9 nicht angegebenen Funktionen entsprechen denen aus vorangegangenen Beispielen und können so übernommen werden. Ziel ist es, das mit der Funktion zeichneObjekt() aus Aufgabe 15 erstellte geometrische Objekt um die xAchse rotieren zu lassen. Dazu wird in einer globalen Variablen spin der aktuelle Drehwinkel gespeichert. Die ser soll in zyklischen Abständen geändert werden und demnach bei jedem Aufruf der Display-Callback einen an deren Wert haben. Dadurch kommt die Bewegung zustande. GLUT bietet für die zyklische Änderung einen Me chanismus an: die Idle-Callback. Die Idle-Callback wird dabei immer dann aufgerufen, wenn „Zeit vorhanden ist“, also wenn keine anderen Ereignisse verarbeitet werden müssen. Hier – im Beispiel myIdle() – kann die die Animation steuernde Variable verändert werden. Die Variable spin wird bei jedem Aufruf der Idle-Callback um eins erhöht und beim Erreichen der 360°-Grenze wieder auf Null zurückgesetzt. Die Funktion glutPost Redisplay() teilt dem OpenGL-System mit, daß der aktuell angezeigte Inhalt nicht mehr gültig ist und daß schnellstmöglich die Display-Callback aufgerufen werden muß. Dies ist hier notwendig, da eine Veränderung des 23 Wertes von spin ja die Transformation des Objektes und damit die Darstellung beeinflußt. In der Display-Call back wird dann zunächst die aktuelle Transformationsmatrix gesichert und dann das Koordinatensystem gezeich net. Die folgende Rotation um den Winkel spin um die x-Achse erzeugt die korrekte Transformation für das animierte Objekt. Die Aufrufe von glPushMatrix() und glPopMatrix() sind notwendig, um die Animati on korrekt ablaufen zu lassen. Aufgabe 17 Aufgabe 17: Was würde geschehen, wenn die Aufrufe von glPushMatrix() und glPopMatrix() weggelas sen werden? Programm 9: Einfache Animation (Ausschnitt) #include <GL/glut.h> // globale Variablen -------------------------------------------------------------------------int spin = 0; // Winkel für Animation void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); glPushMatrix(); zeichneKoordinaten(); glRotatef( spin, 1.0, 0.0, 0.0); zeichneObjekt(); glPopMatrix(); glutSwapBuffers(); } // Bildschirm (Fensterinhalt) loeschen // "Anzeige" des Bildes (DoubleBuffering) /* Idle-Callback zum Manipulieren der Variablen, die zur Steuerung der Animation genutzt werden */ void myIdle( void) { spin += 1; // Erhöhen des Drehwinkels um ein Grad if (spin > 360) spin = 0; // Zurücksetzen glutPostRedisplay(); // neu zeichnen veranlassen } int main( int argc, char** argv) { glutInit( &argc, argv); glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize( screenWidth, screenHeight); glutCreateWindow( "OpenGL"); glutDisplayFunc( myDisplay); glutReshapeFunc( myReshape); glutIdleFunc( myIdle); glutMainLoop( ); exit( 0); } // // // // // // // // Initialisierung von GLUT Einstellen des Anzeigemodus Einstellen der Fenstergroesse Fenster erzeugen Setzen der Display-Funktion Setzen der Reshape-Callback Setzen der Idle-Callback glut-Hauptschleife Aufgabe 18 Aufgabe 18: Was wäre das Ergebnis, wenn anstelle von glPushMatrix() und glPopMatrix() zu Beginn der Display-Callback mittels glLoadIdentity() die Transformationsmatrix neu initialisiert würde? Für Animationen ist es erforderlich, daß das Programm Double-Buffering verwendet, da sonst keine kontinuierli che Ausgabe erfolgen würde – Sprünge und ungleichmäßige Bewegungen wären die Folge. JOGL handhabt Animationen etwas anders. Hierzu ist die Klasse Animator notwendig. Ein Animator wird an ein (oder mehrere) GLAutoDrawables angehangen und bewirkt, daß deren display() immer wieder auf 24 gerufen wird. Die Klasse Animator erzeugt dazu einen Hintergrundthread, in dem die Aufrufe von display() erfolgen. Nachdem jedes GLDrawable gezeichnet wurde, wird eine kurze Pause eingelegt, um die CPU zu „schonen“. Zur Steuerung bietet Animator einige einfache Methoden, die wichtigsten sind hierbei start() und stop(), die genau das tun, was man dem Namen nach vermutet. Um den Animator nutzen zu können, muß allerdings ein „Trick“ angewendet werden, da ansonsten das Fenster mit dem JOGL-Programm nicht geschlossen werden kann. Dazu ist die Klasse JOGLMain zu erweitern. Diese Erweiterungen sind in Java 8 gezeigt, der Klarheit wegen wurde eine neue Klasse JOGLAniMain gewählt, die jetzt als Hauptprogramm dient. Java 8: Neues Hauptprogramm für Animationen public class JOGLAniMain extends JFrame { final static int screenWidth = 600; final static int screenHeight = 600; public JOGLAniMain() { GLCapabilities glcaps = new GLCapabilities(); glcaps.setDoubleBuffered( true); GLCanvas canvas = new GLCanvas( glcaps); final Animator animator = new Animator( canvas); animator.start(); JOGL000009 view = new JOGL000009(); canvas.addGLEventListener( view); setSize( screenWidth, screenHeight); setFocusable( true); setTitle( "JOGL - Beispielszene"); getContentPane().add( canvas, BorderLayout.CENTER); } addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { Thread thread = new Thread(new Runnable() { public void run() { animator.stop(); dispose(); } }); thread.start(); } }); public static void main(String[] args) { final JOGLAniMain app = new JOGLAniMain(); SwingUtilities.invokeLater( new Runnable() { public void run() { app.setVisible(true); } }); } } Die entsprechende Viewer-Klasse JOGL1007 bedient sich dann des gleichen Mechanismus, wie von den C-Pro grammen bekannt. Zunächst wird das Koordinatensystem gezeichnet und dann das Objekt rotiert und auch ge zeichnet. Im Anschluß muß hier die Funktion zur Veränderung des Rotationswinkels allerdings selber aufgerufen werden, da es eine Idle-Callback nicht gibt. Ebenso wie in der C-Version wird dazu eine globale Variable (hier: spin) benötigt. Diese wird in der Funktion idle() verändert und bewirkt dann entsprechend beim Aufruf von glRotate(...) die Drehung. Die weiteren verwendeten Methoden z eichneKoordinaten() und zeich neObjekt() sind im Quellcode in Java 9 nicht mit angegeben. zeichneKoordinaten() wurde in Java 7 be sprochen und zeichneObjekt() war Gegenstand von Aufgabe 15. 25 Java 9: Viewer-Klasse für die einfache Animation public class JOGL1007 implements GLEventListener { final GLUT glut = new GLUT(); final GLU glu = new GLU(); private int spin = 0; private void zeichneKoordinaten( GLAutoDrawable drawable) { ... } private void zeichneObjekt( GLAutoDrawable drawable) { ... } @Override public void display( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); gl.glPushMatrix(); zeichneKoordinaten( drawable); gl.glRotatef( spin, 1.0f, 0.0f, 0.0f); zeichneObjekt( drawable); gl.glPopMatrix(); idle(); gl.glFlush(); } @Override public void displayChanged( GLAutoDrawable drawable, boolean modeChg, boolean deviceChg) {} @Override public void init( GLAutoDrawable drawable) {} @Override public void reshape( GLAutoDrawable drawable, int x, int y, int width, int height) { GL gl = drawable.getGL(); gl.glMatrixMode( GL.GL_PROJECTION); gl.glLoadIdentity(); gl.glFrustum( -1, 1, -1, 1, 1.0, 15.0); gl.glMatrixMode( GL.GL_MODELVIEW); gl.glLoadIdentity(); glu.gluLookAt( 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); gl.glViewport(0, 0, width, height); } } public void idle() { spin += 1; if (spin > 360) spin = 0; } In Vorbereitung auf die nächste Aufgabe, die sich mit einer etwas komplexeren Animation beschäftigt, wird in Programm 10 ein weiteres Beispiel angegeben. Die Steuerung der Animation bleibt hierbei die gleiche wie in Programm 9, so daß hier nur die Display-Callback angegeben ist. Ziel ist es, eine Kugel um eine Seite des Rah mens fliegen zu lassen. Dazu wird zunächst der Rahmen im Koordinatenursprung gezeichnet und dann die Ku gel entsprechend transformiert und animiert. Um die Kugel auf einer kreisförmigen Bahn zu bewegen, muß sie zunächst um den Radius der Kreisbahn vom Ursprung wegbewegt werden. Diese Translation und die Rotation würde dazu führen, daß sich die Kugel auf einer Kreisbahn um die y-Achse bewegt. Diese Kreisbahn muß dann noch ein weiteres Mal um den Radius nach außen verschoben werden, um eine Bewegung um den Rahmen zu realisieren. Demnach muß die Kugel zunächst aus dem Ursprung verschoben, dann rotiert und dann nochmals 26 verschoben werden, was sich im Aufruf der drei Transformationsfunktionen niederschlägt. Man beachte die Rei henfolge des Aufrufs der Funktionen, da durch den Transformationsmechnismus bei OpenGL die zuletzt ange gebene Transformation zuerst ausgeführt wird. Programm 10: Display-Callback für die Kugel-Animation void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); glPushMatrix(); zeichneKoordinaten(); zeichneObjekt(); glPushMatrix(); glTranslatef( 0.4, 0.0, 0.0); glRotatef( spin, 0.0, 1.0, 0.0); glTranslatef( 0.4, 0.0, 0.0); glutSolidSphere( 0.2, 10, 10); glPopMatrix(); glPopMatrix(); glutSwapBuffers(); } // Bildschirm (Fensterinhalt) loeschen // "Anzeige" des Bildes (DoubleBuffering) Die entsprechende display()-Methode für JOGL ist äquivalent (siehe Java 10). Java 10: Kugelanimation wie in Programm 10 public void display( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); gl.glPushMatrix(); zeichneKoordinaten( drawable); zeichneObjekt( drawable); gl.glPushMatrix(); gl.glTranslatef( 0.4f, 0.0f, 0.0f); gl.glRotatef( spin, 0.0f, 1.0f, 0.0f); gl.glTranslatef( 0.4f, 0.0f, 0.0f); glut.glutSolidSphere( 0.2f, 10, 10); gl.glPopMatrix(); gl.glPopMatrix(); idle(); gl.glFlush(); } Aufgabe 19: Ändern Sie Programm 10 so ab, daß sich die Kugel auf einer ∞-förmigen Bahn (zwei aneinander liegende Kreise) um bzw. durch das Objekt bewegt. Das Objekt liegt dabei in der xy-Ebene, der Schnittpunkt der ∞ liegt im Koordinatenursprung und die Kugel fliegt in der xz-Ebene um die Seiten des Objektes herum. Lassen Sie das gesamte System dann um die x-Achse in einer anderen Geschwindigkeit rotieren. Hinweise: Denken Sie an die Benutzung der Funktionen glPushMatrix() und glPopMatrix(). Die Änderungen werden haupt sächlich die Idle-Callback betreffen. Die Lösung dieser Aufgabe wird als Grundlage für die weitere Programmierung dienen. Alle weiteren Aufgaben bauen darauf auf. 9 Interaktion Alle bisherigen Programme haben lediglich eine graphische Ausgabe erzeugt, Eingaben über die Tatstatur oder Maus waren nicht möglich. OpenGL ist eine API für interaktive graphische Applikationen, daher soll im folgen den die Eingabe etwas näher beleuchtet werden. Die einfachste Möglichkeit, in OpenGL Tastatur- und Mausein gaben zu verarbeiten liegt in der Nutzung des OpenGL Utility Toolkits GLUT, das einen Callback-Mechanismus zur Programmierung von Interaktion bereitstellt. Unter Java und JOGL stehen die Callback-Mechanismen von GLUT nicht zur Verfügung. Interaktion erfolgt hier mit „Java-Bordmitteln“, also über KeyboardListener, MouseListener usw. Dementsprechend müssen die er forderlichen Interfaces verwendet werden. 27 Aufgabe 19 9.1 Tastatur Zur Realisierung der Tastatureingabe stellt GLUT die Keyboard-Callback zur Verfügung. Sie wird mittels glut KeyboardFunc(...) registriert und hat selber drei Parameter: • key: den Character-Code der gedrückten Taste • x: die x-Position der Maus zum Zeitpunkt der Aktivierung • y: die y-Position der Maus zum Zeitpunkt der Aktivierung Ausgehend vom abschließenden Beispiel aus dem vorigen Kapitel zeigt Programm 113 die Erweiterungen, um folgende Funktionen zu realisieren: • Beenden des Programms beim Drücken der ESC-Taste: Der Character-Code der ESC-Taste ist 27; das Programm wird beim Auftreten dieses Ereignisses mit exit( 0) beendet. • Ein-/Ausschalten der Animation durch Drücken der Taste 'A': Beide möglichen Character-Codes 'A' und 'a' werden abgefangen und die Idle-Callback entfernt (glutIdleFunc( NULL)) oder wieder regis triert (glutIdleFunc( myIdle)). Die Feststellung, ob die Animation läuft oder nicht erfolgt über eine globale boolesche Variable animationRunning, die zu Beginn auf true gesetzt wird und die bei jedem Tastendruck auf 'a' invertiert wird. Für Spezialtasten wie die Pfeiltasten und Funktionstasten muß mit glutSpecialFunc(...) eine gesonderte Callback registriert werden, die vom Aufbau her der Keyboard-Callback gleicht. Für die einzelnen Tasten sind in GLUT Konstanten definiert wie beispielsweise GLUT_KEY_F1 oder GLUT_KEY_RIGHT, die dann in entspre chenden Abfragen verwendet werden können. Programm 11: Keyboard-Callback (Auszüge) bool animationRunning = true; // an- und abschalten der Animation void myKeyboard( unsigned char key, int x, int y) { if (key == 27) exit( 0); if ((key == 'a') || (key == 'A')) { if (animationRunning) glutIdleFunc( NULL); else glutIdleFunc( myIdle); animationRunning = !animationRunning; } } int main( int argc, char** argv) { // ... glutKeyboardFunc( myKeyboard); // ... } // Setzen der Keyboard-Callback Unter Java wird für die Einbindung der Tastatur das Interface KeyListener verwendet. Dieses sorgt für den Empfang von Tastaturereignissen und bietet Funktionen, auf diese zu reagieren. Die Definition der Viewer-Klas se muß also um ... implements ... KeyListener erweitert werden. Die dann zu implementierenden Funktionen sind: • void keyPressed( KeyEvent e) – wird aufgerufen, wenn eine Taste gedrückt wurde (reagiert nur auf das Drücken, das Loslassen ist irrelevant). • void keyReleased( KeyEvent e) – aufgerufen, wenn eine Taste losgelassen wurde. • void keyTyped( KeyEvent e) – reagiert auf einen Tastenanschlag, d.h. Drücken und Loslassen kurz nacheinander. Wie bereits beim Interface GLEventListener müssen nicht verwendete Methoden mit leerem Fnktionskörper vorhanden sein. Für das oben angegebene Beispiel ergibt sich also der (gekürzt dargestellte) Quellcode in Java 11 3 Die Numerrierung der Quellcodedateien beginnt ab hier mit „1“, da ab jetzt alle Programme die Lösung der Aufgabe 19 als Grundlage verwenden. 28 Die ausgelassenen Teile entsprechen dem vorhergehenden Programm. Java 11: Auswerten von Tastaturereignissen public class JOGL1009 implements GLEventListener, KeyListener { final GLUT glut = new GLUT(); final GLU glu = new GLU(); private boolean animationRunning = true; // an- und abschalten der Animation /* ... */ @Override public void keyPressed( KeyEvent e) {} @Override public void keyReleased( KeyEvent e) {} @Override public void keyTyped( KeyEvent e) { char key = e.getKeyChar(); if (key == 27) System.exit(0); if ((key == 'a') || (key == 'A')) animationRunning = !animationRunning; } 9.2 Maus Auch die Maus – oder allgemeiner ein Zeigegerät wird über den Mechanismus der Callbacks behandelt. Aller dings erzeugt ein Zeigegerät weit mehr unterschiedliche Eventtypen als die Tastatur, so daß es hier mehrere Funktionen zu beachten gibt: • Mausbewegung bei gedrückter Maustaste: Hier werden Mouse-Motion-Events erzeugt, die in der Mo tion-Callback abgefangen werden. Registriert wird diese mittels glutMotionFunc(...). Die Funkti on selber hat den Prototyp myMotion(int x, int y) und damit zwei Parameter: – x gibt die x-Position der Maus an – y gibt die y-Position der Maus an • Mausbewegung bei nicht gedrückter Maustaste: Hier werden ebenfalls Mouse-Motion-Events erzeugt, die aber mit der PassiveMotion-Callback abgefangen werden. Sie wird mittels glutPassiveMotion Func(...) registriert und entspricht in ihrem Aufbau der oben genannten Motion-Callback. • Drücken oder Loslassen einer Maustaste. Es werden Events erzeugt, die in der Mouse-Callback abgefan gen werden. Ihr Prototyp myMouse(int button, int state, int x, int y) schreibt vier Parameter vor, die die folgende Bedeutung haben: – button gibt den Button an, der bei der Bewegung gedrückt war. Eine Abfrage kann mit Hilfe der OpenGL-Konstanten GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON oder GLUT_RIGHT_BUT TON erfolgen. – state gibt an, ob die entsprechende Maustaste gedrückt GLUT_DOWN oder losgelassen GLUT_UP wurde. – x gibt die x-Position der Maus an. – y gibt die y-Position der Maus an. Wichtig ist hierbei, daß jedes Drücken und jedes Loslassen ein eigenes Ereignis erzeugt, ein Mausklick sind also zwei Ereignisse. Die Nutzung dieser Callback-Funktionen ermöglicht z. B. ein interaktives Zeichnen oder aber die interaktive Be wegung von Objekten oder der Kamera. Das im Programm 12 folgende Beispiel für eine Mouse-Callback ersetzt einen Teil der Keyboard-Callback aus Programm 11. Durch Drücken der linken Maustatste wird die Animation eingeschaltet und durch Drücken der rechten Maustaste ausgeschaltet. 29 Programm 12: Tastatur- und Mauseingaben (siehe auch Programm 11) /* geänderte Keyboard-Callback aus dem vorangehenden Beispiel */ void myKeyboard( unsigned char key, int x, int y) { if (key == 27) exit( 0); } void myMouse( int button, int state, int x, int y) { if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN)) glutIdleFunc( myIdle); else if ((button == GLUT_RIGHT_BUTTON) && (state == GLUT_DOWN)) glutIdleFunc( NULL); } int main( int argc, char** argv) { // ... glutKeyboardFunc( myKeyboard); glutMouseFunc( myMouse); // ... } // Setzen der Keyboard-Callback // Setzen der Mause-Callback In Java erfolgt die Einbindung des Zeigegerätes über die Interfaces MouseListener, MouseMotionListe ner (AWT) oder MouseInputListener (Swing). Letzteres vereinigt dabei für Swing die Funktionen der bei den anderen. Je nach verwendetem Window Toolkit müssen dann die Interfaces ausgewählt werden. Die folgen den Beispiele verwenden den MouseInputListener, da die Programme von Anfang an als Swing-Programme entwickelt wurden (siehe Java 1). Die bereitgestellten und zu implementierenden Methoden sind: • void mouseEntered( MouseEvent e) – aufgerufen, wenn die Maus den Bereich des Interfaceele ments betritt, das mit dem Listener verbunden ist. • void mouseExited( MouseEvent e) – aufgerufen, wenn die Maus den Bereich des Interfaceele ments verläßt, das mit dem Listener verbunden ist. • void mousePressed( MouseEvent e) – aufgerufen, wenn ein Mausknopf gedrückt wurde (rea giert nur auf Drücken, nicht auf Loslassen). • void mouseClicked( MouseEvent e) – nach einem Mausklick aufgerufen (Drücken und Loslas sen in kurzer Folge). • void mouseReleased( MouseEvent e) – aufgerufen, wenn ein Mausknopf losgelassen wurde. • void mouseDragged( MouseEvent e) – aufgerufen, wenn die Maus mit gedrücktem Knopf be wegt wurde. Die entsprechenden Events werden solange geliefert (also die Methode solange immer wie der aufgerufen), bis die Maustaste losgelassen wird. • void mouseMoved( MouseEvent e) – wird aufgerufen, wenn die Maus mit losgelassenem Knopf bewegt wurde. Die entsprechenden Events werden solange geliefert (also die Methode solange immer wieder aufgerufen), bis die Maus das Interfaceelement verläßt. Um die aus Programm 12 bekannte Funktionalität zu realisieren, wird die Methode keyTyped() so verändert, daß sie nur noch auf die ESC-Taste reagiert. Zusätzlich wird die Methode mouseClicked() wie in Java 12 ge zeigt implementiert. Die weiteren Tastatur- und Maus-Methoden müssen mit leerem Funktionskörper bereitgestellt werden. 30 Java 12: Maus- und Tastatureingabe (äquivalent zu Programm 12) public class JOGL10010 implements GLEventListener, KeyListener, MouseInputListener { /* ... */ @Override public void keyTyped( KeyEvent e) { char key = e.getKeyChar(); if (key == 27) System.exit(0); } // Abfrage des Tastencodes // Wenn ESC, dann Programm beenden @Override public void mouseClicked( MouseEvent e) { int button = e.getButton(); // Abfrage des Mausknopfes if (button == MouseEvent.BUTTON1) animationRunning = true; // links: Animation start if (button == MouseEvent.BUTTON3) animationRunning = false;// rechts: stop } } /* ... */ Das im Programm 13 gezeigte Beispiel ist etwas komplexer. Ausgangspunkt ist wiederum Programm 11, Starten und Stoppen der Animation erfolgt also weiterhin über die Tastatur. Ziel ist es, die Kamera mit der Maus um das rotierende System herumzubewegen. Dazu sollen die horizontalen und vertikalen Mausbewegungen (die ja in 2D erfolgen) in eine Rotation der Kamera um den Ursprung umgesetzt werden wobei die Blickrichtung der Ka mera immer auf den Ursprung bestehen bleibt. Die Position der Kamera kann sowohl in kartesischen (x, y)-Ko ordinaten als auch in Kugelkoordinaten (θ, φ) beschrieben werden. Das bietet einen Ansatz, um mit einer zweidi mensionalen Bewegung der Maus eine dreidimensionale Bewegung der Kamera zu beschreiben und zu steuern. Die grundlegende Idee ist also, die Änderung in der Mausposition ∆x und ∆y in eine Änderung der Position der Kamera in Kugelkoordinaten ∆θ und ∆φ umzurechnen und aus den Kugelkoordinaten dann die Kameraposition in kartesischen Koordinaten zu bestimmen. Da sich die Koordinaten des Kamerastandpunktes (kartesisch und sphärisch) sowie die Position der Maus ändern, müssen diese in globalen Variablen gespeichert werden, um sie eventuell an anderer Stelle des Programms verwenden zu können. Ebenfalls notwendig ist eine Speicherung der Entfernung der Kamera vom Ursprung. Diese entspricht dem Radius der Kugel, auf deren Oberfläche sich die Kamera bewegt und wird bei der Umrechnung von sphärischen in kartesische Koordinaten benötigt. Diese Um rechung sowie die Abbildung der 2D-Mausbewegung auf die Änderung der sphärischen Koordinaten der Kame ra erfolgt in der Motion-Callback. Zunächst muß die Änderung der Mausposition festgestellt werden. Da die Motion-Callback bei jedem Mouse Motion-Event aufgerufen wird kann dies einfach durch die Differenz der beiden Positionen aus zwei aufeinan derfolgenden Aufrufen geschehen (deltaX und deltaY). Dazu wird am Ende der Callback die aktuelle Positi on in den Variablen oldX und oldY gespeichert. Die Umrechnung der relativen Positionsänderung der Maus in eine Winkeländerung der sphärischen Koordinaten der Kamera ist dann einfach. Angenommen, die Bewegung der Maus über das gesamte Fenster (Breite screenWidth, Höhe screenHeight) soll auf eine komplette Ro tation der Kamera um den Ursprung abgebildet werden, dann ergibt sich für die Winkeländerungen ∆θ = (2π ∆x)/screenWidth und ∆φ = (2π ∆y)/screenHeight. Diese Änderungen werden zu den aktuellen Werten von θ und φ addiert und dann erfolgt die Umrechnung dieser beiden Winkel in die kartesischen Koordinaten der Kamerapo sition nach den bekannten Beziehungen: x = distance cosθ cosφ y = distance cosθ sinφ z = distance sinθ Damit ist die Abbildung der Bewegung der Maus auf die der Kamera abgeschlossen. Um ein Neuzeichnen des Fensterinhaltes zu erzwingen, erfolgt am Ende der Motion-Callback noch ein Aufruf der Funktion glutPost Redisplay(). Zur Komplettierung des Programms fehlen noch die initialen Belegungen der Variablen sowie die eigentliche Anpassung der Kameraposition. Diese geschieht vor jedem Neuzeichnen der Szene, also zu Beginn der Display- 31 Callback. Die Kamera wird mittels gluLookAt( posX, posY, posZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0). Damit wird die Kamera auf die gerade berechnete Position gesetzt. Initial wird die Kamera so positio niert, daß entlang der z-Achse auf den Ursprung geblickt wird, also an den Punkt (0, 0, distance). Die Entfernung distance bleibt bei der Bewegung der Kamera auf der Kugeloberfläche ja stets gleich. Die Eingangswerte für θ und φ berechnen sich dann daraus zu π/2 und 0. Damit kann nach der Belegung der globalen Variablen direkt bei der Deklaration der Aufruf von gluLookAt(...) in der Funktion myReshape() auch mit den Werten von posX, posY und posZ erfolgen. Programm 13: Kamerasteuerung mit der Maus (Auszüge) // -- globale Variablen für die Kamerasteuerung ----------------------------------------------int oldX, oldY; float distance = 5.0; float posX = 0.0, posY = 0.0, posZ = distance; float theta = M_PI / 2.0, phi = 0.0; // -------------------------------------------------------------------------------------------void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT); // Bildschirm (Fensterinhalt) loeschen glLoadIdentity(); gluLookAt( posX, posY, posZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glPushMatrix(); glRotatef( spin, 1.0, 0.0, 0.0); zeichneObjekt(); glPushMatrix(); glTranslatef( direction * 0.4, 0.0, 0.0); glRotatef( (float)direction * winkel, 0.0, 1.0, 0.0); glTranslatef( direction * 0.4, 0.0, 0.0); glutSolidSphere( 0.2, 10, 10); glPopMatrix(); glPopMatrix(); glutSwapBuffers(); } void myReshape( GLsizei w, GLsizei h) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum( -1, 1, -1, 1, 1.0, 15.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( posX, posY, posZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glViewport(0, 0, w, h); } void myMotion( int x, int y) { int deltaX = oldX - x; int deltaY = oldY - y; float deltaTheta = (2 * M_PI * deltaX) / screenWidth; float deltaPhi = (2 * M_PI * deltaY) / screenHeight; theta += deltaTheta; phi += deltaPhi; posX = distance * cos( theta) * cos( phi); posY = distance * cos( theta) * sin ( phi); posZ = distance * -sin( theta); oldX = x; oldY = y; } int main( int argc, char** argv) { // ... glutMotionFunc( myMotion); //... } 32 // Setzen der Motion-Callback Die JOGL-Variante wird äquivalent implementiert. Die für die Maussteuerung verwendete Methode ist hier mouseDragged(). Aus dem übergebenen MouseEvent e können mittels e.getX() und e.getY() die Po sitionsdaten gelesen werden. Der Rest entspricht dem obigen Muster. Aufgabe 20: Erweitern Sie Programm 13 durch eine Zoom-Funktionalität. Dazu können Sie sowohl die Pfeiltas ten verwenden als auch die Maus. 10 Beleuchtung und Materialien Zur Beleuchtungsberechnung und zum Shading in OpenGL sind vier Komponenten notwendig: Oberflächen normalen, Materialen, Lichtquellen und einige globale Einstellungen. Zu diesen globalen Einstellungen gehört ei nerseits das Shading-Verfahren, welches angewendet werden soll und andererseits das globale Ein- bzw. Aus schalten der Beleuchtungsberechnung. Das Shading-Verfahren wird über die OpenGL-Funktion glShadeMo del(...) bestimmt. Die beiden möglichen Parameter hier sind: • GL_FLAT – Hier wird Flat-Shading angewendet, das heißt, ein Beleuchtungswert wird für das gesamte Polygon verwendet. Zur Beleuchtungsberechnung sind Normalen an den Eckpunkten notwendig. OpenGL verwendet hier die Normale des ersten Eckpunktes zur Berechnung der Beleuchtung bei ein zelnen Polygonen, bei Dreiecksnetzen ist dies die dritte Normal für das erste Dreieck, die vierte für das zweite usw. Äquivalentes gilt für andere Polygonnetze. Flat-Shading ist das schnellste aber auch qualitativ schlechteste Shading-Verfahren • GL_SMOOTH – Hier wird Gouraud-Shading angewendet. OpenGL interpoliert die Farben über Polygo ne. Linien und andere Primitive. Wenn Normalen vorhanden sind und die Beleuchtung eingeschaltet ist wird die Farbe im Inneren eines Polygons nach Gouraud-Shading berechet und gezeichnet. Dieses Sha ding-Verfahren zeigt qualitativ bessere Ergebnisse als Flat-Shading, ist aber auch langsamer. Um die Beleuchtungsberechnung global ein- oder auszuschalten, stellt OpenGL die Statusvariable GL_LIGH TING zur Verfügung. Die Beleuchtungsberechnung wird also mittels glEnable(GL_LIGHTING) ein und mit glDisable( GL_LIGHTING) ausgeschaltet. 10.1 Oberflächennormalen Um Beleuchtungswerte korrekt zu berechnen, werden Normalenvektoren an den Vertices des Modells benötigt. Diese können ähnlich den Koordinaten der Eckpunkte bei der Definition des Modells angegeben werden. Dazu dienen die Funktionen glNormal*(...). Eine mit glNormal*(...) definierte Normale wird so lange mit folgenden Vertices assoziiert, bis ein erneuter Aufruf von glNormal*(...) erfolgt. Normalenvektoren wer den – genau wie Eckpunkte – in lokalen Koordinaten spezifiziert und dann entsprechend den gegebenen Trans formationsmatrizen transformiert. Bei der Definition einer Oberfläche aus einzelnen Polygonen wird üblicherweise nach folgendem Schema vorge gangen for (jedes Polygon) { glBegin( GL_POLYGON); for (jeder Eckpunkt des Polygons) { glNormal3f( nx, ny, nz); glVertex3f (vx, vy, vz); } glEnd(); } Bei der Verwendung anderer Primitive ist die Vorgehensweise ähnlich – die Normalen müssen immer vor dem Eckpunkt angegeben werden, zu dem sie gehören. 33 Aufgabe 20 10.2 Lichtquellen Von OpenGL werden verschiedene Typen von Lichtquellen unterstützt: Ambientes Licht, Punktlichtquellen, Spotlichtquellen und gerichtete Lichtquellen. In einem Programm können bis zu 8 Lichtquellen definiert und verwendet werden. Die Definition der Lichtquellen erfolgt über die Angabe von verschiedenen Parametern mit Hilfe der Funktionen • glLightfv( GLenum source, GLenum parameter, GLfloat *array) , die einen Vek tor-Parameter spezifiziert und • glLightf( GLenum source, GLenum parameter, GLfloat value) , die einen skalaren Pa rameter spezifiziert. Der erste Parameter in beiden Funktionen spezifiziert dabei die Lichtquelle, für die der entsprechende Wert zu setzen ist. OpenGL bietet hier die vordefinierten Konstanten GL_LIGHT0 bis GL_LIGHT7 an. Der zweite Para meter beschreibt, welcher Parameter der Lichtquelle zu setzen ist: • GL_POSITION spezifiziert die Koordinaten der Position der Lichtquelle in homogenen Koordinaten. Die Position der Lichtquelle unterliegt der Transformation mit der aktuellen ModelView-Matrix (genau wie Vertices). Wenn die vierte Koordinate des Positionsvektors gleich 0 ist, wird das Licht als gerichtete Lichtquelle angesehen mit den frei spezifizierten Koordinaten als Richtung. In diesem Fall fließt nur die Richtung in die Beleuchtungsberechnung ein und die entfernungsabhängige Abschwächung (Attenuati on) ist abgeschaltet. Bei einer Lichtquelle an einer endlichen Position (vierte Komponente der Position ungleich 0) wird die Beleuchtung inclusive entfernungsabhängiger Abschwächung aus der Positionsanga be berechnet. Die vier angegebenen Werte werden dann als homogene Koordinaten der Lichtquellenpo sition angesehen. Die initiale Position ist auf (0, 0, 1, 0) eingestellt, es handelt sich daher um eine gerich tete Lichtquelle entlang der negativen z-Achse. • GL_AMBIENT spezifiziert die Intensität des ambienten Anteils des von der Lichtquelle ausgesandten Lichtes. Standardwert ist (0, 0, 0, 1) • GL_DIFFUSE spezifiziert die Intensität des diffusen Anteils des von der Lichtquelle ausgesandten Lich tes. Standardwert ist (1, 1, 1, 1) für GL_LIGHT0, für alle anderen Lichtquellen (0, 0, 0, 1) • GL_SPECULAR spezifiziert die Intensität des spekularen Anteils des von der Lichtquelle ausgesandten Lichtes. Standardwert ist (1, 1, 1, 1) für GL_LIGHT0, für alle anderen Lichtquellen (0, 0, 0, 1). • GL_SPOT_DIRECTION spezifiziert die Richtung der zentralen Achse des Lichtkegels bei einem Spot light. Standardeinstellung ist (0, 0, 1) • GL_SPOT_EXPONENT spezifiziert die Geschwindigkeit des Intensitätsabfalls nach außen im Lichtkegel. • GL_SPOT_CUTOFF spezifiziert den Öffnungswinkel des Lichkegels. Werte zwischen 0 und 90 sowie der spezielle Wert 180 sind erlaubt. Bei der Angabe von 180 (Standardeinstellung) erfolgt eine uniforme Lichtverteilung (kein Lichtkegel) • • • GL_CONSTANT_ATENUATION GL_LINEAR_ATENUATION GL_QUADRATIC_ATENUATION spezifizieren die drei Koeffizienten in der quadratischen Gleichung zur entfernungsabhängigen Lichtabschwächung Nachdem die Parameter einer Lichtquelle spezifiziert sind, muß die Lichtquelle eingeschaltet werden. Dies ge schieht beispielsweise für GL_LIGHT0 mittels glEnable( GL_LIGHT0). Dementsprechend wird mit glDi sable( GL_LIGHT0) die Lichtquelle wieder deaktiviert. Diese Funktionen haben allerdings nur dann einen Effekt, wenn die Beleuchtung insgesamt angeschaltet ist. Eine weitere Möglichkeit besteht darin, den Anteil des ambienten Lichts für die gesamte Szene zu setzen. Um beispielsweise eine weiße Lichtquelle als Streulicht zu setzen, kann folgender Code verwendet werden: GLfloat global_ambient[] = {0.1, 0.1, 0.1, 1.0}; glLightModelfv( GL_LIGHT_MODEL_AMBIENT, global_ambient); Die erste Zeile definiert dabei die Farbe des ambienten Lichtes als RGBα-Tupel und mit Hilfe der Funktion gl LightModelfv(...) wird dieser Wert dann für OpenGL gesetzt. 34 10.3 Materialbeschreibungen Wenn in OpenGL einem Objekt ein Material zugewiesen wird, wird theoretisch beschrieben, wie das entspre chende Objekt auf Lichteinfall reagiert. Dies ist nur dann der Fall, wenn wie unten beschrieben mit Materialbe schreibungen gearbeitet wird. Die Nutzung von glColor*(...) weist einem Objekt (oder auch nur einem Vertex) eine Farbe zu, die nicht auf Beleuchtung reagiert. Dies ist sinnvoll für Programme, in denen nicht mit Beleuchtung gearbeitet wird, um die Objektfarbe an sich zu beschreiben. Zur Beschreibung von Materialien stehen wie bei Lichtquellen zwei Funktionen zur Verfügung: • glMaterialfv( GLenum face, GLenum parameter, GLfloat *array) setzt einen Vek tor-Parameter • glMaterialf(GLenum face, GLenum parameter, GLfloat value) setzt einen skalaren Parameter Der erste Parameter face gibt dabei an, welche Seite der Polygone mit dem Material versehen werden sollen. Erlaubt sind die Werte GL_FRONT für die Vorderseite, GL_BACK für die Rückseite oder GL_FRONT_AND_BACK für beide Seiten. Diese Werte werden in Verbindung mit den Einstellungen zum Backface-Culling dann entspre chend verwendet. Die pro Material zu setzenden Parameter sind in folgender Liste zusammengefasst: • GL_AMBIENT enthält vier Werte, die die Intensität der Reflexion des ambienten Lichtanteils spezifizie ren. Der Standardwert ist (0.2, 0.2, 0.2, 1.0) für Vorder- und Rückseiten. • GL_DIFFUSE enthält vier Werte, die die Intensität der Reflexion des diffusen Lichtanteils spezifizieren. Der Standardwert ist (0.8, 0.8, 0.8, 1.0). • GL_SPECULAR enthält vier Werte, die die Intensität der Reflexion des spekularen Lichtanteils spezifizie ren. Der Standardwert ist (0, 0, 0, 1). • GL_EMISSION enthält vier Werte, die die Intensität des vom Material ausgesandten Lichtanteils spezifi zieren. Der Standardwert ist (0, 0, 0, 1). • GL_SHININESS spezifiziert den spekularen Exponenten des Materials. Werte müssen im Bereich zwi schen 0 und 128 liegen. Die Standardeinstellung ist 0 • GL_AMBIENT_AND_DIFFUSE ist äquivalent zum zweimaligen Aufruf von glMaterialfv(...) mit den gleichen Werten für GL_AMBIENT und GL_DIFFUSE. Materialbeschreibungen gelten für alle nachfolgenden Funktionsaufrufe zum Erzeugen von Geometrie so lange, bis sie durch eine andere Materialbeschreibung ersetzt werden. In diesem Sinne wirken sie wie Statusvariablen. Die Quellcodeausschnitte in Programm 14 bis Programm 16 geben ein Beispiel für die Verwendung von Mate rialien und Lichtquellen. Zunächst werden die einzelnen Parameter für die Lichtquellen und die Materialien als globale Variablen definiert (Programm 14). Programm 14: Beispiel für Materialien und Lichtquellen – Definition der Parameter // globale Variablen für die Beleuchtung ------------------------------------------------------GLfloat LightAmbient[]= { 0.2, 0.2, 0.2, 1.0f }; GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; GLfloat LightDiffuse[] = { 0.0f, 0.0f, 1.0f, 1.0f }; GLfloat Light1Position[]= { 0.0f, 2.0f, 0.0f, 1.0f }; GLfloat Light1Diffuse[] = { 1.0f, 0.0f, 0.0f, 1.0f }; // globale Variablen für die Materialien ------------------------------------------------------GLfloat ambientTorus[] = {0.2, 0.2, 0.2, 1.0}; GLfloat diffuseTorus[] = {1.0, 0.8, 0.0, 1.0}; GLfloat specularTorus[]= {1.0, 1.0, 1.0, 1.0}; GLfloat ambientKugel[] = {0.19, 0.19, 0.19, 1.0}; GLfloat diffuseKugel[] = {0.51, 0.51, 0.51, 1.0}; GLfloat specularKugel[]= {0.51, 0.51, 0.51, 1.0}; In der (wieder eingeführten) Funktion myInit() werden die Lichtquellen spezifiziert und einige Parameter ge setzt (Programm 15). Dazu werden die oben definierten Variablen verwendet. Zunächst wird allerdings eine wei tere Graphikfunktion aktiviert: der z-Buffer. Um festzustellen, welche Objekte von welchen verdeckt werden, 35 muß OpenGL die Tiefenwerte von Polygonen vergleichen. Dazu wird der z-Buffer-Algorithmus verwendet, der sehr effizient implementiert werden kann. Das Einschalten des z-Buffers oder Tiefentests erfolgt mittels glEna ble( GL_DEPTH_TEST). Für verschiedene Anwendungen sind möglicherweise verschiedene Vergleichsfunk tionen für Tiefenwerte sinnvoll. OpenGL bietet mit glDepthFunc(...) eine Möglichkeit, diese Vergleichs funktion zu spezifizieren. Hier erfolgt die Spezifikation als GL_LEQUAL, damit verdeckt ein Pixel nur dann die anderen, wenn sein Tiefenwert kleiner oder gleich den Tiefenwerten aller anderer bereits behandelter Polygone an einer Position ist. Zur vollständigen Nutzung des z-Buffer-Algorithmus ist noch ein weiterer Schritt notwen dig, der in der Display-Callback erfolgt und weiter unten beschrieben wird. In der Initialisierungsfunktion wird als nächstes mittels glShadeModel( GL_SMOOTH) Gouraud-Shading als zu verwendendes Shading-Verfahren ausgewählt. Danach wird die Beleuchtungsberechnung mittels glEnable( GL_LIGHTING) allgemein eingeschaltet. Das globale ambiente Licht wird auf ein 20%iges weiß gesetzt (LightAmbient) und danach die Position und diffuse Komponente der Lichtquelle GL_LIGHT0 eingestellt. Diese wird dann eingeschaltet. Ebenso wird mit der Lichtquelle GL_LIGHT1 verfahren. Dabei strahlt Lichtquelle 0 blaues und Lichtquelle 1 rotes Licht aus. Programm 15: Beispiel für Materialien und Lichtquellen – Initialisierung der Lichtquellen void myInit(void) { glEnable( GL_DEPTH_TEST); glDepthFunc( GL_LEQUAL); } // z-Buffer einschalten // Funktion zum Tiefenvergleich glShadeModel( GL_SMOOTH); glEnable( GL_LIGHTING); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightAmbient); // Gouraud-Shading einschalten // Beleuchtung allgemein einschalten // ambiente Beleuchtung setzen glLightfv( GL_LIGHT0, GL_POSITION, LightPosition); glLightfv( GL_LIGHT0, GL_DIFFUSE, LightDiffuse); glEnable( GL_LIGHT0); // Position der ersten LQ // Diffuse Komponente der ersten LQ // Lichtquelle einschalten glLightfv( GL_LIGHT1, GL_POSITION, Light1Position); glLightfv( GL_LIGHT1, GL_DIFFUSE, Light1Diffuse); glEnable( GL_LIGHT1); // Position der zweiten LQ // Diffuse Komponente der zweiten LQ // LQ einschalten In der Display-Callback (Programm 16) schließlich erfolgt die Zuweisung der Materialien Programm 16: Beispiel für Materialien und Lichtquellen – Display-Callback void myDisplay( void) { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Frame- und z-Buffer leeren glLoadIdentity(); gluLookAt( posX, posY, posZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); } glPushMatrix(); zeichneKoordinaten(); glRotatef( spin, 1.0, 0.0, 0.0); glMaterialfv( GL_FRONT, GL_AMBIENT, ambientTorus); glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuseTorus); glMaterialfv( GL_FRONT, GL_SPECULAR, specularTorus); glMaterialf( GL_FRONT, GL_SHININESS, 100); glutSolidTorus( 0.1, 0.5, 20, 40); glPushMatrix(); glTranslatef( direction * 0.4, 0.0, 0.0); glRotatef( (float)direction * winkel, 0.0, 1.0, 0.0); glTranslatef( direction * 0.4, 0.0, 0.0); glMaterialfv( GL_FRONT, GL_AMBIENT, ambientKugel); glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuseKugel); glMaterialfv( GL_FRONT, GL_SPECULAR, specularKugel); glMaterialf( GL_FRONT, GL_SHININESS, 50); glutSolidSphere( 0.2, 10, 10); glPopMatrix(); glPopMatrix(); glutSwapBuffers(); Die entsprechenden Parameterwerte wurden ebenfalls als globale Variablen (Programm 14) bereits definiert. Hier erfolgt auch der letzte Schritt der Verwendung des z-Buffers. Vor dem Zeichnen eines jeden Frames muß dieser 36 – genau wie der Darstellungsbereich im Fenster – gelöscht werden. Dies geschieht mit der Verknüpfung von GL_DEPTH_BUFFER_BIT in der Funktion glClear(...). Da das selbst definierte Objekt Aus Aufgabe 15 keine Normalen besitzt, wird es durch einen Torus ersetzt. Vor dem Zeichnen dieses Torus wird das Material für diesen spezifiziert. Danach wird das Material für die Kugel definiert und diese gezeichnet. Da es sich in beiden Fällen um geschlossene Objekte handelt, wird nur die Vorderseite mit Materialien versehen. In der Funktion main(...) muß nur noch die Funktion myInit() aufgerufen werden. Die Auswirkungen der verschiedenen Arten, Materialien und Farben für Geometrien zu definieren, sind bei OpenGL weit umfangrei cher und verschiedene Einstellungen führen zu einem recht komplexen Verhalten. Aufgabe 21: Beschreiben Sie ausgehend von den im obigen Beispiel verwendeten Einstellungen für Lichtquellen und Materialien, wie die Farben der Objekte in der Ausgabe zustande kommen Aufgabe 21 Aufgabe 22: Ändern Sie die Parameter der Lichtquellen und der Materialien und experimentieren Sie mit ver schiedenen Einstellungen. Aufgabe 22 Die in diesem Kapitel eingeführten Konzepte finden sich natürlich auch in JOGL. Java 13 zeigt die entsprechen den Ausschnitte der Viewer-Klasse. Die nicht angeführten Methoden stammen entweder aus den bisherigen Pro grammen oder sind leer zu lassen. 37 Java 13: Beleuchtung und Materialien public class JOGL100014 implements GLEventListener, KeyListener, MouseInputListener { /* ... */ // globale Variablen für die Beleuchtung -------------------------------private float LightAmbient[]= { 0.2f, 0.2f, 0.2f, 1.0f }; private float LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; private float LightDiffuse[] = { 0.0f, 0.0f, 1.0f, 1.0f }; private float Light1Position[]= { 0.0f, 2.0f, 0.0f, 1.0f }; private float Light1Diffuse[] = { 1.0f, 0.0f, 0.0f, 1.0f }; // globale Variablen für die Materialien -------------------------------private float ambientTorus[] = {0.2f, 0.2f, 0.2f, 1.0f}; private float diffuseTorus[] = {1.0f, 0.8f, 0.0f, 1.0f}; private float specularTorus[]= {1.0f, 1.0f, 1.0f, 1.0f}; private float ambientKugel[] = {0.19f, 0.19f, 0.19f, 1.0f}; private float diffuseKugel[] = {0.51f, 0.51f, 0.51f, 1.0f}; private float specularKugel[]= {0.51f, 0.51f, 0.51f, 1.0f}; /* ... */ @Override public void display( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear( GL.GL_COLOR_BUFFER_BIT); gl.glLoadIdentity(); glu.gluLookAt( posX, posY, posZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); gl.glPushMatrix(); zeichneKoordinaten( drawable); gl.glRotatef( spin, 1.0f, 0.0f, 0.0f); gl.glMaterialfv( GL.GL_FRONT, GL.GL_AMBIENT, ambientTorus, 0); gl.glMaterialfv( GL.GL_FRONT, GL.GL_DIFFUSE, diffuseTorus, 0); gl.glMaterialfv( GL.GL_FRONT, GL.GL_SPECULAR, specularTorus, 0); gl.glMaterialf( GL.GL_FRONT, GL.GL_SHININESS, 100); glut.glutSolidTorus( 0.1, 0.5, 20, 40); zeichneObjekt( drawable); gl.glPushMatrix(); gl.glTranslatef( direction * 0.4f, 0.0f, 0.0f); gl.glRotatef( (float)direction * winkel, 0.0f, 1.0f, 0.0f); gl.glTranslatef( direction * 0.4f, 0.0f, 0.0f); gl.glMaterialfv( GL.GL_FRONT, GL.GL_AMBIENT, ambientKugel, 0); gl.glMaterialfv( GL.GL_FRONT, GL.GL_DIFFUSE, diffuseKugel, 0); gl.glMaterialfv( GL.GL_FRONT, GL.GL_SPECULAR, specularKugel, 0); gl.glMaterialf( GL.GL_FRONT, GL.GL_SHININESS, 50); glut.glutSolidSphere( 0.2f, 10, 10); gl.glPopMatrix(); gl.glPopMatrix(); if (animationRunning) idle(); gl.glFlush(); } @Override public void init( GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glEnable( GL.GL_DEPTH_TEST); // z-Buffer einschalten gl.glDepthFunc( GL.GL_LEQUAL); // Funktion zum Tiefenvergleich gl.glShadeModel( GL.GL_SMOOTH); // Gouraud-Shading einschalten gl.glEnable( GL.GL_LIGHTING); // Beleuchtung allgemein einschalten gl.glLightModelfv( GL.GL_LIGHT_MODEL_AMBIENT, LightAmbient, 0); // ambiente gl.glLightfv( GL.GL_LIGHT0, GL.GL_POSITION, LightPosition, 0); // Position LQ 0 gl.glLightfv( GL.GL_LIGHT0, GL.GL_DIFFUSE, LightDiffuse, 0); // Diffus LQ 0 gl.glEnable( GL.GL_LIGHT0); // LQ 0 einschalten gl.glLightfv( GL.GL_LIGHT1, GL.GL_POSITION, Light1Position, 0); // Position LQ 1 gl.glLightfv( GL.GL_LIGHT1, GL.GL_DIFFUSE, Light1Diffuse, 0); // Diffus LQ 1 gl.glEnable( GL.GL_LIGHT1); // LQ 1 einschalten } /* ... */ } 38 Stichwortverzeichnis Backface-Culling.............................................35 Callback.............................................5, 7, 18, 27 Display-Callback.........................6f., 23f., 31, 36 display().............................................................8 Eclipse................................................................4 Flat-Shading.....................................................33 GL_LIGHT0..............................................34, 36 GL_LIGHT1....................................................36 GL_LIGHT7....................................................34 GL_LIGHTING...............................................33 GL_LINE_LOOP.............................................23 GL_LINE_STRIP............................................23 glBegin....................................................20f., 23 glBegin................................................................. GL_LINE_LOOP......................................21f. GL_LINE_STRIP.......................................21 GL_LINES..................................................21 GL_POINTS...............................................20 GL_POLYGON...........................................21 GL_QUAD_STRIP.....................................21 GL_QUADS................................................21 GL_TRIANGLE_FAN................................21 GL_TRIANGLE_STRIP.............................21 GL_TRIANGLES.......................................21 GLCanvas..........................................................8 glClear..........................................................7, 37 glClear.................................................................. GL_COLOR_BUFFER_BIT........................7 glColor*.....................................................21, 35 glDepthFunc.....................................................36 GL_DEPTH_BUFFER_BIT.......................37 GL_LEQUAL.............................................36 glDisable....................................................6, 33f. glDisable.............................................................. GL_LIGHTING..........................................33 GLDrawable.......................................................8 glEnable...............................................6, 33f., 36 GL_DEPTH_TEST.....................................36 GL_LIGHTING....................................33, 36 glEnd..............................................................20f. GLEventListener................................................8 glFlush............................................................7ff. glFrustum.......................................................17f. glLightf............................................................34 GL_AMBIENT...........................................34 GL_CONSTANT_ATENUATION.............34 GL_DIFFUSE.............................................34 GL_LINEAR_ATENUATION....................34 GL_POSITION...........................................34 GL_QUADRATIC_ATENUATION...........34 GL_SPECULAR.........................................34 GL_SPOT_CUTOFF..................................34 GL_SPOT_DIRECTION............................34 GL_SPOT_EXPONENT.............................34 glLightfv..........................................................34 GL_AMBIENT...........................................34 GL_CONSTANT_ATENUATION.............34 GL_DIFFUSE.............................................34 GL_LINEAR_ATENUATION....................34 GL_POSITION...........................................34 GL_QUADRATIC_ATENUATION...........34 GL_SPECULAR.........................................34 GL_SPOT_CUTOFF..................................34 GL_SPOT_DIRECTION............................34 GL_SPOT_EXPONENT.............................34 glLightModelfv................................................34 glLightModelfv.................................................... GL_LIGHT_MODEL_AMBIENT.............34 glLoadIdentity............................................12, 24 glMaterialf.......................................................35 GL_AMBIENT...........................................35 GL_AMBIENT_AND_DIFFUSE..............35 GL_DIFFUSE.............................................35 GL_EMISSION...........................................35 GL_FRONT................................................35 GL_FRONT_AND_BACK.........................35 GL_SHININESS.........................................35 GL_SPECULAR.........................................35 glMaterialfv.....................................................35 GL_AMBIENT...........................................35 GL_AMBIENT_AND_DIFFUSE..............35 GL_BACK..................................................35 GL_DIFFUSE.............................................35 GL_EMISSION...........................................35 GL_FRONT................................................35 GL_FRONT_AND_BACK.........................35 GL_SHININESS.........................................35 GL_SPECULAR.........................................35 glMatrixMode..........................................12, 16f. glMatrixMode...................................................... GL_MODELVIEW.....................................12 GL_PROJECTION...............................12, 16 39 glNormal*........................................................33 glOrtho.............................................................17 glPopMatrix.......................................15f., 24, 27 glPushMatrix.....................................15f., 24, 27 glRotate*....................................................14, 16 glScale*......................................................14, 16 glShadeModel............................................33, 36 GL_FLAT....................................................33 GL_SMOOTH.......................................33, 36 glTranslate*................................................14, 16 gluLookAt........................................12ff., 18, 32 gluPerspective..............................................4, 17 glutCreateWindow.............................................6 glutDisplayFunc.................................................6 glutIdleFunc.....................................................28 glutInit................................................................6 glutInitDisplayMode..........................................6 GLUT_DEPTH.............................................9 GLUT_DOUBLE......................................6, 9 GLUT_RGB..................................................9 GLUT_RGBA...........................................6, 9 GLUT_SINGLE............................................9 GLUT_STEREO...........................................9 glutInitWindowSize...........................................6 glutKeyboardFunc...........................................28 glutMainLoop..................................................6f. glutMotionFunc...............................................29 glutPassiveMotionFunc...................................29 GLUT_DOWN............................................29 GLUT_LEFT_BUTTON............................29 GLUT_MIDDLE_BUTTON......................29 GLUT_RIGHT_BUTTON..........................29 GLUT_UP...................................................29 glutPostRedisplay......................................23, 31 glutSolidCube..................................................16 40 glutSpecialFunc...............................................28 GLUT_KEY_F1..........................................28 GLUT_KEY_RIGHT..................................28 glutSwapBuffers............................................7, 9 glutWireSphere..................................................9 glVertex2*........................................................20 glVertex3*........................................................20 glViewport.......................................................18 Gouraud-Shading.......................................33, 36 Idle-Callback...........................................23, 27f. Java....................................................................4 JOGL..............................................................4, 7 Keyboard-Callback........................................28f. main...................................................................6 MatrixMode.....................................................12 Matrixstack......................................................15 Microsoft Visual C++........................................4 ModelView-Matrix...........................12ff., 18, 34 Motion-Callback........................................29, 31 Mouse-Callback...............................................29 MouseMotion-Event........................................31 Normalenvektor...............................................33 OpenGL Utility Library GLU............................3 OpenGL Utility Toolkit GLUT..........................3 Parallelprojektion.............................................17 PassiveMotion-Callback..................................29 perspektivische Projektion.............................17f. Projection-Matrix...................................12, 16ff. Reshape-Callback............................................18 Rotation............................................................14 Sichtkörper....................................................17f. Skalierung........................................................14 Translation.......................................................14 Viewing-Pipeline.............................................12 z-Buffer-Algorithmus.......................................36 Verzeichnis der Programme Die in diesem Lehrheft angegebenen Quellcodeausschnitte stammen aus den unten angegebenen Programmen, die auf der Webseite zur Lehrveranstaltung heruntergeladen werden können. In den C(++)-Versionen beziehen sich die mit „1“ beginnenden Quellcodedateien auf die Programme und Auf gaben, die auf die Lösung von Aufgabe 19 aufbauen. Programm Programm 2 Programm 1 Programm 3 Programm 4 Programm 5 Programm 6 Programm 7 Programm 8 Programm 9 Programm 10 Programm 11 Programm 12 Programm 13 Programm 14 Programm 15 Programm 16 Inhalt OpenGL-Rahmenprogramm Schematischer Ablauf der GLUT-Hauptschleife Einfache 3D-Ausgabe (Kugel) Anwendung der Funktion gluLookAt(...) Beispiel für glPushMatrix() und glPopMatrix() Initialisierung der synthetischen Kamera in der ReshapeCallback GL_LINES zum Zeichnen eines Koordinatensystems Erzeugen eigener Geometrien Einfache Animation Kugel-Animation Keyboard-Callback Mouse-Callback zum Ein- und Ausschalten der Animation Kamerasteuerung mit der Maus Beispiel für Materialien und Lichtquellen – Definition der Parameter Beispiel für Materialien und Lichtquellen – Initialisierung der Lichtquellen Beispiel für Materialien und Lichtquellen – Display-Callback Dateiname 00003.cpp 00004.cpp 00005.cpp 00006.cpp Seite 7 7 10 13 16 19 00007.cpp 00008.cpp 00009.cpp 00010.cpp 10011.cpp 10012.cpp 10013.cpp 10014.cpp 21 22 24 27 28 30 32 35 10014.cpp 36 10014.cpp 36 00001.cpp keine In der Java-Version bezeichnen alle mit „0“ beginnenden Quellcodedateien solche Viewer-Klassen, die in JOGL Main.java eingebunden werden können, alle mit „1“ beginnenden Klassen arbeiten mit JOGLAniMain.java zusammen. Programm Java 1 Java 2 Java 3 Java 4 Java 5 Java 6 Java 7 Java 8 Java 9 Java 10 Java 11 Java 12 n/a Java 13 Inhalt Rahmenprogramm für eine JOGL-Anwendung (ohne ViewerKlasse) Viewer-Klasse für das äquivalente Beispiel zu Programm 2 Kugel als GLUT-Funktion Kameraeinstellungen mit gluLookAt() Beispiel für glPushMatrix() und glPopMatrix() – nur die display()-Methode Initialisieren der synthetischen Kamera in der reshape()Methode zeichneKoordinaten() mit JOGL Neues Hauptprogramm für Animationen Viewer-Klasse für die einfache Animation Kugelanimation wie in Programm 10 Auswerten von Tastaturereignissen Maus- und Tastatureingabe (äquivalent zu Programm 12) Kamerasteuerung mit der Maus Beleuchtung und Materialien Dateiname JOGLMain.java Seite 8 JOGL0001.java JOGL0002.java JOGL0003.java JOGL0004.java 9 10 14 16 JOGL0005.java 20 JOGL0006.java JOGLAniMain.java JOGL1007.java JOGL1008.java JOGL1009.java JOGL10010.java JOGL10011.java JOGL10012.java 22 25 26 27 29 31 38 41