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