Grafisches Java - Java OpenGL

Java fu ¨ r Fortgeschrittene Proseminar Sommersemester 2009 Grafisches Java - Java OpenGL Ludwig N¨agele Technische Universita¨t Mu¨nchen 10. Mai 200...
15 downloads 2 Views 644KB Size
Java fu ¨ r Fortgeschrittene Proseminar Sommersemester 2009

Grafisches Java - Java OpenGL Ludwig N¨agele Technische Universita¨t Mu¨nchen 10. Mai 2009

1

Inhaltsverzeichnis 1 Zusammenfassung

3

2 Einleitung 2.1 Vorl¨aufer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Entstehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¨ 2.3 Uberblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3 3 4 4

3 Verwendung 3.1 Installation . . . . . . . . . . . . . . . ¨ 3.2 Uberblick . . . . . . . . . . . . . . . . 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¨oglichkeiten der Programmbibliothek JOGL. Die Anwendung dieser Programmbibliothek wird anhand eines durchgehenden Beispiels verdeutlicht. Detailliertere und weiterf¨ uhrende 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¨ urlich in 3D. Ebenso wie CAD-Programme zur Herstellung von dreidimensionalen Werkst¨ ucken in der Industrie, ben¨otigen auch Echtzeitsimulationsprogramme eine schnelle und zuverl¨assige Berechnungsm¨oglichkeit. Da dies mit dem Prozessor eines Heimrechners nicht ruckelfrei zu bewerkstelligen w¨are, nutzt man hierbei zus¨atzlich die Rechenkapazit¨at 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¨ ur 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¨ uhren dabei in der Regel einfach den korrespondierenden nativen C-Code aus. OpenGL ist eine plattformunabh¨angige Bibliothek, wohingegen das bekannte DirectX als Microsoftprodukt nur auf Windowssystemen l¨auft. Java bietet mit Java3D neben JOGL noch eine alternative Grafik-Entwicklerschnittstelle an, die beide Bibliotheken unterst¨ utzt. Ihre Objektstruktur abstrahiert von den zugrunde liegenden Schnittstellenmethoden und ist daher in der Verwendung verschieden von JOGL.

2.1 Vorl¨ aufer Mangels Unterst¨ utzung 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¨ uhrte: 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¨ utzt gl4java OpenGL nur bis Version 1.3, die Benutzung neuer

3

I/O-Ger¨ate wird nur beschr¨ankt gew¨ahrleistet und die umfangreiche API macht es Programmierern schwer, den Einstieg zu finden. W¨ahrend LWJGL zwar neueste I/O-Ger¨ate unterst¨ utzt und auch f¨ ur die aktuellste OpenGL Version geeignet ist, liegt der Hauptnachteil in der nicht zul¨assigen Einbindung von AWT oder Swing Komponenten. Das dritte Binding Magician l¨ost zwar das Problem der AWT und Swing Einbindung, jedoch wird es weder weiterentwickelt, noch unterst¨ utzt es neue I/O-Ger¨ate. Ein weiterer Nachteil liegt darin, dass Magician nie OpenSource war. Die erste wirklich vielversprechende Entwicklung stellte das im August 2003 ver¨offentlichte JOGL dar.

2.2 Entstehung Begonnen wurde die Programmbibliothek urspr¨ unglich 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¨ort, als Open Source st¨andig weiterentwickelt und verbessert. Es ist davon auszugehen, dass JOGL in eine der sp¨ateren Java-Versionen standardm¨aßig integriert wird. Die ersten Versionen von JOGL arbeiteten im Package net.java.games.jogl. Dieses hat ¨ sich aber nach dem Ubergang zu den JSR-231 APIs zu javax.media.jogl ge¨andert. Leider greifen jedoch noch sehr viele Tutorials und Informationsseiten im Internet auf die alte ¨ JOGL-Version zur¨ uck, die auch in der Klassen- und Methodenstrucktur einige Anderungen aufweist. Beispielsweise wurden einige Klassen (Animator ) aus der neueren Version herausgenommen, oder sogar in ihrer Erzeugung ver¨andert: Die Klasse GLU ist nun nicht mehr u ¨ber eine Factory zu erzeugen, sondern direkt u ¨ber new() instanziierbar.

