Grafisches Java - Java OpenGL - Technische Universität München

Werbung
Java für Fortgeschrittene
Proseminar Sommersemester 2009
Grafisches Java - Java OpenGL
Ludwig Nägele
Technische Universität München
10. Mai 2009
1
Inhaltsverzeichnis
1 Zusammenfassung
3
2 Einleitung
2.1 Vorläufer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Entstehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3
4
4
3 Verwendung
3.1 Installation . . . . . . . . . . . . . . .
3.2 Überblick . . . . . . . . . . . . . . . .
3.3 Klassenaufbau . . . . . . . . . . . . . .
3.3.1 GLCanvas . . . . . . . . . . . .
3.3.2 GL . . . . . . . . . . . . . . . .
3.3.3 GLU . . . . . . . . . . . . . . .
3.3.4 GLEventListener . . . . . . . .
3.4 Vorgehensweise anhand eines Beispiels
3.5 Objektverwaltung . . . . . . . . . . . .
3.6 Perspektive / Kamera . . . . . . . . .
3.7 Animation . . . . . . . . . . . . . . . .
3.8 Beleuchtung . . . . . . . . . . . . . . .
3.9 Materialien . . . . . . . . . . . . . . .
3.10 Eingabeverarbeitung . . . . . . . . . .
3.11 Einlesen von externen 3D-Objekten . .
3.12 JOGLAppletLauncher . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
6
7
7
8
8
9
11
13
14
15
16
17
17
17
4 Demoprojekt
18
5 Fazit
18
6 Literatur
19
2
1 Zusammenfassung
Dieser Artikel gibt einen kurzen Einblick in die wichtigsten Funktionsweisen und Möglichkeiten
der Programmbibliothek JOGL. Die Anwendung dieser Programmbibliothek wird anhand
eines durchgehenden Beispiels verdeutlicht. Detailliertere und weiterführende Informationen sind unter Anderem unter den im Quellenverzeichnis angegebenen Links nachzulesen.
2 Einleitung
Ein Computerspiel der heutigen Zeit hat nur dann eine auf
dem Markt Erfolg versprechende Chance, wenn es neben einer spannenden Handlung unter Anderem auch eine ansprechende und atemberaubende Grafik bietet – natürlich in 3D.
Ebenso wie CAD-Programme zur Herstellung von dreidimensionalen Werkstücken in der Industrie, benötigen auch
Echtzeitsimulationsprogramme eine schnelle und zuverlässige
Berechnungsmöglichkeit. Da dies mit dem Prozessor eines
Heimrechners nicht ruckelfrei zu bewerkstelligen wäre, nutzt man hierbei zusätzlich die
Rechenkapazität der Grafikkarte. In Java wird dem Entwickler mit JOGL eine Schnittstelle zu eben dieser angeboten.
Mit JOGL (Java OpenGL) wurde eine externe OpenGL-Programmbibliothek für Java entwickelt, mithilfe derer ein Programmierer auf OpenGL-Funktionen zugreifen kann. Hierzu
werden spezielle Java-Wrapperklassen bereitgestellt, die die Schnittstellen zu den nativen
Funktionen von OpenGL bereitstellen. Die angebotenen Methoden führen dabei in der
Regel einfach den korrespondierenden nativen C-Code aus.
OpenGL ist eine plattformunabhängige Bibliothek, wohingegen das bekannte DirectX als
Microsoftprodukt nur auf Windowssystemen läuft. Java bietet mit Java3D neben JOGL
noch eine alternative Grafik-Entwicklerschnittstelle an, die beide Bibliotheken unterstützt.
Ihre Objektstruktur abstrahiert von den zugrunde liegenden Schnittstellenmethoden und
ist daher in der Verwendung verschieden von JOGL.
2.1 Vorläufer
Mangels Unterstützung von OpenGL konnte sich Java in den ersten Jahren nach Erscheinung 1995 speziell in der Spielindustrie nicht durchsetzen. Einige Entwickler erkannten das
Problem, was zu der Entwicklung von sogenannten OpenGL-Bindings führte: gl4java [5]
(OpenGL for Java Technology), LWJGL [6] (Lightweight Java Game Library) und Magician [7]. Diese drei Bindings wiesen jedoch einige Probleme auf, die ihre Durchsetzungskraft hemmten. So unterstützt gl4java OpenGL nur bis Version 1.3, die Benutzung neuer
3
I/O-Geräte wird nur beschränkt gewährleistet und die umfangreiche API macht es Programmierern schwer, den Einstieg zu finden. Während LWJGL zwar neueste I/O-Geräte
unterstützt und auch für die aktuellste OpenGL Version geeignet ist, liegt der Hauptnachteil in der nicht zulässigen Einbindung von AWT oder Swing Komponenten. Das dritte
Binding Magician löst zwar das Problem der AWT und Swing Einbindung, jedoch wird es
weder weiterentwickelt, noch unterstützt es neue I/O-Geräte. Ein weiterer Nachteil liegt
darin, dass Magician nie OpenSource war.
Die erste wirklich vielversprechende Entwicklung stellte das im August 2003 veröffentlichte
JOGL dar.
2.2 Entstehung
Begonnen wurde die Programmbibliothek ursprünglich von Kenneth Russell (Sun Microsystems) und Chris Kline (Irrational Games) unter dem Namen Jungle. Dabei wurde versucht,
die Vorteile der vorhergehenden Bindings zu kombinieren. Mittlerweile wird sie aber von
der Game Technology Group, welche zu Sun Microsystems gehört, als Open Source ständig
weiterentwickelt und verbessert. Es ist davon auszugehen, dass JOGL in eine der späteren
Java-Versionen standardmäßig integriert wird.
Die ersten Versionen von JOGL arbeiteten im Package net.java.games.jogl. Dieses hat
sich aber nach dem Übergang zu den JSR-231 APIs zu javax.media.jogl geändert. Leider greifen jedoch noch sehr viele Tutorials und Informationsseiten im Internet auf die alte
JOGL-Version zurück, die auch in der Klassen- und Methodenstrucktur einige Änderungen
aufweist. Beispielsweise wurden einige Klassen (Animator ) aus der neueren Version herausgenommen, oder sogar in ihrer Erzeugung verändert: Die Klasse GLU ist nun nicht
mehr über eine Factory zu erzeugen, sondern direkt über new() instanziierbar.
2.3 Überblick
In den nachfolgenden Kapiteln wird zunächst auf vier Klassen von JOGL eingegangen.
Um einen kleinen Einblick in deren Funktionalitäten und Anwendungsweise zu gewinnen,
erzeugen wir anhand eines Beispiels eine einfache JOGL-Ausgabe. Wir werden verstehen,
wie man auch komplexere 3D-Strukturen darstellt und diese über eine Kamera aus einer
räumlichen Perspektive betrachten kann. Außerdem sind die Verwendung von Lichtquellen
und ein kleines Animationsbeispiel Thema dieses Artikels.
4
3 Verwendung
3.1 Installation
Da JOGL noch nicht standardmäßig in Java integriert ist, muss man zunächst die Programmbibliothek auf dem Rechner installieren, bevor man sie verwenden kann.
1. Herunterladen der OS-spezifischen Binaries von https://jogl.dev.java.net/
2. Einbinden der Archive jogl.jar und gluegen-rt.jar in den Buildpath des Projekts
3. Speichern der nativen Dateien in einem der in java.library.path angegebenen Pfade
JOGL unterstützt die gängigen Plattformen, auf denen J2SE 1.4 oder höher läuft.
3.2 Überblick
OpenGL (Open Graphics Library) ist eine Programmierschnittstelle zur Entwicklung von
3D-Computergrafik unabhängig von Plattform oder Programmiersprache. Sie beinhaltet
etwa 250 Befehle, die die Darstellung komplexer 3D-Szenen in Echtzeit erlauben. Zudem
können eigene Erweiterungen (beispielsweise von Grafikkartenherstellern) definiert werden.
JOGL ermöglicht die Verwendung dieser Befehle in Java. Hierzu wurden sämtliche Methoden direkt übernommen, die lediglich die korrespondierenden C-Implementierungen aufrufen. Aus diesem Grund gibt es auch mehrere ähnlichnamige Funktionen, die sich lediglich
im Parameter-Typ unterscheiden (vgl. glVertex3f() und glVertex3d()).
Einige Begriffe werden in den folgenden Kapiteln immer wieder fallen und tragen auch zum
Verständnis von JOGL bei:
Viewport beschreibt den sichtbaren Ausschnitt einer Umgebung.
Primitive sind einfache Objekte, die sich oft zu komplexen 3D-Strukturen kombinieren
lassen.
Matrizen dienen in JOGL als Berechnungsgrundlage. Es wird zwischen mehreren Matrizen unterschieden: Auf der Modelview-Matrix werden alle 3D-Objekte und Szenen abelegt, die Projektions-Matrix bestimmt die räumliche Position des Viewports
(Standort des Betrachters), und wird deshalb auch Kamera genannt.
Kamera ist eine Umschreibung für eine modifizierte Projektions-Matrix.
Licht spielt in 3D-Anwendungen eine große Rolle. Lichtquellen können selbst definiert werden, wodurch der Umgebung ein dreidimensionales Aussehen verliehen werden kann.
Standardmäßig sind alle Objekte mit einem gleichmäßigen diffusen Licht beleuchtet.
Material bestimmt die Oberflächenbeschaffenheit von Objekten. Dies kann einfach eine
Farbe oder auch eine Grafik sein. Zudem können Einstellungen bezüglich der Reflexion von Licht vorgenommen werden.
5
3.3 Klassenaufbau
Die Funktionen der verschiedenen OpenGL-Bibliotheken werden in JOGL durch Objekte
aufgerufen. Dies geschieht hauptsächlich mit dem Interface GL, das die Erstellung komplexer 3D-Szenen ermöglicht, und der Klasse GLU, die unter Anderem die Möglichkeiten
der Kameraeinstellungen bietet. Die Referenz auf das GL-Objekt erhält man über das verwendete GLDrawable, auf dem das Rendering erfolgt (z.B. GLCanvas), wohingegen das
GLU -Objekt mit new() gebildet werden muss. Dem Konstruktor von GLCanvas kann
optional ein GLCapabilities-Objekt übergeben werden, über das die Parameter für das
Rendering festgelegt werden, z.B. das Ein- oder Ausschalten der Hardwarebeschleunigung
oder des Double-Bufferings.
Bildlich kann man sich die Funktion der Basisklassen wie folgt vorstellen: Über das GLInterface wird die Szene aufgebaut, GLU bestimmt die Sichtweise. Die Verwaltung über
diese beiden Klassen übernimmt ein GLEventListener. Wahrgenommen“ werden kann das
”
fertige Bild über ein GLDrawable-Objekt (GLCanvas).
Diese vier Klassen samt ihrer interessantesten Methoden seien auf den folgenden Seiten
kurz erwähnt. Interessierte Leser können weitere Informationen in der Dokumentation [4]
nachschlagen.
6
3.3.1 GLCanvas
Die Klasse GLCanvas implementiert das Interface GLDrawable und leitet von Canvas ab.
Sie ist eine AWT-Komponente, die Hardware-Beschleunigung unterstützt. Auf ihr kann die
3D-Welt angezeigt werden.
Über die Methode addGLEventListener() weist man dem
GLCanvas eine Klasse zu, die Ereignisse wie Größenänderungen
behandelt.
Über getGL() bekommt man das GL-Objekt, auf dem man
arbeiten kann (vgl. getGraphics() bei Applets).
Alternativ kann das Swing-kompatible GLJPanel verwendet werden, falls GLCanvas nicht
benutzt werden kann. Allerdings gewährleistet diese Klasse keine Hardwareunterstützung.
3.3.2 GL
Das Interface GL stellt die Schnitstelle dar, mit der die 3D-Welt modelliert wird.
Über glVertex3f() werden Eckpunkte erstellt, die – wenn
mehrere von ihnen innerhalb eines glBegin()/glEnd()-Paares
stehen – zu einem Objekt gruppiert werden. glBegin() wird
dabei ein einzelnes Argument übergeben, das spezifiziert, wie
die Eckpunkte interpretiert werden:
Beispielsweise zeichnet
GL TRIANGLE STRIP ein ausgefülltes Dreieck. Mit
GL QUAD STRIP lässt sich ein ausgefülltes Viereck erzeugen.
Mittels glColor3f() kann die aktuelle Zeichenfarbe in RGB
(rot-grün-blau) gesetzt werden.
glMatrixMode() aktiviert die Matrix, auf der gearbeitet werden soll. Hierbei wird in unserem Beispiel unterschieden in:
GL MODELVIEW: Auf dieser Matrix wird die 3D-Welt aufgebaut.
GL PROJECTION: Diese Matrix wird für Projektionseigenschaften wie Kameraperspektive verwendet.
Darüber hinaus gibt es noch GL TEXTURE und GL COLOR.
Über glRotatef() können wir die aktuelle Matrix manipulieren und auf alle nachfolgenden
Eingaben eine Rotation bewirken. glTranslatef() hingegen versetzt den Ursprung und hat
eine Verschiebung aller nachfolgenden Eingaben zufolge.
Mit glLoadIdentity() können alle Modifikationen auf der aktiven Matrix zurückgesetzt und
die ursprüngliche Matrix wieder hergestellt werden. glClear() leert den Bitpuffer.
7
Mit glEnable() lassen sich GL-Eigenschaften aktivieren. Ein Beispiel hierfür sehen wir in
einem späteren Kapitel.
3.3.3 GLU
Die Klasse GLU (GL Utilities) bietet in erster Linie die Verwaltung der Kameraeinstellung und stellt darüber hinaus erweiterte Zeichenfunktionen zur Klasse GL zur Verfügung.
In unserem Beispiel gehen wir lediglich auf die beiden Funktionen gluPerspective() und gluLookAt() ein. Hierüber können
die Perspektiveigenschaften und der Betrachterstandort festgelegt werden.
3.3.4 GLEventListener
Der GLEventListener ist ein Interface, das vom Anwender implementiert werden muss. Es
setzt folgende Methoden zur Behandlung der Ereignisse des GLCanvas voraus:
Die init()-Methode wird zu Beginn aufgerufen. An dieser
Stelle können allgemeine Parameter zum Rendern der Szene
eingestellt werden.
Die Methoden displayChanged() und reshape() behandeln
Änderungen der Bildschirmauflösung oder der Fenstergröße
sowie das Verschieben des Fensters.
Die Methode display() wird immer dann aufgerufen, wenn
tatsächlich gerendert werden soll. In ihr werden u.a. die zu
zeichnenden Elemente der Szene beschrieben.
Eine Animation erreicht man, indem diese Methode in regelmäßigen Abständen ausgeführt
wird, und die Szene jeweils in Abhängigkeit von internen Variablen gerendert wird. Dies
kann ein externer Thread übernehmen, der über den Aufruf repaint() des Canvas-Objekts
implizit die Ausführung der Methode display() bewirkt. Eine dynamische Änderung dieser
Variablen hat dann eine Bewegung in der Darstellung zufolge.
8
3.4 Vorgehensweise anhand eines Beispiels
Wir möchten nun eine einfache Ausgabe mittels JOGL erstellen. Hierzu implementieren wir zwei Klassen. Der gesamte Code des hier aufgebauten Beispiels ist unter www.
ludwig-naegele.de/tum/jogl/jogl.zip downloadbar.
Wir erstellen zunächst ein Frame (MyFrame), auf dem wir eine JOGL-Ausgabe (MyGLEventListener ) darstellen möchten.
public c l a s s MyFrame extends Frame {
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
Frame frame = new Frame ( ”Mein e r s t e s JOGL−P r o j e k t ” ) ;
f i n a l GLCanvas c a n v a s = new GLCanvas ( ) ;
MyGlEventListener l i s t e n e r = new MyGLEventListener ( ) ;
c a n v a s . addGLEventListener ( l i s t e n e r ) ;
frame . add ( c a n v a s ) ;
frame . s e t S i z e ( 4 0 0 , 3 0 0 ) ;
frame . addWindowListener (new WindowAdapter ( ) {
public void windowClosing ( WindowEvent e ) {
System . e x i t ( 0 ) ;
}
});
frame . s e t V i s i b l e ( true ) ;
}
}
Mit dem Aufruf new GLCanvas() erzeugen wir eine neue Zeichenfläche, auf der die Ausgabe
gezeichnet werden soll. In Java werden Eingaben des Benutzers, z.B. über I/O-Geräte,
durch das sogenannte Event-Listener-Modell abgefangen und verarbeitet. Tritt auf der
Zeichenfläche ein Ereignis ein, so soll dieses von einem EventListener verarbeitet werden.
Die Zuordnung legen wir über den Aufruf von addGLEventListener() fest. Wird nun das
Canvas geladen oder in der Größe verändert, reagiert MyGLEventListener darauf und baut
die Zeichenfläche neu auf. Letztendlich wollen wir die Zeichenfläche auch sehen können,
und fügen sie noch dem Frame hinzu.
Beginnen wir mit der Implementierung von MyGLEventListener.
public c l a s s MyGLEventListener implements GLEventListener {
public void d i s p l a y ( GLAutoDrawable d r a w a b l e ) {
f i n a l GL g l = d r a w a b l e . getGL ( ) ;
g l . g l B e g i n (GL. GL TRIANGLES ) ;
gl . glColor3f (1.0 f , 0.0 f , 0.0 f ) ;
gl . glVertex3f (0.0 f , 0.5 f , 0.0 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , 0 . 0 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , 0 . 0 f ) ;
g l . glEnd ( ) ;
}
...
}
9
Diese Klasse soll auf Ereignisse der Zeichenfläche reagieren können und implementiert daher
das Interface GLEventListener. Für unser Beispiel muss zunächst lediglich die Funktion
display() angepasst werden, die anderen Methoden werden leer implementiert.
Mittels getGL() erhalten wir das GL-Objekt der übergebenen GLAutoDrawable-Instanz.
Auf diesem GL-Objekt beginnen wir nun eine neue Gruppe. Der Parameter GL TRIANGLES
bedeutet hierbei, dass gezeichnete Punkte zu einem Dreieck zusammengeführt werden.
Ebenso wäre beispielsweise GL QUADS für ein Viereck möglich.
Mit glColor3f() setzen wir die Zeichenfarbe in RGB. Nun erstellen wir drei Punkte glVertex3f(), die innehrhalb der Gruppierung als ausgefülltes Dreieck abgebildet werden.
Als Ergebnis erhalten wir ein Fenster mit einem roten Dreieck.
Verändern wir das Fenster in seiner Größe, so fällt uns auf,
dass das Dreieck verzerrt dargestellt wird. Das liegt daran,
dass JOGL keine Information vorliegt, in welcher Proportion
das gerenderte Bild später einmal angezeigt werden soll. Zur
Behebung dieses Problems eignet sich die Methode reshape(),
in der JOGL über die neue Zeichenflächengröße benachrichtigt wird.
Passen wir also die Methode reshape() der Klasse MyGLEventListener an:
public void r e s h a p e ( GLAutoDrawable drawable , i n t x , i n t y , i n t width , i n t h e i g h t ) {
f i n a l GL g l = d r a w a b l e . getGL ( ) ;
int s i z e = height ;
i f ( width<h e i g h t ) s i z e = width ;
g l . g l V i e w p o r t ( ( width−s i z e ) / 2 , ( h e i g h t −s i z e ) / 2 , s i z e , s i z e ) ;
}
Mit dem Aufruf von glViewport() kann bestimmt werden, wie das gerenderte Bild in das
Canvas gezeichnet wird. In unserem Beispiel stellen wir eine quadratische Ausgabe (grüner
Rahmen) mit Seitenlänge size in einem rechteckigen Fenster dar.
10
3.5 Objektverwaltung
Wird ein Ereignis ausgelöst - zum Beispiel die Maus bewegt - so soll die 3D-Welt darauf
reagieren. Beispielsweise soll ein Würfel in Richtung der Mausbewebung rotieren oder eine
Spielfigur einer gedrückten Pfeiltaste nachlaufen. Die klassische Vorstellung eines JavaEntwicklers wäre: Man besitzt eine Referenz auf ein Java-Objekt, das jeweils ein 3D-Objekt
repräsentiert. Über spezifische Methoden kann man so die Ausgabe modifizieren.
Doch JOGL bietet diese Objektverwaltung nicht. Da die gesamte Ausgabe ausschließlich über parametrisierte Methodenaufrufe der Klasse GL zusammengestellt wird, erhält
man keine Referenzen, auf die man später zurückgreifen könnte. Tatsächlich muss für jedes einzelne Bild die gesamte Szenerie neu aufgebaut und durchgerechnet werden, bevor
sie gerendert werden kann. Um dennoch das Programm nicht nur möglichst objektorientiert, sondern auch übersichtlich zu gestalten, lagert man üblicherweise die Generierung
sämtlicher 3D-Objekte in sogenannte Erzeugermethoden aus. Als Parameter können OffsetWerte übergeben werden, die je nach Anforderung einen Verschiebungs-, Rotations- oder
auch Verzerrungsgrad bestimmen.
Um nun unser Beispiel konform dieser Richtlinie zu halten, würden wir unsere Klasse
MyGLEventListener anpassen und das Erzeugen des Dreiecks in eine neue Methode drawTriangle() auslagern.
Damit die nachfolgenden Kapitel leichter nachzuvollziehen sind, werden wir an dieser Stelle
das Dreieck durch einen Würfel ersetzen, der in der Methode drawCube() generiert wird.
11
private void drawCube (GL g l ) {
g l . g l B e g i n (GL. GL QUAD STRIP ) ;
g l . g l N o r m a l 3 f ( −1.0 f , 0 . 0 f , 0 . 0 f ) ;
gl . glColor3f (1.0 f , 0.0 f , 1.0 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , 0 . 5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , 0 . 5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , 0 . 5 f , 0 . 5 f ) ;
g l . glEnd ( ) ;
g l . g l B e g i n (GL. GL QUAD STRIP ) ;
g l . g l N o r m a l 3 f ( 0 . 0 f , −1.0 f , 0 . 0 f ) ;
gl . glColor3f (0.0 f , 1.0 f , 1.0 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , 0 . 5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , 0 . 5 f ) ;
g l . glEnd ( ) ;
g l . g l B e g i n (GL. GL QUAD STRIP ) ;
g l . glNormal3f ( 1 . 0 f , 0.0 f , 0.0 f ) ;
gl . glColor3f (0.0 f , 1.0 f , 0.0 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , 0 . 5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , 0 . 5 f ) ;
gl . glVertex3f (0.5 f , 0.5 f , 0.5 f ) ;
g l . glEnd ( ) ;
g l . g l B e g i n (GL. GL QUAD STRIP ) ;
g l . glNormal3f ( 0 . 0 f , 0.0 f , 1.0 f ) ;
gl . glColor3f (1.0 f , 1.0 f , 0.0 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , 0 . 5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , 0 . 5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , 0 . 5 f , 0 . 5 f ) ;
gl . glVertex3f (0.5 f , 0.5 f , 0.5 f ) ;
g l . glEnd ( ) ;
g l . g l B e g i n (GL. GL QUAD STRIP ) ;
g l . glNormal3f ( 0 . 0 f , 1.0 f , 0.0 f ) ;
gl . glColor3f (1.0 f , 0.0 f , 0.0 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , 0 . 5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , 0 . 5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , 0 . 5 f , 0 . 5 f ) ;
gl . glVertex3f (0.5 f , 0.5 f , 0.5 f ) ;
g l . glEnd ( ) ;
g l . g l B e g i n (GL. GL QUAD STRIP ) ;
g l . g l C o l o r 3 f ( 0 . 0 f , 0 . 0 f , −1.0 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , −0.5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , −0.5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( −0.5 f , 0 . 5 f , −0.5 f ) ;
g l . g l V e r t e x 3 f ( 0 . 5 f , 0 . 5 f , −0.5 f ) ;
g l . glEnd ( ) ;
}
In der display()-Methode rufen wir lediglich noch die Erzeugerfunktion auf:
public void d i s p l a y ( GLAutoDrawable d r a w a b l e ) {
f i n a l GL g l = d r a w a b l e . getGL ( ) ;
drawCube ( g l ) ;
}
12
3.6 Perspektive / Kamera
Führen wir das Programm nun aus, so sehen wir lediglich
ein Viereck. Das liegt daran, dass der Betrachter von vorne
senkrecht auf eine Seite des Würfels blickt.
Interessanter wird es, wenn man die Perspektive so ändert,
dass der Würfel im Raum zu liegen scheint. In JOGL lässt
sich dies als sogenannte Kameraeinstellung beliebig anpassen.
Erstellen wir also nun eine solche Kameradefinition.
public void i n i t ( GLAutoDrawable d r a w a b l e ) {
GL g l = d r a w a b l e . getGL ( ) ;
GLU g l u = new GLU ( ) ;
g l . glMatrixMode (GL. GL PROJECTION ) ;
gl . glLoadIdentity ( ) ;
glu . gluPerspective (45.0 f , 1.0 f , 0.1 f , 100.0 f ) ;
g l u . gluLookAt ( 1 . 0 f , 1 . 5 f , 2 . 0 f ,
0.0 f , 0.0 f , 0.0 f ,
0.0 f , 1.0 f , 0.0 f ) ;
g l . glMatrixMode (GL.GL MODELVIEW) ;
gl . glLoadIdentity ( ) ;
}
Die verschiedenen Matrizen, auf die OpenGL aufbaut, können über glMatrixMode() aktiviert werden. Für die Kameraeinstellungen wird der GL PROJECTION Matrix Stack
verwendet. Die GL MODELVIEW Matrix hingegen findet bei der Erstellung der 3D-Welt
Anwendung.
Die Methode gluPerspective() definiert eine perspektivische Projektion, um sicherzustellen,
dass Objekte nach ihrer Position im Raum perspektivisch verzerrt werden. Der Methode
wird zuerst der Öffnungswinkel der Kamera übergeben, dann das Verhältnis zwischen Höhe
und Breite des Viewing-Volumens, sowie die Punkte, ab wann im Raum Objekte angezeigt
werden beziehungsweise ab welcher Entfernung Objekte nicht mehr zu sehen sein sollen.
Als Ergebnis erhalten wir eine räumliche Abbildung des farbigen Würfels. Doch scheint hier etwas nicht zu stimmen:
JOGL zeigt alle Objekte in der Einfügereihenfolge an. Dies
hat zur Folge, dass das zuletzt eingefügte Quadrat ganz oben
erscheint, obwohl es eigentlich von den weiter vorne liegenden Seiten des Würfels verdeckt werden müsste.
Dieser Fehler lässt sich durch das Einschalten der Tiefenunterstützung beheben, d.h. durch Hinzufügen folgender Zeile
in der init()-Methode:
g l . g l E n a b l e (GL. GL DEPTH TEST ) ;
13
In der display()-Methode fügen wir hinzu:
g l . g l C l e a r (GL. GL COLOR BUFFER BIT | GL. GL DEPTH BUFFER BIT ) ;
Der Würfel wird nun richtig angezeigt.
Im nächsten Schritt wollen wir den Würfel animieren.
3.7 Animation
Unser 3D-Würfel wirkt noch etwas statisch. Wir möchten ihn nun um eine Achse rotieren
lassen. Die Idee dabei ist, nach einer bestimmten Zeitspanne jeweils die Methode repaint()
des GLCanvas aufzurufen. Dadurch wird die Methode display() aktiviert, die nach jedem
Aufruf den Würfel anders darstellt. Implementieren wir also einen Animator-Thread!
Am Ende der main()-Methode in der Klasse MyFrame erstellen wir einen neuen Thread,
der kontinuierlich die Zeichenfläche auffrischt, und starten ihn anschließend.
new Thread ( ) {
public void run ( ) {
while ( true ) {
canvas . r e p a i n t ( ) ;
try { Thread . s l e e p ( 2 0 ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) {}
}
}
}. start ( ) ;
In der Klasse MyGLEventListener definieren wir zwei neue Variablen:
private f l o a t r o t a t i o n = 0 . 0 f ;
private f l o a t d e l t a = 1 . 0 f ;
rotation bestimmt den derzeitigen Rotationswinkel und delta gibt an, um wieviel der Würfel
gedreht wird. In der Methode display() fügen wir vor dem Aufruf von drawCube() folgenden
Codeschnipsel ein:
gl . glLoadIdentity ( ) ;
gl . glRotatef ( rotation , 1.0 f , 1.0 f , 0.0 f ) ;
r o t a t i o n += d e l t a ;
Durch das Manipulieren der Modell-Matrix bewirken wir eine Rotation um den Winkel
rotation in x- und y-Richtung. Anschließend wird der Rotationswinkel erhöht.
14
Der Würfel dreht sich! Noch interessanter können wir die Szene darstellen, wenn wir mit
eigenen Lichtquellen arbeiten, die den Würfel aus einem bestimmten Winkel beleuchten.
Wie man das umsetzen kann, wird im nächsten Kapitel beschrieben.
3.8 Beleuchtung
JOGL unterscheidet drei Arten von Licht:
• Der ambiente (ungerichtete) Anteil der Lichtquelle ist diffuses Licht, das aus allen
Richtungen zu kommen scheint und aufgrund dessen sich die Lichtquelle nicht mehr
lokalisieren lässt. Wird ein 3D-Körper ausschließlich mit ambientem Licht beleuchtet,
dann erscheint dieser aufgrund der Lichtstreuung zweidimensional.
• Der diffuse Anteil kommt aus der Richtung der Lichtquelle und wird von einer angestrahlten Oberfläche in alle Richtungen gleichmäßig reflektiert. Objekte erscheinen
unabhängig von der Position des Betrachters gleichmäßig hell.
• Der spiegelnde Lichtanteil kommt ebenfalls aus nur einer Richtung, wird aber an
der getroffenen Oberfläche in nur eine bestimmte Richtung reflektiert. Bei geeigneter
Wahl des Betrachterstandortes erscheint so ein Glanzlicht.
Für die Berechnung eben solcher Reflexionen benötigt JOGL
Informationen über den Reflexionswinkel. Dieser wird über
Normalenvektoren angegeben, die die relative Position der
Objekte zur Lichtquelle bestimmen. Diese Angabe geschieht
über den Methodenaufruf glNormal3f(), der im Codebeispiel
der Erzeugerfunktion drawCube() bereits implementiert wurde.
Es gibt drei Arten von Lichtquellen:
Richtungslicht kommt von einer unendlich weit entfernten Lichtquelle und strahlt in eine
bestimmte Richtung (ideal als Sonne).
Punktlicht hingegen beschreibt eine Lichtquelle innerhalb der Szene, die in alle Richtungen
gleichmässig strahlt.
Spotlicht ist ein Punktlicht, bei dem der Abstrahlwinkel eingeschränkt ist, und somit
einen Lichtkegel darstellt.
Um nun ein Richtungslicht in JOGL einzufügen, setzen wir in der init()-Methode die
Lichtanteile für Licht0 und aktivieren dieses anschließend. Ebenso muss über den Aufruf
von glEnable(GL.GL LIGHTING) die Verwendung von Licht aktiviert werden.
float
float
float
float
[]
[]
[]
[]
LightPos
LightAmb
LightDif
LightSpc
=
=
=
=
{0.0 f
{1.0 f
{1.0 f
{1.0 f
,
,
,
,
5.0 f
1.0 f
1.0 f
1.0 f
,
,
,
,
10.0 f
1.0 f ,
1.0 f ,
1.0 f ,
, 0.0 f };
1.0 f };
1.0 f };
1.0 f };
15
gl
gl
gl
gl
.
.
.
.
g l L i g h t f v (GL. GL
g l L i g h t f v (GL. GL
g l L i g h t f v (GL. GL
g l L i g h t f v (GL. GL
LIGHT0 ,
LIGHT0 ,
LIGHT0 ,
LIGHT0 ,
GL. GL POSITION , LightPos
GL. GL AMBIENT, LightAmb ,
GL. GL DIFFUSE , L i g h t D i f ,
GL. GL SPECULAR, LightSpc
, 0);
0);
0);
, 0);
g l . g l E n a b l e (GL. GL LIGHT0 ) ;
g l . g l E n a b l e (GL. GL LIGHTING ) ;
Führen wir unseren Quellcode aus, sehen wir das Ergebnis
dieses Kapitels: Die Flächen des rotierenden Würfels wechseln abhängig vom Neigungswinkel ihre Helligkeit. Doch man
bemerkt auch, dass der Würfel nicht mehr farbig dargestellt
wird. Im Licht-Modus ignoriert JOGL alle Angabe von glColor3f(), stattdessen muss man hier Materialien verwenden.
3.9 Materialien
Materialien setzen sich ebenso wie Licht aus einem ambienten, einem diffusen und einem
spiegelnden Anteil zusammen. Ausserdem kann über einen Spiegelungsexponenten festgelegt werden, wie glänzend das Material dargestellt werden soll. Je größer der Spiegelungsexponent, desto schärfer und kleiner wird das Highlight auf der Oberfläche dargestellt.
Daneben kann die Emissionsfarbe der Oberfläche festgelegt werden, d.h. in welcher Farbe
die Oberfläche Licht abstrahlt.
Definieren wir nun für unseren Würfel Materialeigenschaften, die wir über die Methode
glMaterialfv() festlegen. Dabei werden die relevanten Seite des Objekts, der Lichttyp, sowie
die Emissionsfarbe als RGBA-Wert übergeben. RGBA ist hierbei ein RGB-Wert, dem noch
ein Alpha-Wert (Transparenz) angehängt wird.
float
float
float
float
gl
gl
gl
gl
.
.
.
.
mat
mat
mat
mat
ambient [ ] = { 0 . 2 4 f , 0 . 1 9 f , 0 . 0 7 f , 1 . 0 f } ;
diffuse [ ] = {0.75 f , 0.60 f , 0.22 f , 1.0 f };
specular [ ] = {0.62 f , 0.55 f , 0.36 f , 1.0 f };
shininess = 51.2 f ;
g l M a t e r i a l f v (GL. GL FRONT, GL. GL AMBIENT, mat ambient , 0 ) ;
g l M a t e r i a l f v (GL. GL FRONT, GL. GL DIFFUSE , m a t d i f f u s e , 0 ) ;
g l M a t e r i a l f v (GL. GL FRONT, GL. GL SPECULAR, m a t s p e c u l a r , 0 ) ;
g l M a t e r i a l f (GL. GL FRONT, GL. GL SHININESS , m a t s h i n i n e s s ) ;
Erweitern wir die Funktion drawCube() zu Beginn mit obigem Codeschnipsel, werden alle nachfolgend erstellten Quadrate mit dieser Materialeigenschaft ausgestattet.
Der Würfel erstrahlt in einem spiegelnden Gold.
16
3.10 Eingabeverarbeitung
Wichtig für 3D-Spiele ist, dass sie auf Maus- oder Tastatureingaben des Anwenders reagieren können. Wir wollen nun erreichen, dass unser Würfel auf Tastaturereignisse reagiert.
Dazu werden wir den MyGLEventListener um das Interface KeyListener erweitern, der
die zu implementierenden Methoden vorgibt.
In der Klasse MyFrame ordnen wir dem Canvas bereits einen GLEventListener zu, der auf
Fensterereignisse reagiert. An dieser Stelle definieren wir analog den KeyListener :
canvas . addKeyListener ( l i s t e n e r ) ;
In der letzten Zeile der main()-Methode legen wir den Fokus auf die Zeichenfläche, so dass
nach dem Laden des Fensters unsere Tastatureingaben sofort verarbeitet werden können:
canvas . requestFocus ( ) ;
Die Klassendefinition des MyGLEventListener ändern wir wie folgt ab:
public c l a s s MyGLEventListener implements GLEventListener , K e y L i s t e n e r {
Jetzt müssen wir noch die durch das Interface KeyListener vorgegebenen Methoden implementieren. Hierzu erweitern wir die Klasse MyGLEventListener um folgende Methode:
@Override
public void k e y P r e s s e d ( KeyEvent a r g 0 ) {
i f ( a r g 0 . getKeyCode ()==38) s p e e d += 0 . 1 f ;
e l s e i f ( a r g 0 . getKeyCode ()==40) {
s p e e d −= 0 . 1 f ;
i f ( speed <0) s p e e d = 0 ;
}
}
Beim Drücken der nach-oben/nach-unten Taste erhöht bzw. verringert sich die Rotationsgeschwindigkeit des Würfels.
Andere Listener können analog eingebunden werden.
3.11 Einlesen von externen 3D-Objekten
Leider ist es nicht ohne Weiteres möglich, 3D-Objekte aus Dateien wie .3ds mittels JOGL
einzulesen. Als Workaround kann man aber eine eigene Loader-Klasse schreiben oder einen
existierenden Loader verwenden: Da die Java3D-Bibliothek OpenGL unterstützt, kann
auch auf deren Loader zurückgegriffen werden.
3.12 JOGLAppletLauncher
Natürlich will man JOGL auch innerhalb eines Applets verwenden können, das dann auf
einer Website abgelegt ist. Da JOGL jedoch auf nativen Code zurückgreift, ist das nicht
ganz so einfach. Die vom Betriebssystem abhängigen Klassen müssen auf dem Rechner
installiert werden [8].
17
Allerdings dürfen die lokalen C-Klassen vom Applet aus Sicherheitsgründen nicht ausgeführt werden. Es ist aber möglich, dem Applet Vollzugriff auf den Rechner zu gestatten,
indem man es zertifiziert. Die Zertifizierung kann über einen Anbieter wie Verisign geschehen. Man kann auch ein eigenes Zertifikat erstellen [9]. Der User muss dann allerdings
entscheiden, ob er diesem vertrauen will. Die Basisklassen der JOGL-Bibliothek sind standardmäßig bereits zertifiziert.
4 Demoprojekt
Als Demoprojekt wurde der Grundaufbau eines 3D-Spiels entwickelt. In Anlehnung an die
Bedienung des bekannten Egoshooters CounterStrike“ lässt sich die Spielfigur mit der
”
Tastatur bewegen und mit der Maus drehen.
Der Spieler kann zwischen verschieden Perspektiven wählen:
Ich-Perspektive: Das Geschehen wird aus Sicht der Spielfigur wahrgenommen.
Erweiterte Ich-Perspektive: Der Betrachter läuft unmittelbar hinter der Spielfigur.
Vogelperspektive: Der Betrachter blickt von oben auf das Spielfeld.
Dieses Beispiel zeigt neben der Verarbeitung von Tastatur- und Mausereignissen auch die
Verwendung von Kamera, Licht und Texturen.
5 Fazit
JOGL ist ein mächtiges Werkzeug, wenn es um die Erstellung von 3D-Applikationen geht.
Die Library stellt über die in diesem Artikel erwähnten Funktionen hinaus noch zahlreiche
weitere Möglichkeiten zur Verfügung. So kann man Bilder als Texturen an 3D-Objekte
binden oder Partikel in Form von Nebel oder Explosionen einsetzen.
Der klare Vorteil von JOGL: Durch das direkte Binden der OpenGL-Funktionen kann
ein Entwickler mit OpenGL-Kenntnissen in C/C++ analog auch JOGL verwenden. Zudem können 3D-Anwendungen leicht zwischen den beiden Programmiersprachen portiert
werden.
In Anbetracht der Plattformunabhängigkeit und der leichten Erlernbarkeit von Java ist
JOGL für Entwickler der Spieleindustrie wie auch aus anderen Bereichen durchaus eine
interessante Alternative, 3D-Anwendungen zu entwickeln. Als Beispiele für Onlinespiele
unter Verwengung von JOGL seien PoxNora [10] und Wurm Online [11] erwähnt.
18
6 Literatur
[ 1 ] G e t t i n g s t a r t e d with JOGL, Mai 2009
h t t p : / / e i n s t e i n . i n f o r m a t i k . uni−o l d e n b u r g . de / f o r s c h u n g / o p e n g l / j o g l s n a p . html
[ 2 ] A l l e s rund um d i e Java B i n d i n g s f ü r OpenGL , Mai 2009
M e l a n i e K l e i n , S t e f a n Jouaux
h t t p : / /www. j o g l . i n f o /
[ 3 ] J o g l − User ’ s Guide , Mai 2009
h t t p s : / / j o g l . dev . j a v a . n e t / s o u r c e / browse /∗ c h e c k o u t ∗/ j o g l / t r u n k / doc / u s e r g u i d e / i n d e x . html
[ 4 ] JOGL API − JSR−231 1 . 1 . 2 S p e c i f i c a t i o n , Mai 2009
Sun Microsystems , I n c .
h t t p : / / download . j a v a . n e t / media / j o g l / b u i l d s / n i g h t l y / j a v a d o c p u b l i c /
[ 5 ] OpenGL f o r Java ( g l 4 j a v a ) , Mai 2009
http :// g l 4 j a v a . s o u r c e f o r g e . net /
[ 6 ] Home o f t h e L i g h t w e i g h t Java Game L i b r a r y , Mai 2009
http :// l w j g l . org /
[ 7 ] 3D g r a p h i c s programming i n Java , Part 3 : OpenGL , Mai 2009
h t t p : / /www. r o s e i n d i a . n e t / s o f t w a r e −t u t o r i a l s / d e t a i l /867
[ 8 ] JOGLAppletLauncher (JOGL API −− JSR−231 1 . 1 . 2 S p e c i f i c a t i o n ) , Mai 2009
Sun Microsystems , I n c .
h t t p : / / download . j a v a . n e t / media / j o g l / b u i l d s / n i g h t l y / j a v a d o c p u b l i c /com/ sun / o p e n g l / u t i l / JOGLAppletLaunche
[ 9 ] Z e r t i f i z i e r u n g von Java−Applets , Mai 2009
Babak Sa yy id H o s s e i n i
h t t p : / / t r a n s p o r t b r o k e r . v c e . de / r e s s o u r c e s / documents / E r s t e l l e n v o n Z e r t i f i k a t e n 1 . p d f
[ 1 0 ] PoxNora O n l i n e Game , Mai 2009
h t t p : / /www. poxnora . com/ i n f o / media . do
[ 1 1 ] Wurm Online , Mai 2009
h t t p : / / wurmonline . com/
19
Herunterladen