Geschrieben von evil-devil am 09.08.2005, 21:12

Einleitung

Hallo und willkommen zu meinem ersten Tutorial über JAVA im Zusammenhang mit OpenGL. Bevor wir aber loslegen will ich noch ein paar Worte darüber verlieren welche Möglichkeiten man unter JAVA hat um OpenGL zu nutzen sowie ein paar Informationen bezüglich des Tutorials selbst.

Zum einen kann man das Java3D API von Sun nutzen das für D3D und OpenGL verfügbar ist. Eine weitere Möglichkeit ist die JOGL (JAVA OpenGL) API die seit einiger Zeit mit von Sun unterstützt wird. Die letzte Möglichkeit die ich erwähnen will und um die sich dieses Tutorial dreht, ist die LWJGL API (Lightweight JAVA Game Library). Neben dem Support von OpenGL bietet sie Möglichkeiten zur Nutzung von OpenAL, FMod, DevIL und seit 0.96a auch nativen AWT/OpenGL Support.

Um das Tutorial zu verstehen ist es unabdingbar Grundkenntnisse in JAVA oder einer vergleichbaren Sprache zu haben, da hier nicht auf die Grundlegenden Techniken eingegangen werden kann. Ebenso ist ein Grundwissen über OpenGL von Vorteil aber nicht zwingend erforderlich. Desweiteren sei zu erwähnen das die Code Snippets nur Ausschnitte aus dem Source wiederspiegeln und somit auch Referenzen, try/catch Blöcke nicht immer mit übernommen wurden. Für ein besseres Verständnis wird daher empfohlen sich den beiliegenden Sourcecode mit anzusehen.

Es geht los

Bevor wir nun aber loslegen können, benötigen wir noch die LWJGL Binaries die ihr auf http://www.lwjgl.org erhaltet. Installiert diese entweder in eure JRE innerhalb des JDK, oder in einem Unterordner des Projektes.

Jetzt wo wir alles notwendige erledigt haben, könnt ihr eure IDE starten und ein neues Projekt anlegen. Wer nicht weiß welche IDE er nutzen soll, dem kann ich die Eclipse IDE mit dazugehörigen JDT Plugin empfehlen. Beides erhaltet ihr unter http://www.eclipse.org.

Gebt dem Projekt einen aussagekräftigen Namen, ich habe meines „Java_Lwjgl_Tutorial“ genannt, und erstellt eine neue Java Klasse. Meine hab ich JavaLWJGL genannt. Alles speichern und tief durchatmen.

Der nächste Schritt hat zwar noch nichts mit OpenGL zu tun, ist aber unerlässlich für unser Vorhaben wenn wir es als alleinstehende Anwendung laufen lassen wollen. Wir brauchen die Main Methode.

public static void main(String[] args) {
JavaLWJGL jlwjgl = new JavaLWJGL("JAVA LWJGL Tutorial");
}

Nun würde unsere Anwendung zumindest schonmal starten und sich danach beenden.

Fenster öffne dich

Wer auch immer die Bezeichnung des Anwendungs Fensters (Application Window) in Umlauf gebracht hat, so finde ich es schon komisch zu sagen „das alles in einem Fenster abläuft“, aber nichts desdotrotz müssen wir weiter im Kontext. Wir benötigen ein Fenster in dem wir unseren OpenGL Kontext anzeigen können, doch dazu müssen wir all dies zunächst erstellen.

Eine geeignete Bildschirmauflösung für die spätere Darstellung ist ein guter Anfang, diese ermitteln wir mittels der nachfolgenden Zeile.

displayMode = org.lwjgl.util.Display.getAvailableDisplayModes(
800,600,800,600,32,32,60,60)[0];

Was geschieht hier? wir fragen einfach, frech wie wir sind, welche Anzeige Möglichkeiten wir zur Auswahl haben. Wie zu erkennen ist wird lediglich nach 800 x 600 x 32 @ 60Hz gefragt. Man kann aber auch einen größeren Bereich abfragen bzw sich mit entsprechender Funktion aus der API alle verfügbarbaren Anzeigemodi geben lassen.

Der nächste Schritt besteht darin dem noch nicht erstellen Display mitzuteilen welchen Anzeigemodi wir nutzen wollen. Nachdem dies geschehen ist, können wir noch festlegen ob wir den Vollbild Modus haben wollen oder nicht.

Display.setDisplayMode(displayMode);
Display.setFullscreen(fullscreen);

Wir haben jetzt alle Information ermittelt und zugewiesen, womit der Erzeugung des Displays nichts mehr im Wege steht. Dazu rufen wir die create() Methode der Display Klasse auf, die dann für uns das Fenster in der gewählten Bildschirmauflösung und einen OpenGL Kontext erstellen wird. Um das ganze Abzurunden geben wir unserem Fenster einen Titel mit auf den Weg.