¨ 2.3 Uberblick In den nachfolgenden Kapiteln wird zun¨achst auf vier Klassen von JOGL eingegangen. Um einen kleinen Einblick in deren Funktionalit¨aten 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 u ¨ber eine Kamera aus einer r¨aumlichen 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¨aßig in Java integriert ist, muss man zun¨achst 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¨ utzt die g¨angigen Plattformen, auf denen J2SE 1.4 oder h¨oher l¨auft.

¨ 3.2 Uberblick OpenGL (Open Graphics Library) ist eine Programmierschnittstelle zur Entwicklung von 3D-Computergrafik unabh¨angig von Plattform oder Programmiersprache. Sie beinhaltet etwa 250 Befehle, die die Darstellung komplexer 3D-Szenen in Echtzeit erlauben. Zudem k¨onnen eigene Erweiterungen (beispielsweise von Grafikkartenherstellern) definiert werden. JOGL erm¨oglicht die Verwendung dieser Befehle in Java. Hierzu wurden s¨amtliche Methoden direkt u ¨bernommen, die lediglich die korrespondierenden C-Implementierungen aufrufen. Aus diesem Grund gibt es auch mehrere ¨ahnlichnamige 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¨andnis 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¨aumliche Position des Viewports (Standort des Betrachters), und wird deshalb auch Kamera genannt. Kamera ist eine Umschreibung f¨ ur eine modifizierte Projektions-Matrix. Licht spielt in 3D-Anwendungen eine große Rolle. Lichtquellen k¨onnen selbst definiert werden, wodurch der Umgebung ein dreidimensionales Aussehen verliehen werden kann. Standardm¨aßig sind alle Objekte mit einem gleichm¨aßigen diffusen Licht beleuchtet. Material bestimmt die Oberfl¨achenbeschaffenheit von Objekten. Dies kann einfach eine Farbe oder auch eine Grafik sein. Zudem k¨onnen Einstellungen bez¨ uglich 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¨achlich mit dem Interface GL, das die Erstellung komplexer 3D-Szenen erm¨oglicht, und der Klasse GLU, die unter Anderem die M¨oglichkeiten der Kameraeinstellungen bietet. Die Referenz auf das GL-Objekt erh¨alt man u ¨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 u ur das ¨bergeben werden, u ¨ber das die Parameter f¨ 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: Uber das GLInterface wird die Szene aufgebaut, GLU bestimmt die Sichtweise. Die Verwaltung u ¨ber diese beiden Klassen u ¨bernimmt ein GLEventListener. Wahrgenommen“ werden kann das ” fertige Bild u ¨ber ein GLDrawable-Objekt (GLCanvas).

