Leightweight Java Games Library

Transcription

Leightweight Java Games Library
Hochschule Fulda
[LOGO]
Ausarbeitung im Rahmen der Lehrveranstaltung
„Grafische Interaktive Systeme“
von Daniel Bogdan
07.01.09
Daniel Bogdan
Grafische Interaktive Systeme
Seite 1
Inhaltsverzeichnis
1. Einleitung
2. Unterschied von LWJGL 1.1.4 zu 2.0
3. Benutzung der Zusatzbibliothek von [POTATO]
4. Entwicklungsdokumentation
4.1. Praktisches Beispiel (Tutorial)
4.1.1. Grundgerüst
4.1.2. Klassendiagramm
4.1.3. Kamera
4.1.4. Wetter erzeugen
4.1.5. Objekte erstellen
4.1.6. Texturen auf Objekte
4.1.7. Fahrzeugmodell erstellen
4.1.8. 3D-Modelle importieren
4.1.9. Screenshots
4.2. Fazit
4.3. Quellen
5. Anhang
5.1. Anwenderdokumentation
5.2. Eingesetzte Werkzeuge
5.3. Darstellung des Aufwands
Daniel Bogdan
Grafische Interaktive Systeme
Seite 2
1. Einleitung
Ich habe dieses Thema gewählt, da mir es Freude bereitet Spiele zu programmieren.
Dazu kommt noch die Kombination OpenGL mit der Programmiersprache Java. Java ist im
Allgemeinen übersichtlich und strukturiert aufgebaut und verfügt über eine gute
Dokumentation. Ein weiterer Vorteil ist die mächtige Entwicklungsumgebung Eclipse. Der
einzige Nachteil ist nur, dass Java vergleichsweise sehr langsam ist. Hier kommt nun die
Lightweight Java Game Library ins Spiel, die eine Schnittstelle zwischen Java und
OpenGL bietet. Die OpenGl-Befehle sind schnell und machen den Nachteil, dass Java von
Grund auf langsamer ist, wieder wett. Die Performance war mehr als nur akzeptabel. Es
muss aber auch gesagt werden, dass sich die Komplexität des Programms in Grenzen
hält. Wie es in einem richtig großen Programm aussieht, kann ich schlecht beurteilen. Als
weiteren Vorteil sehe ich, das die Kombination plattformunabhängig ist. Sowohl Java, als
auch OpenGL funktionieren nicht nur unter Windows.
Meine Ausarbeitung baut auf der von Daniel Türpitz auf. Daher gehe ich nicht mehr auf die
Grundlagen ein, sondern beginne relativ zügig mit dem praktischen Beispiel.
Des weiteren möchte ich erwähnen, dass ich im Quellcode englische Variablen verwendet,
den Kommentar aber auf Deutsch geschrieben habe. Dies hat einen einfachen Grund; die
englischen Wörter sind im Durchschnitt kürzer. Somit macht das den Quellcode wieder
etwas übersichtlicher. In der deutschen Sprache ist jedoch die Redundanz größer und
man kann Probleme besser erklären. Im Englischen führt das oft zu einer Zweideutigkeit,
daher die Kommentare auf Deutsch.
2. Unterschied von LWJGL 1.1.2 zu 2.0
Mittlerweile ist die aktuellste Version 2.0.1 von LWJGL. Mit Beginn meiner Ausarbeitung,
existierte aber nur LWJGL 2.0. Deshalb beachte ich die Version 2.0.1 hier auch nicht
weiter. Da meine Ausarbeitung auf die von Daniel Türpitz aufbaut, möchte ich kurz die
wichtigste Änderung erwähnen. Alle weiteren Unterschiede aufzuzählen sprenge den
Rahmen. Sollte diesbezüglich Interesse bestehen, gibt es auf der folgenden Seite
ausführlichere Informationen: [LWJGL_C]
In der Version 1.1.2 war die Klasse GLU noch im Paket org.lwjgl.opengl. Ab Version 2.0
liegt sie im Paket org.lwjgl.util. Dies ist zu beachten und ggf. zu ändern. Das Einrichten
der LWJGL 2.0 funktioniert genauso unter der Version 1.1.2.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 3
3. Benutzung der Zusatzbibliothek von [POTATO]
Meine erste Aufgabe bestand darin, Beispiel-Programme und Demos im Internet zu
suchen, die die LWJGL benutzen. Ich fand aber nur zwei nennenswerte Seiten. Einmal die
von [LWJGL] selbst und des weiteren von [POTATO]
Bei der letzteren bemühte sich der Entwickler sehr und erstellte einige Klassen, die man
als Zusatzbibliothek zählen kann. Diese bietet die einfache Möglichkeit Texturen auf
Objektoberflächen zu bringen, OBJ- oder 3DS-Modelle zu importieren und Audio-Dateien
einzufügen. Auf diese Bereiche werde ich auch nicht weiter eingehen; ich verwende nur
die Schnittstellen und werde auf einige Probleme aufmerksam machen.
Das Hauptproblem war, dass bisher nur mit LWJGL 1.1.4 gearbeitet und somit die Klasse
GLU
im falschen Paket eingebunden wurde. Ein weiteres Problem war, dass in der Klasse
GLApp
die Methode GLU.gluProject() und GLU.gluUnProject() als letzten Parameter
einen Datentyp float[] verlangt und in der LWJGL 2.0 einen Datentyp von FloatBuffer.
Da ich diese Methode aber nicht anwenden werde, habe ich sie einfach auskommentiert.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 4
4. Entwicklungsdokumentation
4.1. Praktisches Beispiel (Tutorial)
Der Schwerpunkt meiner Ausarbeitung liegt im praktischen Teil. Mein Ziel ist es nicht, ein
fertiges 3D-Spiel zu programmieren, sondern viele Ansätze darzustellen, diese aber nicht
bis ins kleinste Detail zu diskutieren.
4.1.1 Grundgerüst
Das Grundgerüst von meinem Projekt ist die Klasse GameMain, die von der Klasse GLApp
abgeleitet ist. GLApp ist eine voll funktionsfähige Klasse. Das ausführen des Programms an
dieser Stelle würde eine Demo starte, in dem sich zwei Würfel drehen. Da ich aber meine
eigene Szene zeichnen will, habe ich die GameMain geschrieben und überschreibe nur die
Methoden init(), render(), keyDown() und keyUp(). Auf die restlichen Methoden von
GLApp
kann ich aber trotzdem noch zugreifen, denn die werden vererbt.
Die init() - Methode wird nur einmal zum Start des Programms aufgerufen und wichtige
Funktionen initialisiert.
Die render() - Methode wird in einer Schleife ausgeführt, die sich so lange wiederholt, bis
die ESC-Taste gedrückt wird. Hier wird auch das zeichnen der Szene realisiert.
Die keyDown() - Methode wird aufgerufen, wenn auf der Tastatur eine Taste gedrückt wird.
Wenn die Taste gedrückt gehalten wird, wird diese Methode aber nicht ständig aufgerufen.
Wenn die Taste losgelassen wird, wird die Methode keyUp() aufgerufen.
Dies sollte erstmal als Grundgerüst reichen.
package spiel;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import lib.*;
public class GameMain extends GLApp
{
public void init ()
{
initDisplay ();
initInput ();
initOpenGL ();
Daniel Bogdan
/* Initialisierung des Fenster
*/
/* Initialisierung der Tastatur und Maus */
/* initialisierung der OpenGL Umgebung
*/
Grafische Interaktive Systeme
Seite 5
}
public void render ()
{
GL11.glClear (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
public void keyDown (int keycode)
{
}
public void keyUp (int keycode)
{
}
public static void main (String args[])
{
GameMain demo = new GameMain ();
demo.run ();
}
}
4.1.2 Klassendiagramm
Im Klassendiagramm sieht man sehr deutlich, was ich selbst geschrieben habe und was
vorgefertigt verwendet wurde. Das Paket game kommt von mir und das Paket lib von
[POTATO].
Daniel Bogdan
Grafische Interaktive Systeme
Seite 6
4.1.3 Kamera
Ich habe zwei Kameraeinstellungen implementiert. Erstens das Verfolgen des Fahrzeugs
und zweitens das freie Bewegen in der 3-D Welt.
Dazu erstellen wir erst einmal die Klasse Camera mit sechs Attribute, die später für das
gluLookAt()
benötigt werden. Ich habe sie wie folgt genannt: xPos, yPos, zPos. Die drei
Koordinaten repräsentieren einen Punkt in der Welt, an dem die Kamera steht. Die
anderen drei Attribute: xView, yView, zView repräsentieren einen Punkt in der Welt, auf
den die Kamera schaut. Des weiteren benötigen wir noch einige boolische Variable, die
den Wert true besitzen, wenn eine bestimmte Taste gedrückt wird.
/* Kamera um das Objekt nach rechts oder links drehen */
private boolean rotaCCW, rotaCW;
/* Kamera nach oben oder unten bewegen */
private boolean
yVecUp, yVecDown;
/* Kamera nach vorne oder hinten bewegen */
private boolean
xVecForw, xVecBackw;
/* Kamera nach links oder rechts bewegen */
private boolean
zVecLeft, zVecRight;
/* View-Punkt nach oben oder unten bewegen */
private boolean
xRotaUp, xRotaDown;
/* View-Punkt nach links oder rechts drehen */
private boolean
yRotaLeft, yRotaRight;
Die action()-Methode ist das Herzstück der Klasse. Sie wird von der Methode render()
in der Klasse GameMain für jeden Durchlauf einmal ausgerufen. Dabei wird nur überprüft,
ob einer der boolischen Variablen den Wert true besitzt. Wenn keine Taste gedrückt wird,
verändert die Methode auch nichts.
Ich beschreibe hier nur die Kamera, die sich im Raum frei bewegen kann.
Damit dies der Kamera möglich ist, muss die xPos und zPos Variable geändert werden.
Damit der Blick immer gleich bleibt, ändere ich parallel auch die xView und zView Variable.
if (xVecForw)
{
}
/* Kamera bewegt sich mit View-
/* Punkt nach vorne
xPos -= Math.cos (camAngle);
xView -= Math.cos (camAngle);
zPos -= Math.sin (camAngle);
zView -= Math.sin (camAngle);
*/
*/
Das Rückwärtsbewegen funktioniert analog; nur alle vier Vorzeichen sind umgekehrt.
Beim Linksbewegen dreht sich das Vorzeichen der z-Koordinate, im Gegensatz zum
Daniel Bogdan
Grafische Interaktive Systeme
Seite 7
Vorwärtsbewegen. Ansonsten sieht es identisch aus.
if (zVecLeft)
/* Kamera bewegt sich mit View{
/* Punkt nach links
xPos -= Math.sin (camAngle);
xView -= Math.sin (camAngle);
zPos += Math.cos (camAngle);
zView += Math.cos (camAngle);
}
*/
*/
Das Rechtsbewegen funktioniert wieder analog, nur sind wieder alle Vorzeichen
umgekehrt.
z
camAngle = 30°
xPos = zPos = 0
grün:
xPos += cos (30°) = 0,866
zPos += sin (30°) = 0,5
x
rot:
xPos -= cos (30°) = -0,866
zPos -= sin (30°) = -0,5
blau:
xPos -= cos (30°) = -0,866
zPos += sin (30°) = 0,5
magenta:
xPos += cos (30°) = 0,866
zPos -= sin (30°) = -0,5
An dieser Skizze ist dies deutlicher erkennbar. Die Kamera befindet sich im
Koordinatenursprung. Der Kamerawinkel beträgt 30°. Wenn ich mich in einer der vier
Richtungen bewegen möchte, kommen für xPos und zPos die Ergebnisse auf der rechten
Seite neben der Skizze heraus. Bei den Variable xView und zView sieht dies analog aus.
Dies war ein kurzer Einblick in die Klasse Camera; weitere Informationen stehen im
Quellcode selbst.
4.1.4 Wetter erzeugen
Dieser Punkt hört sich vielleicht spannend an, aber ich spreche hier nicht von Gewitter und
Regen, sondern lediglich von einem Tag-/Nachtzyklus und Nebel.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 8
Das Wichtigste in der Klasse Weather ist das Licht. Ich verwende einmal als Lichtquelle
paralleles Licht, das die Sonne darstellen soll sowie ein Punktlicht, das eine Lampe
vergegenwärtigt.
Wieder ist die Methode action() das Herzstück der Klasse. Sie wird von der Methode
render()
in der Klasse GameMain für jeden Durchlauf einmal ausgerufen. Mit jedem Aufruf
erhöht sich die Zeit. Die
Lichtverhältnisse werden
mit Hilfe der Sinus-
Übergang von
Tag in Nacht
Mittag
½ * PI
Funktion berechnet.
0 * PI
Bei ½ PI ist der Sinus
eins und somit sind die
1 * PI
Lichtverhältnisse am
stärksten ausgeprägt. Ist
Aktuelle
Zeit
die Zeit größer als PI, ist
der Sinus davon negativ
Mitternacht
und die Nacht wird
¾ * PI
Übergang
von Nacht in
Tag
simuliert.
Wenn der Nebel aktiv ist, bleibt die Hintergrundfarbe in der Nacht schwarz und der Nebel
wird ebenfalls dunkel. Am Tag ist der Nebel grau, genau wie die Hintergrundfarbe. Je
näher die Zeit am Mittag, desto heller dieses Grau. Ansonsten wird es wieder dunkler, bis
es ins Schwarz übergeht. Den Zeitpunkt der Aktivierung des Nebels kann der Benutzer mit
Hilfe einer Taste beeinflussen. Das Punktlicht leuchtet in der Garage und der Benutzer
kann es auch mit Hilfe einer Taste an- und ausschalten. Mehr Informationen zu der
Tastenbelegung gibt es in der Anwenderdokumentation. Ein Problem, das mir aber erst
sehr spät aufgefallen ist, war, dass das Punktlicht durch die Wände scheinen konnte und
auch den Boden beleuchtete, obwohl ja ein Objekt dazwischen stand. Für die Berechnung
des Schattens konnte ich mich aus zeitlichen Gründen nicht mehr informieren und habe es
daher einfach hingenommen.
4.1.5 Objekte erstellen
Die Klasse GLApp bietet mit den Methoden renderCube() und renderSphere() die
Möglichkeit einen Würfel bzw. eine Kugel zu zeichnen. Wenn man sich aber die Methode
renderCube()
Daniel Bogdan
ansieht, fällt ganz schnell auf, dass sich die Vertex-Punkte noch in dem
Grafische Interaktive Systeme
Seite 9
glBegin() und glEnd()
glDrawArrays()
– Block befinden. Heutzutage benutzt man nur noch
oder glDrawElements(). Da dies vom Aussehen her keinen Unterschied
macht, sondern nur die Performance verbessert, empfand ich es als nicht notwendig, dies
umzuschreiben.
Als Erstes erstellen wir einen Boden mit Hilfe der renderCube()-Methode. Dann skalieren
wir den Würfel in x- und z-Richtung um das Hundertfache und in y-Richtung um ein
Zehntel. Zuletzt geben wir den Objekten noch eine Materialeigenschaft für ambientes und
diffuses Licht, beispielsweise grau. Die GL11.glMaterial()-Funktion verlangt als letzten
Parameter einen Datentyp FloatBuffer. Daher die Methode allocFloats(), die in der
Oberklasse GLApp definiert ist. Sie wandelt das float-Array in einen FloatBuffer um und
gibt dieses als Rückgabewert zurück.
public void init ()
{
...
gray = new float[] {0.5f, 0.5f, 0.5f, 1.0f};
...
}
public void render ()
{
...
GL11.glPushMatrix ();
{
GL11.glMaterial (GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE,
allocFloats (gray));
GL11.glScalef (100f, 0.1f, 100f);
renderCube ();
/* Soll den Boden darstellen */
}
}
GL11.glPopMatrix ();
...
Im nächsten Unterpunkt erkläre ich, wie man Texturen auf diesen Objekten erhält, damit
sie etwas echter aussehen.
Mit diesem Prinzip habe ich jetzt eine Garage gebaut, die aus drei Wänden und einem
Dach besteht. Das ist nicht die schönste Variante, aber eine schnelle. Schöner wäre es,
wenn man ein Objekt mit 3D-Modellierungswerkzeugen erstellt und sie hier lediglich
einbindet. Dies funktioniert auch, das ich aber erst im Unterpunkt „3D-Modelle importieren“
erkläre.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 10
4.1.6 Texturen auf Objekte
Texturen auf Objekten zu erhalten ist mit der Zusatzbibliothek von [POTATO] relativ
einfach. Die Klasse GLApp erledigt die meiste Arbeit; zusätzlich benötigt man die Klasse
GLImage. In der init() - Methode ruft man als erstes die Funktion
GL11.glEnable(GL11.GL_TEXTURE_2D);
auf, damit die Texturen eingeschaltet werden. Als Nächstes lädt man die Bild-Datei, dazu
die Methode loadImage() und als Parameter übergibt man den Pfad zur Bild-Datei als
String. Der Rückgabetyp ist ein GLImage. Wichtig ist, dass man weiß, in welchen Paket
die Klasse GLApp liegt. Der Pfad zählt nämlich ab dem Ordner und ist somit relativ. Die
Klasse GLApp liegt bei mir im Paket lib. Mein Ordner mit den Bild-Dateien liegt aber in
dem Ordner „res“, der wiederum auf der selben Höhe von dem Paket lib liegt. Ich muss
mich also in der Hierarchie einen Ordner höher bewegen und könnte dann in „res“
wechseln. Das Zurückspringen funktioniert mit dem „../“. Der Aufruf würde dann wie folgt
heißen:
GLImage textureImg = loadImage ("../res/images/ground.png");
Als Nächstes verwendet man die Methode makeTexture() und übergibt als Parameter das
GLImage, das zuvor erzeugt wurde. Als Rückgabetyp gibt es einen Integer-Wert. Der
Aufruf sieht dann folgendermaßen aus:
groundTexHandle = makeTexture (textureImg);
Als Letztes noch die Methode makeTextureMipMap(), die man auch in der Klasse GLApp
vorfindet. Als Parameter übergibt man wieder das zu Beginn erzeugte GLImage.
Rückgabetyp gibt es diesmal keinen. Der Aufruf ist auch nicht unbedingt erforderlich, lässt
das Bild aber schöner aussehen. Der Aufruf würde wie folgt aussehen:
makeTextureMipMap (textureImg);
Das war die Initialisierungsphase, die nur einmal zum Programmstart aufgerufen wird. Die
render() - Methode sieht jetzt folgendermaßen aus:
public void render ()
{
...
GL11.glPushMatrix ();
{
GL11.glMaterial (GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE,
allocFloats (gray));
Daniel Bogdan
Grafische Interaktive Systeme
Seite 11
GL11.glBindTexture (GL11.GL_TEXTURE_2D, groundTexHandle);
GL11.glScalef (100f, 0.1f, 100f);
renderCube ();
/* Soll den Boden darstellen */
}
GL11.glPopMatrix ();
...
}
Möchte man nun für ein anderes Objekt eine andere Textur verwenden, führt man die
Initialisierungsphase noch einmal durch, diesmal nur mit dem anderen Bild als Datei-Pfad,
und speichert dies in eine andere Variable. Mit diesen Variablen hat man dann Zugriff auf
die unterschiedlichen Texturen. Die Variable ist eigentlich nur eine Integer-Zahl, die
hochgezählt wird.
Ein Problem tritt auf, wenn man für ein Objekt keine Textur haben möchte. Denn selbst
wenn man glBindTexture() nicht aufruft, wird automatisch die zuletzt verwendete Textur
benutzt. Daher ist es notwendig, vor dem Zeichnen des bestimmten Objekts die Texturen
mit dem Befehl
GL11.glDisable(GL11.GL_TEXTURE_2D);
zu deaktivieren und nach dem Zeichnen wieder zu aktivieren.
4.1.7 Fahrzeugmodell erstellen
Das Fahrzeugmodell ist nichts anderes, als die Berechnung der Bewegung des
Fahrzeuges. Die wichtigsten Instanzvariablen sind die x- und z-Koordinate, sowie der
Drehwinkel des Fahrzeuges. Diese Berechnung geschieht mit Hilfe von zwei Vektoren.
Zum Einen der Geschwindigkeitsvektor velocity und zum Anderen der Lenkvektor steer.
Da der Wendekreis hauptsächlich abhängig von der Geschwindigkeit und dem Lenkeinschlagswinkel ist, sollten diese beiden Vektoren reichen.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 12
z
angle = arctan (steer / velocity)
angle = arctan (3 / 4)
angle = 36,87°
steer = 3
angle
velocity = 4
x
In der Skizze sieht man deutlich, dass man für den Winkel nur die beiden Vektoren als
Eingabewerte benötigt. Da wir in dem rechtwinkligen Dreieck nur die beiden Katheten
vorgegeben haben, müssen wir den Winkel mittels Tangens berechnen. Also der Tangens
vom Winkel angle ist gleich Gegenkathete durch Ankathete. Da wir den Winkel aber als
Ergebnis erhalten möchten, brauchen wir die Umkehrfunktion vom Tangens. Also die
Formel wie sie oben in der Skizze steht. Des weiteren sollte aufgefallen sein, dass man
den Quotienten steer durch velocity nicht berechnen kann, wenn die Geschwindigkeit
gleich Null ist. Dies ist aber auch nicht schlimm, da das Fahrzeug, wenn es nicht rollt, sich
auch nicht drehen können muss. Es ist nur darauf zu achten, das mit einer if-Bedingung
die Division durch Null verhindert. Dies ist mit if (velocity != 0) einfach getan.
Der alte Winkel war 0° und der neue ist jetzt ca. 36,87°. Wenn das Fahrzeug weiter lenkt,
dann wird der wiederum neu berechnete Wert auf den alten aufaddiert. Damit der Winkel
immer zwischen 0° und 360° liegt, benötigt man noch zwei weitere Bedingungen. Einmal,
falls der Winkel negativ werden sollte (in der Skizze oben z.B. genau in die andere
Richtung lenkt, also steer = -3), wird zu dem negativen Winkel 360° aufaddiert. Die
zweite Bedingung ist, falls der Winkel größer als 360° wird, wird 360° von dem zu großen
Winkel subtrahiert.
Wenn man jetzt noch präziser vorgehen möchte, dann ist der Wendekreis auch noch
abhängig vom Radstand. Je größer der Radstand (das bedeutet der Abstand zwischen
Vorderachse und Hinterachse), desto größer auch der Wendekreis. In meiner Skizze habe
ich darauf verzichtet um sie nicht zu kompliziert zu machen, im Quellcode aber
berücksichtigt und mit einem Kommentar auch kurz erklärt.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 13
Nun haben wir den neuen Winkel und benötigen nur noch die neue Position des
Fahrzeuges.
z
radius = sqrt (velocity² + steer²)
radius = sqrt (4² + 3²)
radius = 5
x = raduis * cos (angle)
z = radius * sin (angle)
steer = 3
velocity = 4
x
Dazu berechnen wir erst einmal die Länge des Vektors radius. Da wir ein rechtwinkliges
Dreieck haben, können wir den Satz des Pythagoras anwenden. Die beiden Katheten
quadrieren, dann zusammenaddieren und daraus die Quadratwurzel gezogen. Schon
haben wir den Betrag des Vektors radius. Die letzte Aufgabe ist es noch, die genaue
Position zu ermitteln. Der Cosinus von dem Winkel angle mal des eben errechneten
radius
ergibt die x-Koordinate und der Sinus von dem Winkel angle mal dem radius ergibt
die z-Koordinate. Die neu errechneten Koordinaten werden dann noch auf die alten
addiert, analog wie beim Winkel.
Dies war die Kernberechnung des Fahrzeugmodells. Die anderen Berechnungen empfand
ich als nicht so wichtig, daher führe ich sie hier auch nicht weiter auf. Sie sind aber im
Quellcode kommentiert.
4.1.8 3D-Modelle importieren
Daniel Bogdan
Grafische Interaktive Systeme
Seite 14
Um 3D-Modelle vom Typ 3DS oder OBJ zu importieren, benötigt man eine Variable vom
Typ GLModel. Diese Klasse wird auch von der Zusatzbibliothek von [POTATO]
bereitgestellt. In der init() - Methode erzeugt man sich dann ein neues Objekt von der
Klasse GLModel und gibt als Parameter den Pfad zu dieser 3DS- oder OBJ-Datei. Hierbei
ist zu beachten, dass der Pfad diesmal von dem src-Ordner ausgeht und nicht von dem
Paket „lib“. Obwohl die Klasse GLModel genauso wie die Klasse GLImage im Paket „lib“
liegt. Der Aufruf sehe bei mir dann wie folgt aus:
private GLModel
car;
...
public void init ()
{
...
car = new GLModel ("res/models/AUDI_5.2_FSI/R8.obj");
...
}
Obwohl das Fahrzeug [AUDI] eigene Texturen mitgeliefert hat, werden diese nicht mit
eingebunden. Ich habe versucht den Grund dafür herauszufinden, aber dies wurde mir
dann zu komplex und die vorgegebene Zeit knapp. Man kann zwar eine Textur auf das
Modell legen, das aber dann nur ein Bild ist und dementsprechend nicht schön aussieht.
Der Aufruf hieße aber
car.setTexture (carTexHandle);
und stünde in der init() - Methode direkt nach dem Erzeugen des Objektes.
Was jetzt noch fehlt, ist in der render() - Methode das Objekt zu zeichnen. Da ich schon
Texturen verwendet habe und die für das Objekt dann auch benutzt werden, obwohl ich
das nicht will (siehe Problem vom Unterpunkt Texturen auf Objekte), muss ich für diesen
Moment die Texturen deaktivieren. Der Quellcode sehe dann folgendermaßen aus:
public void render ()
{
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glPushMatrix ();
{
...
/* Evtl hier die Materialeigenschaften */
...
/* Hier stehen dann die Translations-, */
...
/* Rotations-, und Skalierungsmatrizen */
car.render ();
}
GL11.glPopMatrix ();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
Die Transformationsmatrix, die als erstes ausgeführt werden soll, steht den Geometrie-
Daniel Bogdan
Grafische Interaktive Systeme
Seite 15
Daten am nächsten, also am weitesten unten. Da ich dem Objekt keine Textur gebe, heißt
das noch lange nicht, dass es lediglich weiß aussehen muss. Mit der Materialeigenschaft
kann ich dem Objekt auch eine andere Farbe verpassen.
4.1.9 Screenshots
Daniel Bogdan
Grafische Interaktive Systeme
Seite 16
4.2 Fazit
Das ist nun das fertige Programm. Dies ist zwar kein eigenständiges Spiel, aber es wird
deutlich, was alles mit der Grafik-Bibliothek machbar ist. Theoretisch kann auf das
Programm noch weiter aufgebaut und es in allen Bereichen erweitert werden. Während
der Semesterferien werde ich sicher weiter daran arbeiten.
Jedenfalls weiß ich jetzt wie sich Spieleentwickler fühlen. Am Anfang nimmt man sich so
viel vor und am Ende reicht die Zeit nicht. Da ich die vorgegebene Zeit nicht verlängern
konnte, war ich eben gezwungen einige Sachen streichen. Ich wollte zudem SoundDateien einbinden, die man an einer bestimmten Stelle in der 3D Welt hören kann und
immer leiser werden, wenn man sich weiter entfernt. Des weiteren wollte ich auch Text auf
dem Bildschirm ausgeben lassen, wie schnell das Fahrzeug beispielsweise fährt. Also eine
Art Tachometer.
Dies hatte mich schon ein wenig verärgert, aber ließ sich leider nicht ändern. Spaß hat es
trotzdem bereitet und ich würde mich auch immer wieder freiwillig daran setzen.
Ich hoffe, ich konnte mit meiner Ausarbeitung verdeutlichen, dass es gar nicht mal so
schwer ist, eine 3D Welt zu erzeugen und in dieser auch interaktiv zu agieren.
Vorausgesetzt, man arbeitet mit den vorgefertigten Bibliotheken. Das Rad neu zu erfinden
ist anstrengend und macht auch keinen Sinn.
Als ich Anfang des Jahres meine Quellen überprüfte, habe ich festgestellt, dass diese
Zusatzbibliothek von [POTATO] enorm überarbeitet wurde. Sie wurde auch auf die Version
2.0 von der LWJGL angepasst. Als ich mir ein paar Demos angesehen hatte, war ich fast
schon enttäuscht. Nicht, weil die Weiterentwicklung schlecht war, sondern eher im
Gegenteil. Der erste Eindruck von dem Quellcode hat mir gezeigt, dass man mit weniger
Aufwand noch mehr erreichen konnte. Zum Beispiel funktionierte in der einen Demo das
korrekte Laden der Textur für ein 3D-Modell oder die Schatten-Berechnung wurde mit
integriert. Ich habe mich dann gefragt, warum dies nicht schon vor drei Monaten heraus
gekommen ist. Das hätte mir jede Menge Zeit erspart. Aber nun gut, ich denke, dass es
dadurch auch für die nachfolgenden Studierenden ein Anreiz ist, in diese Richtung weiter
zu entwickeln.
Denn den Vorteil wird es weiterhin besitzen, dass Java und OpenGL plattformunabhängig
und nicht nur auf Windows ausführbar sind.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 17
4.3 Quellen
[LOGO]
Das Logo von LWJGL auf der Titelseite
http://upload.wikimedia.org/wikipedia/de/6/6c/LWJGL.png
[LWJGL]
Die Seite, von der ich die eigentliche Grafik-Bibliothek runter laden konnte.
Zu finden gibt es da auch noch viel Informationen und abspielbare Demos
http://lwjgl.org/
[LWJGL_C] Auf dieser Seite sieht man die gesamten Änderungen von allen LWJGL
Versionen.
http://lwjgl.org/changelog.php
[POTATO]
Die Seite, von der ich die Zusatzbibliothek bekommen habe und jede Menge
Beispielcode. Leider existiert die Art von Version nicht mehr, die ich hier
benutzt habe, aber ich habe die Zip-Datei mit auf den Datenträger gepackt.
http://www.potatoland.com/code/gl/
[AUDI]
Das Fahrzeugmodell Audi, das ich in meinem praktischen Beispiel benutze
http://gfx-3d-model.blogspot.com/2008/12/audi-r8-52-fsi-car-model.html
[TEXTUR]
Jede Menge gute und kostenlose Texturen
http://www.mediacastle.de/texturen/metalltexturen/index.html
[3D_MOD]
Eine Seite, auf der es verschieden 3D Modell kostenlos zum runter laden gibt
http://www.blogcatalog.com/blogs/free-3d-models-for-maya-andmax/posts/tag/vehicles/
Daniel Bogdan
Grafische Interaktive Systeme
Seite 18
5. Anhang
5.1 Anwenderdokumentation
Das Programm findet man im Unterordner Programm und startet man über die run.bat
Datei.
Folgende Befehle sind enthalten:
Taste
Beschreibung
A
Beschleunigt das Fahrzeug
Y
Bremst das Fahrzeug
,
Lenkt nach links
.
Lenkt nach rechts
NUM7
Kamera nach oben blicken (nur bei Frei-Sicht-Modus (F2))
NUM9
Kamera nach unten blicken (nur bei Frei-Sicht-Modus (F2))
NUM4
Kamera nach links um das Fahrzeug schwenken
NUM6
Kamera nach rechts um das Fahrzeug schwenken
NUM1
Kamera dreht sich nach links um die eigene Achse (nur bei Frei-Sicht-Modus
(F2))
NUM3
Kamera dreht sich nach rechts um die eigene Achse (nur bei Frei-SichtModus (F2))
NUM8
Kamera bewegt sich nach oben
NUM2
Kamera bewegt sich nach unten
NUM-
Kamera entfernt sich vom Objekt (nur bei Verfolgungs-Modus (F2))
NUM+
Kamera nähert sich dem Objekt (nur bei Verfolgungs-Modus (F2))
F2
Umschalten zwischen Verfolgungs-Modus und Frei-Sicht-Modus
Pfeil oben Kamera bewegt sich nach vorne
Pfeil unten Kamera bewegt sich nach hinten
Pfeil links Kamera bewegt sich nach links
Pfeil rechts Kamera bewegt sich nach rechts
N
Nebel wird eingeschaltet
L
Licht in der Garage wird eingeschaltet
Um die Tastaturbelegung zu ändern, muss man in der Klasse Control die Methode
setKey()
finden. Ich wollte es eigentlich über das Programm einstellen lassen können,
aber die Zeit hat nicht mehr gereicht.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 19
5.2 Eingesetzte Werkzeuge
Eingesetztes Betriebssytem:
Windows XP Pro
Service Pack 3
Wurde für alle Arbeiten eingesetzt.
Eingesetzte Hardware:
Prozessor:
AMD Athlon 3200+
Arbeitsspeicher:
1536 MB
Grafikkarte:
NVIDIA GeForce 6800 mit 128 MB Speicher
Eingesetzte Software:
Eclipse Version 3.3.0
mit JRE 1.6.0_07
Wurde für die ganze Programmierarbeit benutzt.
LWJGL Version 2.0
Als Grafik-Bibliothek, um mit Java auf OpenGL Befehle zugreifen zu können.
Ein paar Klassen von [email protected]
In der Ausarbeitung als Zusatzbibliothek benannt.
Mozilla Firefox Version 3.0.5
Wurde zum suchen nach Beispielcode benutzt. Und für sonstige Informationen zu
beschaffen.
Open Office Version 2.4
Wurde benutzt um die Ausarbeitung zu schreiben und die Präsentation zu erstellen.
UMLet Version 9.03
Wurde benutzt um das Klassendiagramm zu zeichnen.
Daniel Bogdan
Grafische Interaktive Systeme
Seite 20
5.3 Darstellung des Aufwands
1%
19%
49%
Prototypen
Programmierarbeit
Fehlersuche
Ausarbeitung
Anwenderdokumentation
10%
21%
Bezeichnung
Dauer
Prototypen
Programmierarbeit
Fehlersuche
Ausarbeitung
Anwenderdokumentation
Zusammen:
Daniel Bogdan
70 Stunden
30 Stunden
15 Stunden
27 Stunden
2 Stunden
Zeitraum
Oktober – Dezember
Dezember – Januar
Oktober – Januar
Dezember – Januar
Januar
144
Grafische Interaktive Systeme
Seite 21

Documents pareils