Du bist nicht angemeldet.

JuKu

Profi

  • »JuKu« ist der Autor dieses Themas

Beiträge: 574

Registrierungsdatum: 29.09.2011

Danksagungen: 48

  • Private Nachricht senden

1

23.08.2015, 19:30

Eine kleine Einführung in LWJGL mit OpenGL

LWJGL

Was ist LWJGL und wofür steht es?
LWJGL steht für Leightweight Java Game Library und ist eine Library für OpenGL, OpenAL und OpenCL Anwendungen.
LWJGL ist sehr schnell und wird in vielen Graphic Engines verwendet.

Das erste Fenster mit LWJGL

Java-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glViewport;

public class Main {

    public static void main (String[] args) {
        Main main = new Main();
        main.createWindow();
    }

    public void createWindow () {
        try {
            Display.setDisplayMode(new DisplayMode(800, 600));
            Display.setResizable(true);
            Display.setTitle("LWJGL Test");
            Display.setVSyncEnabled(true);
            Display.create();

           //setzt die clear Color, also die Color, die gezeichnet wird, wenn nichts auf der Fläche ist
           glClearColor(0f, 0f, 0f, 0f);

           glDisable(GL_DEPTH_TEST);

            resize();

            while(!Display.isCloseRequested()) {
                if (Display.wasResized()) {
                    //legt den Ausschnitt, der gezeichnet werden soll, neu fest
                    resize();
                }

                //Löscht das Fenster bzw. den Background Buffer
                glClear(GL_COLOR_BUFFER_BIT);

                render();
                
                //zeichnet den Background Buffer auf den Frontend Buffer und zeichnet somit das Fenster neu
                Display.update();
            }

            Display.destroy();
        } catch (LWJGLException e) {
            e.printStackTrace();
        }
    }

    public void render () {
        //render Methode
    }

    private void resize() {
        glViewport(0, 0, Display.getWidth(), Display.getHeight());
    }

}


Wenn ihr Maven nutzt, könnt ihr einfach LWJGL in eure pom.xml einbinden:

Spoiler Spoiler

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>com.jukusoft.lwjgltest</artifactId>
        <groupId>com.jukusoft</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>com.jukusoft.lwjgltestmain</artifactId>

    <repositories>
        <repository>
            <id>mavenNatives</id>
            <name>Maven Natives Repository</name>
            <url>http://mavennatives.googlecode.com/svn/repo</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>

        <repository>
            <id>slick</id>
            <name>slick</name>
            <url>http://slick.cokeandcode.com/mavenrepo</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.lwjgl.lwjgl</groupId>
            <artifactId>lwjgl</artifactId>
            <version>2.8.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.jukusoft.lwjgltest.Main</mainClass>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.googlecode.mavennatives</groupId>
                <artifactId>maven-nativedependencies-plugin</artifactId>
                <version>0.0.7</version>
                <executions>
                    <execution>
                        <id>unpacknatives</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>


Mit Display.setDisplayMode(new DisplayMode(800, 600)) setzt ihr die Breite und die Höhe eures Fensters, welches mit OpenGL gezeichnet werden soll.
Display.setResizable(true) erlaubt es dem User, die Fenstergröße zu verändern und mit Display.setTitle("LWJGL Test") könnt ihr den Titel des Fensters setzen.
Display.setVSyncEnabled(true) aktiviert VSync und mit Display.create() wird das Fenster schließlich erstellt und das erste mal gezeichnet.

LWJGL nutzt einen Background Buffer, auf dem gezeichnet wird, um das Flackern des Bildes zu verhindern.
Damit die Änderungen dann auch gezeichnet werden, muss Display.update() aufgerufen werden. Diese Methode zeichnet den Background Buffer auf den Frontend Buffer und zeichnet euer Fenster somit neu.
Display.destroy() entfernt euer Fenster wieder vom Fenster Manager eures Betriebssystems und gibt die Resourcen frei.