Diese vier Klassen samt ihrer interessantesten Methoden seien auf den folgenden Seiten kurz erw¨ahnt. Interessierte Leser k¨onnen 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¨ utzt. Auf ihr kann die 3D-Welt angezeigt werden. ¨ Uber die Methode addGLEventListener() weist man dem GLCanvas eine Klasse zu, die Ereignisse wie Gr¨oßen¨anderungen behandelt. ¨ Uber 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¨ahrleistet diese Klasse keine Hardwareunterst¨ utzung. 3.3.2 GL Das Interface GL stellt die Schnitstelle dar, mit der die 3D-Welt modelliert wird. ¨ Uber 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 u ¨bergeben, das spezifiziert, wie die Eckpunkte interpretiert werden: Beispielsweise zeichnet ulltes Dreieck. Mit GL TRIANGLE STRIP ein ausgef¨ ulltes Viereck erzeuGL QUAD STRIP l¨asst sich ein ausgef¨ gen. Mittels glColor3f() kann die aktuelle Zeichenfarbe in RGB (rot-gr¨ un-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¨ ur Projektionseigenschaften wie Kameraperspektive verwendet. Dar¨ uber hinaus gibt es noch GL TEXTURE und GL COLOR. ¨ Uber glRotatef() k¨onnen 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¨onnen alle Modifikationen auf der aktiven Matrix zur¨ uckgesetzt und die urspr¨ ungliche Matrix wieder hergestellt werden. glClear() leert den Bitpuffer.

7

Mit glEnable() lassen sich GL-Eigenschaften aktivieren. Ein Beispiel hierf¨ ur sehen wir in einem sp¨ateren Kapitel. 3.3.3 GLU Die Klasse GLU (GL Utilities) bietet in erster Linie die Verwaltung der Kameraeinstellung und stellt dar¨ uber hinaus erweiterte Zeichenfunktionen zur Klasse GL zur Verf¨ ugung. In unserem Beispiel gehen wir lediglich auf die beiden Funktionen gluPerspective() und gluLookAt() ein. Hier¨ uber k¨onnen 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¨onnen allgemeine Parameter zum Rendern der Szene eingestellt werden. Die Methoden displayChanged() und reshape() behandeln ¨ Anderungen der Bildschirmaufl¨osung oder der Fenstergr¨oße sowie das Verschieben des Fensters. Die Methode display() wird immer dann aufgerufen, wenn tats¨achlich gerendert werden soll. In ihr werden u.a. die zu zeichnenden Elemente der Szene beschrieben. Eine Animation erreicht man, indem diese Methode in regelm¨aßigen Abst¨anden ausgef¨ uhrt wird, und die Szene jeweils in Abh¨angigkeit von internen Variablen gerendert wird. Dies kann ein externer Thread u ¨bernehmen, der u ¨ber den Aufruf repaint() des Canvas-Objekts ¨ implizit die Ausf¨ uhrung der Methode display() bewirkt. Eine dynamische Anderung dieser Variablen hat dann eine Bewegung in der Darstellung zufolge.

8

3.4 Vorgehensweise anhand eines Beispiels Wir m¨ochten 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¨achst ein Frame (MyFrame), auf dem wir eine JOGL-Ausgabe (MyGLEventListener ) darstellen m¨ochten. 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¨ache, auf der die Ausgabe gezeichnet werden soll. In Java werden Eingaben des Benutzers, z.B. u ¨ber I/O-Ger¨ate, durch das sogenannte Event-Listener-Modell abgefangen und verarbeitet. Tritt auf der Zeichenfl¨ache ein Ereignis ein, so soll dieses von einem EventListener verarbeitet werden. Die Zuordnung legen wir u ¨ber den Aufruf von addGLEventListener() fest. Wird nun das Canvas geladen oder in der Gr¨oße ver¨andert, reagiert MyGLEventListener darauf und baut die Zeichenfl¨ache neu auf. Letztendlich wollen wir die Zeichenfl¨ache auch sehen k¨onnen, und f¨ ugen 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¨ache reagieren k¨onnen und implementiert daher das Interface GLEventListener. F¨ ur unser Beispiel muss zun¨achst lediglich die Funktion display() angepasst werden, die anderen Methoden werden leer implementiert. Mittels getGL() erhalten wir das GL-Objekt der u ¨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¨ uhrt werden. Ebenso w¨are beispielsweise GL QUADS f¨ ur ein Viereck m¨oglich. Mit glColor3f() setzen wir die Zeichenfarbe in RGB. Nun erstellen wir drei Punkte glVertex3f(), die innehrhalb der Gruppierung als ausgef¨ ulltes Dreieck abgebildet werden. Als Ergebnis erhalten wir ein Fenster mit einem roten Dreieck. Ver¨andern wir das Fenster in seiner Gr¨oße, so f¨allt uns auf, dass das Dreieck verzerrt dargestellt wird. Das liegt daran, dass JOGL keine Information vorliegt, in welcher Proportion das gerenderte Bild sp¨ater einmal angezeigt werden soll. Zur Behebung dieses Problems eignet sich die Methode reshape(), in der JOGL u ¨ber die neue Zeichenfl¨achengr¨oß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