Display.create();
Display.setTitle(title);

Alle die sich nun fragen, ob dieses Fenster womöglich ein AWT/Swing Window/Frame ist, die können aufatmen. Es ist nativ erzeugt und nicht mit AWT/Swing nutzbar.

ESC mag nimmer

Damit wir in unserer Anwendung auch die Kontrolle über das Geschehen haben, wäre es nicht schlecht die Tastatur dafür nutzen zu können. Wie schon bei der Erzeugung des Displays rufen wir hierfür die create() Methode auf. Diesmal aber der Klasse Keyboard. Anschließend haben wir Zugriff auf die Tastatur und können Eingaben abfangen sowie verarbeiten.

Keyboard.create();

Jetzt wo uns die Tastatur gehorcht sollten wir den Moment nutzen und eine entsprechende Anweisung schreiben die bei Druck auf ESC die Anwendung beendet.

if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
cleanup();

Wie zu erkennen, fragen wir ab welcher Key gedrückt wird und rufen dann die gewünschte Funktion auf die die notwendigen Schritte durchführt und letztendlich unsere Anwendung beendet.

Der Discowürfel

Wir alle haben sicher schon mehr oder weniger oft einen Würfel in OpenGL erzeugt. Heute wird es nicht anders sein und wir erstellen uns einen „Discowürfel“. Doch zunächst müssen wir noch OpenGL initialisieren und alles erzeugen bzw. aktivieren was für unseren Discowürfel benötigt wird.

Unsere OpenGL Initialierung ist sehr einfach gehalten und wird den meisten sicherlich bekannt vorkommen.

GL11.glShadeModel(GL11.GL_SMOOTH);
GL11.glClearColor(0.0f,0.0f,0.0f,0.0f);
GL11.glClearDepth(1.0f);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glDepthFunc(GL11.GL_LEQUAL);
GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT,GL11.GL_NICEST);

Als nächstes erstellen wir uns eine Methode zum resizen der Szene, welche später direkt nach der OpenGL Initialierung zum Einsatz kommen wird.

private void reSizeGLScene(int width, int height) {
if (height == 0)
height = 1;

GL11.glViewport(0,0,width,height);
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GLU.gluPerspective(45.0f,(float)width/(float)height,0.1f,100.0f);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity();}

Der nächste Schritt den wir unternehmen wollen, ist die Erzeugung des Lichtes das später den Würfel beleuchten wird. Wir haben uns für 3 Lichter entschieden, die dem ganzen einen netten Look geben werden.

Was wäre ein Discowürfel ohne Licht? Nichts! Deswegen legen wir Arrays mit den Werten für die Lichter an und erzeugen anschließend die Lichter.

private float[] redDiffuse = { 1.0f, 0.0f, 0.0f, 1.0f };
private float[] greenDiffuse = { 0.0f, 1.0f, 0.0f, 1.0f };
private float[] blueDiffuse = { 0.0f, 0.0f, 1.0f, 1.0f };
private float[] posTopLeft = {-2.0f, 2.0f, 0.0f, 1.0f };
private float[] posTopRight = {2.0f, 2.0f, 0.0f, 1.0f };
private float[] posBottomFront = {0.0f, -2.0f, 1.0f, 1.0f };

public void initLight() {
GL11.glLight(GL11.GL_LIGHT0,GL11.GL_DIFFUSE,arrayToBuffer(redDiffuse));
GL11.glLight(GL11.GL_LIGHT0,GL11.GL_POSITION,arrayToBuffer(posTopLeft));

GL11.glLight(GL11.GL_LIGHT1,GL11.GL_DIFFUSE,arrayToBuffer(greenDiffuse));
GL11.glLight(GL11.GL_LIGHT1,GL11.GL_POSITION,arrayToBuffer(posTopRight));

GL11.glLight(GL11.GL_LIGHT2,GL11.GL_DIFFUSE,arrayToBuffer(blueDiffuse));
GL11.glLight(GL11.GL_LIGHT2,GL11.GL_POSITION,arrayToBuffer(posBottomFront));

GL11.glEnable(GL11.GL_LIGHT0);
GL11.glEnable(GL11.GL_LIGHT1);
GL11.glEnable(GL11.GL_LIGHT2);
GL11.glEnable(GL11.GL_LIGHTING);
}

Der aufmerksame Leser wird sich sicher nun fragen was die arrayToBuffer Methode im glLight() macht. Ganz einfach. Da wir keine Arrays übergeben dürfen, sondern auf einen Buffer angewiesen sind, müssen wir unser jeweiliges Array erst in einen Buffer umwandeln. Dazu dient die nachfolgende Methode.