In eurem Fenster könnt ihr auch größer als 800x600 zeichnen, aber es wird lediglich ein Ausschnitt angezeigt. Diesen Ausschnitt, der in einer 3D Graphics Engine dann auch als Camera bezeichnet wird, könnt ihr mit glViewport() festlegen. glViewport(0, 0, Display.getWidth(), Display.getHeight()) setzt den Ausschnitt also von 0x0 bis 800x600.

OpenGL Optionen
OpenGL bietet mehrere Optionen, u.a. kann z.B. ein Alpha Kanal aktiviert werden usw.
Um eine Option zu deaktivieren oder zu aktivieren, gibt es die Methoden glDisable() und glEnable().

Um z.B. den Alpha Kanal zu aktivieren, kann man den folgenden Code verwenden:

Java-Quelltext

1
2
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


Wichtig!: Die OpenGL Implementierung arbeitet nur mit statischen Methoden, die mit statischen imports importiert werden müssen.

OpenGL 11:

Java-Quelltext

1
2
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;


OpenGL 15:

Java-Quelltext

1
2
import static org.lwjgl.opengl.GL15.glDisable;
import static org.lwjgl.opengl.GL15.glEnable;


Im Code oben wird z.B. die Option GL_DELPTH_TEST nicht benötigt, weshalb diese mit glDisable(GL_DEPTH_TEST) deaktiviert wird.
Um diese zu aktivieren, muss glEnable(GL_DEPTH_TEST) verwendet werden.
GL_DELPH_TEST sorgt dafür, dass alle Bilder in der richtigen Reihenfolge gerendert werden und prüft, ob diese überhaupt gerendert werden müssen.
Dazu wird eine Tiefenprüfung durchgeführt.
Da in den meisten 2D Games aber schon vom Programm Ablauf die Reihenfolge gewährleistet sein sollte, wird diese Option hier nicht benötigt.
3D Games würden diese Option wieder benötigen.

Ein Bild zeichnen
Ein Bild ist auf einem Computer nichts anderes, als ein byte Array.
Bilder werden meist als 8 Bit RPGA gespeichert, also Rot Grün Blau Alpha Array.

Zitat


Java-Quelltext

1
2
3
4
5
6
new byte[ imageWidth * imageHeight * 4 ] {
0x00, 0x00, 0x00, 0x00, //Pixel index 0, position (x=0, y=0), transparent black
0xFF, 0x00, 0x00, 0xFF, //Pixel index 1, position (x=1, y=0), opaque red
0xFF, 0x00, 0x00, 0xFF, //Pixel index 2, position (x=2, y=0), opaque red
... etc ...
}


Quelle: https://github.com/mattdesl/lwjgl-basics/wiki/Textures


In diesem Beispiel benötigt jeder Pixel 4 Bytes, für RGB würde man nur 3 Bytes benötigen, da dann nur die Werte der Farben Rot Grün und Blau gespeichert werden müssten, da wir aber mit RGBA auch einen Alpha Kanal besitzen, also Transparenz unterstützen wollen, benötigen wir für jeden Pixel 4 Byte. Da ein solches Byte Array aber sehr groß werden kann, nutzt man heutzutage Formate mit Kompression, wie PNG oder JPG, welche die Dateigröße reduzieren und somit Speicherplatz sparen.

OpenGL nutzt Texturen, um die Bilddaten zu speichern. Eine Texture ist ein float Array, welches auf der GPU gespeichert wird und später noch z.B. mit Filtern, Masken usw. bearbeitet werden kann.

Um ein Bild in eine Texture zu konvertieren, muss dieses Bild in ein RGBA byte Array konvertiert werden.
Als nächstes benötigt man von der GPU eine ID, mit der man die Texture zur Graphic Card hochladen und auch später noch darauf zugreifen kann.
Dann muss die Texture mit OpenGL gebunden und die Parameter gesetzt werden, bevor das byte Array dann zur GPU bzw. zu OpenGL hochgeladen werden kann.

Als nächstes muss also die PNG Datei in ein byte Array decodiert und in einem NIO ByteBuffer gespeichert werden.
NIO steht hierbei für New Input Output Api.
Eine Einführung dazu gibt es auf der Website https://github.com/mattdesl/lwjgl-basics…ava-NIO-Buffers .
Ein ByteBuffer ist ein reservierter Speicher Block im RAM, welcher beschrieben werden kann.

