Lektion
1
Basiskomponenten von JOGL
Der GLEventListener
Fenstererzeugung
Zum Download des Quellcodes dieser Lektion: Auf das
Bild klicken oder hier.
Basiskomponenten von
JOGL
Im Gegensatz zu OpenGL verwendet JOGL keine eigenen Datentypen.
Es werden die vorgegebenen Javadatentypen benutzt. Dabei sollte
vor allem die Benutzung von float beachtet werden, da Kommazahlen
in Java standardmässig von Typ double sind. Soll float verwendet
werden, muss an die Zahl ein 'f' oder ein 'F' angehängt werden. Eine
weitere Möglichkeit ist das Casten, wobei der Kommazahl "(float)"
vorangestellt wird.
Oft werden Konstanten benutzt, um den Quellcode leserlicher zu
machen. Aussagekräftige Namen sind deshalb von Vorteil. Ausserdem
bestehen Konstanten, nach den bekannten Java Namens-konventionen,
immer aus Großbuchstaben.
Obwohl Funktionen in Java bei verschiedenen Argumenten, z.B. verschiedene
Datentypen, Arrays oder unterschiedliche Anzahl der Parameter, überladen
werden können, müssen in JOGL teilweise parameterspezifische
Funktionen aufgerufen werden, da diese auf C basieren und es dort
keine Möglichkeit des Überladens von Funktionen gibt.
Als Beispiel sei hierbei die Methode glColor* genannt, die sich
u.a. mit drei float-Zahlen (glColor3f), drei Bytezahlen (glColor3b)
oder auch mit einem Array vom Typ int (glColor3iv) aufrufen lässt.
Dies kann für einige Programmierer eine Umstellung darstellen,
jedoch wird dies durch die Eindeutigkeit und Eingängigkeit
der Methodennamen kompensiert, so dass das Prinzip schnell zu verstehen
und anzuwenden ist.
Funktionen der verschiedenen OpenGL-Bibliotheken werden durch Objekte
aufgerufen. Dies geschieht hauptsächlich mit den Interfaces
GL und GLU. Diese werden auf der Basis der verwendeten GLDrawable
gebildet, z.B. einem GLCanvas, auf dem das Rendering erfolgt.
Auf die GLUT-Bibliothek kann ebenfalls zugegriffen werden, allerdings
erfolgt dies nicht über ein Interface. GLUT ist eine eigene
Klasse, deren Objekte mit new() gebildet werden.
Weitere Basisklassen der JOGL-API sind GLCanvas, GLJPanel, GLCapabilities,
GLDrawableFactory, GLDrawable und GLEventListener.
GLCanvas ist eine AWT-Komponente, die Hardware-Beschleunigung unterstützt.
Während GLJPanel zwar eine 100%ige Swing Integration für
die Fälle garantieren soll, wenn GLCanvas nicht benutzt werden
kann, gewährleistet es jedoch keine Hardwareunterstützung.
GLCanvas und GLJPanel werden über die Factory-Methoden der
GLDrawableFactory erstellt.
Die GLDrawableFactory erlaubt es dem Benutzer bei neuen Widgets
(GUI-Element) bestimmte Parameter von OpenGL einzustellen, die im
Zusammenhang mit dem Rendering stehen. Dies geschieht mittels eines
GLCapabilities Objekts. GLCapabilities Objekte werden verwendet,
um die Parameter für das Rendering festzulegen, z.B. das Ein-
oder Ausschalten der Hardwarebeschleunigung oder des Double-Bufferings.
GLDrawable bietet Zugang zu GL- und GLU-Objekten, um die OpenGL
Routinen aufzurufen. Ausserdem wird über GLDrawable das
OpenGL Rendering per GLEventListener zur Verfügung gestellt
(addGLEventListener(GLEventListener listener)). Des weiteren kann
unabhängig von Swing oder AWT die Größe des Widgets
über getSize() und setSize(Dimension d) ausgelesen und manipuliert
werden.
Der GLEventListener wird im nächsten Abschnitt besprochen.
nach oben
Der GLEventListener
In Java werden Eingaben des Benutzers, z.B. über I/O-Geräte,
durch das sogenannte Event-Listener-Modell abgefangen und verarbeitet.
Auch die Kommunikation zwischen Threads kann über dieses Modell
gewährleistet werden.
Beim Event-Listener Modell gibt es zwei Teilnehmer, das Empfängerobjekt
und die Ereignisquelle.
Das Empfängerobjekt ist eine Instanz einer Klasse, die ein
spezielles Interface, die Empfängerschnittstelle implementiert.
In dieser Klasse wird festgelegt, wie ein auftretendes Ereignis
behandelt werden soll.
Die Ereignisquelle ist ein Objekt, das Empfängerobjekte registriert
und diesen Ereignisobjekte senden kann. In der Ereignisquelle werden
Benutzereingaben oder Threadereignisse an das Empfängerobjekt
delegiert. Dies geschieht über die Ereignisobjekte, die Informationen
bzgl. des auftretenden Ereignisses sammeln und so die aufzurufende
Methode im Empfängerobjekt festlegen.
Der GLEventListener wird immer dann aufgerufen, wenn OpenGL Rendering
ausgeführt werden soll. Jede Klasse, die das Interface GLEventListener
benutzt, muss die folgenden Methoden implementieren: init(..), display(..),
reshape(..) und displayChanged(..). Dabei ist immer eine Instanz
der Klasse GLDrawable, ein GLCanvas oder ein GLJPanel, zu übergeben,
auf der das Rendering ausgeführt werden soll.
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.
Die Methode display(..) wird immer dann aufgerufen, wenn tatsächlich
gerendert werden soll. Das kann z.B. durch einen Aufruf von repaint()
geschehen oder bei Erstellung einer Animation durch eine Instanz
der Klasse Animator (siehe Kapitel 5). In dieser Methode werden
u.a. die zu zeichnenden Elemente der Szene beschrieben oder Methoden
aufgerufen, denen dies übertragen wird. Reshape(..) wird aufgerufen,
wenn die verwendete Instanz der Klasse GLDrawable, ein GLCanvas
oder ein GLJPanel, initialisiert oder in ihrer Größe
verändert wird. Es bietet sich an, in dieser Methode die Kameraeinstellungen
vorzunehmen.
Schließlich wird in der Methode displayChanged(..) der Code implementiert,
der immer dann ausgeführt werden soll, wenn die Auflösung
des Bildschirms während der Laufzeit der JOGL-Applikation verändert
wird.
nach oben
Fenstererzeugung
m folgenden Abschnitt erzeugen wir ein einfaches Fenster,
in dem ein weißes Viereck auf schwarzem Grund zu sehen ist. Es
stellt die Basis für das Spielfeld dar. Dazu benötigen
wir zwei Klassen: Beispielszene.java, die von JFrame abgeleitet
wird und die eigentliche Fenstererzeugung übernimmt und BeispielszeneView.java,
die das Interface GLEventListener implementiert und in der das Zeichnen
vorgenommen wird. Um auf die Klassen und Interfaces von JOGL zugreifen
zu können, müssen wir die Bibliothek net.java.games.jogl
importieren.
Die Klasse Beispielszene.java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import net.java.games.jogl.*;
/**
* @author Melanie Klein & Stefan Jouaux
*/
public class Beispielszene extends JFrame
{
public Beispielszene()
{
GLCapabilities glcaps = new GLCapabilities();
GLCanvas canvas = GLDrawableFactory.getFactory().
CreateGLCanvas (glcaps);
BeispielszeneView view = new BeispielszeneView();
canvas.addGLEventListener(view);
setSize(500,500);
setTitle("CAV-Projekt: JOGL - Beispielszene");
setResizable(false);
getContentPane().add(canvas,BorderLayout.CENTER);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
public static void main(String[] args)
{
final Beispielszene app = new Beispielszene();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
app.setVisible(true);
}
});
}
}
Das Neue an dieser Klasse sind insgesamt vier Zeilen JOGL-Code:
GLCapabilities glcaps = new GLCapabilities();
GLCanvas canvas = GLDrawableFactory.getFactory().createGLCanvas
(glcaps);
Bei der Initialisierung eines GLCanvas werden zuerst die plattformabhängigen
Parameter zur Erzeugung ermittelt, z.B. die Farbtiefe. Dies geschieht
mittels eines GLCapabilities-Objektes. Um trotz der unterschiedlichen
Möglichkeiten, der verschiedener Plattformen, wozu auch die
verwendete JOGL- bzw. JVM-Version gehört, Plattformunabhängigkeit
zu gewährleisten, muss ein GLCanvas-Objekt über die GLDrawableFactory
erzeugt werden. Der Methode createGLCanvas(..) werden dabei die
durch das GLCapabilities-Objekt spezifizierten Parameter der jeweiligen
Plattform übergeben.
In den letzten beiden Zeilen JOGL-Code:
BeispielszeneView view = new BeispielszeneView();
canvas.addGLEventListener(view);
Wird der GLEventListener an die GLCanvas übergeben. Damit
gibt es eine Verbindung zwischen der GLDrawable, in diesem Fall
canvas, auf der gezeichnet werden soll und den Funktionen, die
definieren was und wie gerendert werden soll. Dies geschieht im
GLEventListener.
Die Klasse BeispielszeneView.java:
import net.java.games.jogl.*;
/**
* @author Melanie Klein & Stefan Jouaux
*/
public class BeispielszeneView implements GLEventListener
{
public void init(GLDrawable arg0)
{
GL gl = arg0.getGL();
GLU glu = arg0.getGLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
setCamera(gl, glu);
gl.glMatrixMode(GL.GL_MODELVIEW);
}
private void setCamera(GL gl, GLU glu)
{
int w = 500, h = 500;
gl.glViewport(0, 0, w, h);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(50.0, 1, 2.0, 40.0);
}
public void display(GLDrawable arg0)
{
GL gl = arg0.getGL();
GLU glu = arg0.getGLU();
gl.glClear(GL.GL_COLOR_BUFFER_BIT);
gl.glLoadIdentity();
glu.gluLookAt(0, 12, 19,
0, 0, 0,
0, 1, 0);
gl.glTranslated(0, 1, 0);
drawField(gl, glu);
}
public void drawField(GL gl, GLU glu)
{
gl.glBegin(GL.GL_QUADS);
gl.glVertex3f(-6.5f, -1.5f, -6.5f);
gl.glVertex3f(-6.5f, -1.5f, 6.5f);
gl.glVertex3f(6.5f, -1.5f, 6.5f);
gl.glVertex3f(6.5f, -1.5f, -6.5f);
gl.glEnd();
}
public void reshape(GLDrawable arg0, int arg1, int arg2, int arg3,
int arg4)
{
}
public void displayChanged(GLDrawable arg0, boolean arg1, boolean
arg2)
{
}
}
Die Methoden reshape(..) und displayChanged(..) werden für
dieses Beispiel nicht benötigt.
In der init-Methode wird zuerst die Farbe des GLCanvas bestimmt,
die nach dem Löschen der Szene angenommen werden soll. Dies
geschieht in der Zeile
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Der Methode glClearColor(..) werden vier float-Zahlen übergeben.
Die ersten drei Parameter stellen die Farbe im RGB-Modell dar. Zulässige
Werte sind Zahlen zwischen 0.0 und 1.0. Der letzte Parameter bestimmt
den Alpha-Wert.
In der zweiten Zeile rufen wir die Methode setCamera(..) auf, in
der wir die Kameraerzeugung ausgelagert haben. Zuerst bestimmen
wir mit glViewport(..) wie das gerenderte Bild in das Fenster eingepasst
werden soll. Die Parameter geben dabei die obere linke Ecke in Bildschirmkoordinaten
an, während die beiden letzten Parameter die Weite und Höhe
des gerenderten Bildes angeben:
int w = 500, h = 500;
gl.glViewport(0, 0, w, h);
Nun folgt unsere Kameradefinition. Dafür wird der GL_PROJECTION
Matrix Stack verwendet. Für Transformationen bezüglich
Objekten und Kamera sollte dagegen der GL_MODELVIEW Matrix-Stack
verwendet werden, d.h. auch auch die Positionierung der Kamera muss
auf dem GL_MODELVIEW Stack durchgeführt werden [Ker02]:
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
Die letzte Zeile der Methode definiert eine perspektivische Projektion,
um sicherzustellen, dass Objekte nach ihrer Position im Raum perspektivisch
verzerrt werden:
glu.gluPerspective(50.0, 1, 2.0, 40.0);
Der Methode wird zuerst der Öffnungswinkel der Kamera übergeben,
dann das Verhältnis zwischen Höhe und Breite des Viewing-Volumens,
sowie die Größe des Viewing-Volumens. Kurzgefasst wird die Information übergeben,
ab welchem Punkt im Raum Objekte angezeigt werden (der dritte Paramter
near) und ab welcher Distanz Objekte nicht mehr zu sehen sein sollen
(der letzte Paramter far).
In der Methode display(..) löschen wir zuerst das Color-Buffer-Bit,
dann wechseln wir in die Modelview-Matrix, da nun das Zeichnen der
Objekte, in diesem Fall eines Vierecks, das später einmal das
Spielfeld für unser "Mensch ärgere dich nicht"-Spiel
darstellt, erfolgen soll. Da wir die Kamera leicht nach oben versetzt
und schräg auf das Spielfeld sehen lassen wollen, manipulieren
wir mit der Methode gluLookAt(..) die Kamerapostion. Auf gluLookAt(..)
wird in dem Kapitel über Interaktion im 3D-Raum näher
eingegangen. Schließlich setzen wir mit glTranslate(..) die
Position des Spielfeldes um eins nach oben und lassen dann von der
Methode drawField(..) das Viereck zeichnen.
nach
oben
|