private FloatBuffer arrayToBuffer(float data[]) {
FloatBuffer buffer = ByteBuffer.allocateDirect(
data.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
buffer.clear();
buffer.put(data);
buffer.rewind();
return buffer;
}

Im einzelnen erzeugen wir einen ByteBuffer der für uns Speicher alloziiert und die Bytes in nativer Reihenfolge (Big bzw. Little Endian) ordnet. Anschließend gibt er eine FloatBuffer Sicht zurück die es uns ermöglicht den ByteBuffer wie einen FloatBuffer zu behandeln. Nun übergeben wir nur noch das Array und schreiben anschließend den Inhalt. Der letzte Schritt besteht darin den erzeugten FloatBuffer zurückzuliefern.

Wer mehr zu dem Thema wissen will, sollte sich in der JAVA Dokumentation die Kapitel bezüglich der JAVA NIO API durchlesen.

Tja, was fehlt nun noch? Lasst uns mal scharf nachdenken, ja, der Würfel selbst. Seit dem Beginn des Tutorials haben wir ein Fenster mit einem OpenGL Kontext erzeugt. Uns der Tastatur bemächtigt und OpenGL mitgeteilt was wir alles für unsere Szene benötigen. All diese Vorgänge kann man natürlich ideal in einer allumfassenden init() Methode zusammenfassen und von dort auch die Hauptschleife aufrufen in der dann der eigentliche Ablauf stattfinden wird. Doch nun wollen wir erst noch unseren Würfel erstellen. Der dazugehörige Quellcode wird nicht extra aufgebröselt und sollte jedem der schonmal einen Würfel erstellt hat klar sein. Bezüglich des Lichts verweise ich auf entsprechend andere Literatur :p

private void renderGLScene() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GL11.glLoadIdentity();
GL11.glTranslatef(0.0f,0.0f,-5.0f);
GL11.glRotatef(rotX,1.0f,0.0f,0.0f);
GL11.glRotatef(rotY,0.0f,1.0f,0.0f);
GL11.glRotatef(rotZ,0.0f,0.0f,1.0f);
GL11.glScalef(scale,scale,scale);
// Der Würfel aller Würfel ^__^'
GL11.glBegin(GL11.GL_QUADS);
// Front Side
GL11.glNormal3f(0.0f,0.0f,1.0f);
GL11.glVertex3f(-1.0f,-1.0f,1.0f);
GL11.glVertex3f(1.0f,-1.0f,1.0f);
GL11.glVertex3f(1.0f,1.0f,1.0f);
GL11.glVertex3f(-1.0f,1.0f,1.0f);
// Back Side
GL11.glNormal3f(0.0f,0.0f,-1.0f);
GL11.glVertex3f(1.0f,-1.0f,-1.0f);
GL11.glVertex3f(-1.0f,-1.0f,-1.0f);
GL11.glVertex3f(-1.0f,1.0f,-1.0f);
GL11.glVertex3f(1.0f,1.0f,-1.0f);
// Left Side
GL11.glNormal3f(-1.0f,0.0f,0.0f);
GL11.glVertex3f(-1.0f,-1.0f,-1.0f);
GL11.glVertex3f(-1.0f,-1.0f,1.0f);
GL11.glVertex3f(-1.0f,1.0f,1.0f);
GL11.glVertex3f(-1.0f,1.0f,-1.0f);
// Right Side
GL11.glNormal3f(1.0f,0.0f,0.0f);
GL11.glVertex3f(1.0f,-1.0f,1.0f);
GL11.glVertex3f(1.0f,-1.0f,-1.0f);
GL11.glVertex3f(1.0f,1.0f,-1.0f);
GL11.glVertex3f(1.0f,1.0f,1.0f);
// Top Side
GL11.glNormal3f(0.0f,1.0f,0.0f);
GL11.glVertex3f(-1.0f,1.0f,1.0f);
GL11.glVertex3f(1.0f,1.0f,1.0f);
GL11.glVertex3f(1.0f,1.0f,-1.0f);
GL11.glVertex3f(-1.0f,1.0f,-1.0f);
// Bottom Side
GL11.glNormal3f(0.0f,-1.0f,0.0f);
GL11.glVertex3f(1.0f,-1.0f,-1.0f);
GL11.glVertex3f(-1.0f,-1.0f,-1.0f);
GL11.glVertex3f(-1.0f,-1.0f,1.0f);
GL11.glVertex3f(1.0f,-1.0f,1.0f);
GL11.glEnd();

if (rotX < 360)
rotX += 0.2f;
else rotX = 0.0f;

if (rotY < 360)
rotY += 0.3f;
else rotY = 0.0f;

if (rotZ < 360)
rotZ += 0.1f;
else rotZ = 0.0f;

if (scale >= 1.5f)
scaleUp = false;
else if (scale <= 0.25f)
scaleUp = true;

if (scaleUp)
scale += 0.0005f;
else scale -= 0.0005f;
}