Spoiler Spoiler


Java-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//LWJGL includes utilities for easily creating buffers
ByteBuffer buffer = BufferUtils.createByteBuffer(4);

//"relative put" method, which places the byte and
//then moves the position forward
buffer.put(a);
buffer.put(b);
buffer.put(c);
buffer.put(d);

//flip the position to reset the relative position to zerobuffer.
flip();

//loop through all of the bytes that were written, using "relative get"
for (int i=0; i<buffer.limit(); i++) {
    System.out.println( buffer.get() );
}


Quelle: https://github.com/mattdesl/lwjgl-basics…-Buffers//LWJGL

Der ByteBuffer arbeitet wie ein Stapelspeicher, mit put() wird immer ein byte drauf gesetzt. Die Position wird dabei immer um 1 erhöht.
buffer.put(a) speichert also den byte a, und setzt die Position von 0 auf 1.
Bei buffer.put(d) steht die Position dann auf 4.
Um den ByteBuffer auslesen zu können, muss diese Position aber wieder auf 0 gesetzt werden, was die Methode flip() erledigt.


Um ein PNG Bild in ein byte Array zu dekodieren, gibt es bereits fertige Libraries, z.B. http://hg.l33tlabs.org/twl/file/tip/src/…PNGDecoder.java .

Zitat


Java-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//get an InputStream from our URL
input = pngURL.openStream();

//initialize the decoder
PNGDecoder dec = new PNGDecoder(input);

//read image dimensions from PNG header
width = dec.getWidth();
height = dec.getHeight();

//we will decode to RGBA format, i.e. 4 components or "bytes per pixel"
final int bpp = 4;

//create a new byte buffer which will hold our pixel data
ByteBuffer buf = BufferUtils.createByteBuffer(bpp * width * height);

//decode the image into the byte buffer, in RGBA format
dec.decode(buf, width * bpp, PNGDecoder.Format.RGBA);

//flip the buffer into "read mode" for OpenGL
buf.flip();



Als nächstes muss die Texture erstellt und gebunden werden.
Wenn diese gebunden wird, ist diese sozusagen aktiv und alle folgenden OpenGL Befehle, Filter, usw. werden auf diese angewandt.

Java-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Aktiviere Texturen
glEnable(GL_TEXTURE_2D);

//generiere eine Texture ID
int id = glGenTextures();

//Texture binden
glBindTexture(GL_TEXTURE_2D, id);

//use an alignment of 1 to be safe
//this tells OpenGL how to unpack the RGBA bytes we will specify
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

//Hier können weitere Parameter folgen

//RGBA Texture zur GPU hochladen
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
Wenn euch mein Beitrag weitergeholfen hat, drückt auf "Bedanken"!
Danke! :D

Dieser Beitrag wurde bereits 21 mal editiert, zuletzt von »JuKu« (23.08.2015, 20:55)


Es haben sich bereits 2 registrierte Benutzer bedankt.

Benutzer, die sich für diesen Beitrag bedankt haben:

Patrick (25.08.2015), Erik (19.09.2015)

JuKu

Profi

  • »JuKu« ist der Autor dieses Themas

Beiträge: 574

Registrierungsdatum: 29.09.2011

Danksagungen: 48

  • Private Nachricht senden

2

25.11.2016, 19:45

Da dieses Tutorial noch LWJGL 2 nutzt, mittlerweile aber sogar schon LWJGL 3.1 ershienen ist, habe ich auf meiner Webseite jukusoft.com eine neue Tutorial Serie zu LWJGL 3.1 gestartet:
https://jukusoft.com/2016/11/25/tutorial…lfw-mit-opengl/

Vllt. nützt es ja dem ein oder anderem noch was. :D
Feedback ist gerne gesehen und erwünscht! :D
Wenn euch mein Beitrag weitergeholfen hat, drückt auf "Bedanken"!
Danke! :D

Ähnliche Themen

Verwendete Tags

Java, lwjgl, OpenGL