Wir haben nun unseren Würfel erzeugt und lassen ihn sich in alle 3 Richtungen drehen und zusätzlich seine Größe ändern.

Nun ist eigentlich alles wichtige getan, doch was ist mit der Hauptschleife? Tja, ich will sie kurz ansprechen, auch wenn zu ihr eh nicht viel zu sagen ist. Im wesentlichen prüfen wir ob unser Fenster eine Schließaufforderung erhalten hat und ob unser Fenster sichtbar bzw. von einem anderen Fenster „verdreckt“ wurde und deshalb neugezeichnet werden sollte. In dem Falle wird dann unsere Szene gerendert und der Kontext erneuert. Am Ende fangen wir noch alle Tastatureingaben ab und verwerten diese. Dies ist natürlich nur eine kleine Hauptschleife, aber sie verdeutlicht die allgemeinen Vorgänge die man in ihr unternehmen kann.

while (true) {
if (Display.isCloseRequested())
cleanup();
break;
}
if (Display.isVisible() || Display.isDirty())
try {
renderGLScene();
Display.update();
} catch (OpenGLException e) {
e.printStackTrace();
}
}
checkKeyboardInput();
}

Das Finale

Um den Sourcecode kompilieren zu können, müsst ihr eurer IDE natürlich mitteilen, das sie die LWJGL Bibliotheken einbinden soll. Für das Tutorial reicht es die LWJGL.jar einzubinden.

Nach erfolgreichem kompilieren könnt ihr die Anwendung starten, hierzu müsst ihr lediglich der VM mitteilen wo sich die LWJGL Bibliotheken befinden. Dies geschieht über den VM Parameter -Djava. library.path. Und die VM will auch wissen welche der Bibliotheken in dem Pfad sie denn nutzen soll. Der Pfad zur Bibliothek muss relativ zum aktuellen Pfad sein.

Falls eure IDE euch Exception in thread "main" java.lang.NoClassDefFoundError meldet, solltet ihr die kompilierte Klass Datei in ein JAR Archiv packen und zum starten eine kleine Batch Datei schreiben. Alle gängigen JAVA IDEs haben die Möglichkeit ein Projekt zu deployen und in ein JAR Archiv zu packen. Anschließend braucht ihr nur noch die besagte Batch Datei. In diese kommt dann folgende Anweisung.

java -Djava.library.path=lwjgl/ -classpath
JavaLWJGL.jar;lwjgl/lwjgl.jar;lwjgl/lwjgl_util.jar
com.evildevil.tutorials.java.lwjgl.JavaLWJGL

Natürlich solltet ihr die Pfade entsprechend den euren anpassen. Das obige Beispiel geht davon aus das sich die LWJGL Bibliotheken in einem Unterorder names lwjgl in eurem Projektverzeichnis befinden, das JAR Archiv den Namen JavaLWJGL.jar trägt. Zuletzt wird der Pfad zur Mainclass angegeben. Nach all den Strapazen sollte nun die Anwendung starten und ihr könnt euch entspannt zurücklehnen und dem Diskowürfel bei seiner Arbeit zusehn :)

Hinterm Horizont geht's weiter

Tja, nun ist es schon vorüber mein Tutorial. Ich hoffe ihr konntet dabei etwas lernen und habt Appetit auf mehr bekommen. Bevor wir aber abschliessen mit dem Tutorial, will ich noch auf ein paar Dinge eingehen die während des Tutorials ein wenig vernächlässigt wurden.

Viele Klassen der LWJGL API sind komplett mit statischen Methoden versehen, sodass sie nicht instanziert werden können Ausnahmen gibt es aber auch, wie man der LWJGL Dokumentation entnehmen kann. Wer bereits mit dem JDK 5 arbeitet, kann sich auch unnötige Tipparbeit ersparen sofern der Zugriff auf die statischen Methoden Überhand nehmen. Dazu sollte die jeweilige Klasse mit der „import static“ direktive eingebunden werden. Ebenso bietet das LWJGL API Methoden zur Erzeugung von Buffern, wenn man hierfür keine eigene Klasse und entsprechende Methoden schreiben will. So, nun sind wir wirklich am Ende und ich würde mich über Feedback freuen.

Evil-Devil, d. 9. August 2005

PS: Der zugehörige Quellcode und die Beispiel App gibt es auf meiner Website :)

Bewertung Anzahl
6
100,0 %
4 Bewertungen