# OpenGL: Eine Kugel



## temi (22. Jan 2021)

Ich beschäftige mit gerade etwas mit OpenGL und versuche eine Kugel zu erzeugen.

Die Idee ist es einen Würfel zu erstellen und diesen anschließen "aufzublasen".

Dazu habe ich eine Klasse Quad, die eine Fläche mit einer bestimmten Orientierung erzeugt und eine Klasse Sphere, die für jede Richtung eine entsprechende Fläche erzeugen soll und diese dann in je ein Array mit Vertices und Indices für den Mesh kopieren soll.

Solange ich nur eine einzelne Seite erzeuge scheint das auch zu funktionieren. Sobald eine weitere Seite dazukommt, wird es konfus.

```
import org.joml.Vector3f;

public class Sphere {

    private static final int MIN_RESOLUTION = 2;
    private final static Vector3f[] DIRECTIONS = {
        //new Vector3f(1f, 0f, 0f),
        new Vector3f(-1f, 0f, 0f)
    };
    private final int resolution;
    private float[] vertices;
    private int[] indices;

    public Sphere(final int resolution) {
        this.resolution = resolution < MIN_RESOLUTION ? MIN_RESOLUTION : resolution;
        create();
    }

    public Mesh getMesh() {
        return new Mesh(vertices, indices);
    }

    private void create() {
        vertices = new float[resolution * resolution * 3 * (DIRECTIONS.length)];
        indices = new int[(resolution - 1) * (resolution - 1) * 6 * (DIRECTIONS.length)];

        for(int ixDirection = 0; ixDirection < DIRECTIONS.length; ixDirection++) {
            // create a new Quad for a given direction
            Quad quad = new Quad(resolution, DIRECTIONS[ixDirection]);

            // normalize and copy all vertices to the vertices array
            Vector3f[] quadVertices = quad.getVertices();
            int ixVertices = ixDirection * quadVertices.length;
            for (int ixQuadVertices = 0; ixQuadVertices < quadVertices.length; ixQuadVertices++) {
                Vector3f vertex = quadVertices[ixQuadVertices].normalize();
                vertices[ixVertices++] = vertex.x;
                vertices[ixVertices++] = vertex.y;
                vertices[ixVertices++] = vertex.z;
            }

            // copy all indices to the indices array
            int[] quadIndices = quad.getIndices();
            int ixIndices = ixDirection * quadIndices.length;
            for(int index : quadIndices) {
                indices[ixIndices++] = index;
            }
        }
    }
}
```



Das Ergebnis mit einer Seite (die andere funktioniert entgegengesetzt):

```
private final static Vector3f[] DIRECTIONS = {
        new Vector3f(1f, 0f, 0f),
        //new Vector3f(-1f, 0f, 0f)
    };
```



Und mit zwei Seiten:


----------



## temi (22. Jan 2021)

Einen Fehler habe ich bei der Berechnung des aktuellen Vertexindex gehabt:

```
int ixVertices = ixDirection * quadVertices.length * 3;
```
Aus einem Vektor werden ja drei Floats.

Der zweite Fehler lag bei den Indices. Da ja jeder Quad separat erzeugt wird, beginnt dessen Indexzuweisung auch immer wieder bei 0. Es wird hier ein Offset benötigt.

```
indices[ixIndices++] = index + ixDirection * quadVertices.length;
```



Ist zwar optisch keine Kugel, aber da fehlt nur eine entsprechende Korrektur der Perspektive.


----------



## temi (23. Jan 2021)

Der Vollständigkeit halber, hier noch der funktionierende Code für Quad und Sphere. Aus dem Code für die Sphere lässt sich ganz einfach ein Cube machen, indem die Normalisierung der Vertices weggelassen wird (Hier: `Vector3f normalizedVertex = quadVertex.normalize();`. Damit bleiben die Ebenen des Quad erhalten und werden nur kubisch angeordnet.


[CODE lang="java" title="Quad"]import org.joml.Vector2f;
import org.joml.Vector3f;

public class Quad {

    private static final int MIN_RESOLUTION = 2;
    private final int resolution;
    private final Vector3f faceNormal;
    private final Vector3f axisA;
    private final Vector3f axisB;
    private Vector3f[] vertices;
    private int[] indices;

    public Quad(final int resolution, final Vector3f faceNormal) {
        this.resolution = resolution < MIN_RESOLUTION ? MIN_RESOLUTION : resolution;
        this.faceNormal = faceNormal.normalize();
        axisA = new Vector3f(faceNormal.y, faceNormal.z, faceNormal.x);
        axisB = new Vector3f(faceNormal).cross(axisA);
        create();
    }

    Vector3f[] getVertices() {
        return vertices;
    }

    int[] getIndices() {
        return indices;
    }

    public Mesh getMesh() {
        return new Mesh(vertices, indices);
    }

    private void create() {
        vertices = new Vector3f[resolution * resolution];
        indices = new int[(resolution - 1) * (resolution - 1) * 6];
        int ixVertices = 0;
        int ixIndices = 0;
        for (int y = 0; y < resolution; y++) {
            for (int x = 0; x < resolution; x++) {
                Vector2f percent = new Vector2f(x, y).div(resolution - 1);
                Vector3f a = new Vector3f(axisA).mul((percent.x - 0.5f) * 2);
                Vector3f b = new Vector3f(axisB).mul((percent.y - 0.5f) * 2);

                vertices[ixVertices] = new Vector3f(faceNormal).add(a).add(b);

                if (x < resolution - 1 && y < resolution - 1) {
                    indices[ixIndices++] = ixVertices;
                    indices[ixIndices++] = ixVertices + resolution + 1;
                    indices[ixIndices++] = ixVertices + resolution;
                    indices[ixIndices++] = ixVertices;
                    indices[ixIndices++] = ixVertices + 1;
                    indices[ixIndices++] = ixVertices + resolution + 1;
                }
                ixVertices++;
            }
        }
    }
}
[/CODE]
[CODE lang="java" title="Sphere"]import org.joml.Vector3f;

public class Sphere {

    private static final int MIN_RESOLUTION = 2;
    private final static Vector3f[] DIRECTIONS = {
            new Vector3f(1f, 0f, 0f), // right
            new Vector3f(0f, 1f, 0f), // top
            new Vector3f(0f, 0f, 1f), // front
            new Vector3f(-1f, 0f, 0f), // left
            new Vector3f(0f, -1f, 0f), // bottom
            new Vector3f(0f, 0f, -1f) // back
    };
    private final int resolution;
    private float[] vertices;
    private int[] indices;

    public Sphere(final int resolution) {
        this.resolution = Math.max(resolution, MIN_RESOLUTION);
        create();
    }

    public Mesh getMesh() {
        return new Mesh(vertices, indices);
    }

    private void create() {
        vertices = new float[resolution * resolution * 3 * (DIRECTIONS.length)];
        indices = new int[(resolution - 1) * (resolution - 1) * 6 * (DIRECTIONS.length)];

        for(int ixDirection = 0; ixDirection < DIRECTIONS.length; ixDirection++) {
            // create a new Quad for a given direction
            Quad quad = new Quad(resolution, DIRECTIONS[ixDirection]);

            // normalize and copy all vertices to the vertices array
            Vector3f[] quadVertices = quad.getVertices();
            int ixVertices = ixDirection * quadVertices.length * 3;
            for (Vector3f quadVertex : quadVertices) {
                Vector3f normalizedVertex = quadVertex.normalize();
                vertices[ixVertices++] = normalizedVertex.x;
                vertices[ixVertices++] = normalizedVertex.y;
                vertices[ixVertices++] = normalizedVertex.z;
            }

            // copy all indices to the indices array
            int[] quadIndices = quad.getIndices();
            int ixIndices = ixDirection * quadIndices.length;
            for(int quadIndex : quadIndices) {
                indices[ixIndices++] = quadIndex + ixDirection * quadVertices.length;
            }
        }
    }
}
[/CODE]


----------



## httpdigest (23. Jan 2021)

temi hat gesagt.:


> `Vector3f normalizedVertex = quadVertex.normalize();`


Bitte immer darauf achten, dass im JOML alle Methoden, die keinen expliziten `dest` (out) Parameter haben, immer `this` aktualisieren, also keine neue Instanz zurückliefern. Hier wird also `quadVertex` selbst verändert.
Siehe: https://github.com/JOML-CI/JOML/wik...-a-dest-parameter-modify-this-and-return-this
(und natürlich immer die JavaDocs der Methoden lesen  )


----------



## temi (23. Jan 2021)

httpdigest hat gesagt.:


> Bitte immer darauf achten, dass im JOML alle Methoden, die keinen expliziten `dest` (out) Parameter haben, immer `this` aktualisieren, also keine neue Instanz zurückliefern. Hier wird also `quadVertex` selbst verändert.
> Siehe: https://github.com/JOML-CI/JOML/wik...-a-dest-parameter-modify-this-and-return-this
> (und natürlich immer die JavaDocs der Methoden lesen  )


Danke für den Hinweis! 

Da bin ich bei Quad schon drüber gestolpert, wo sich die Verschiebevektoren plötzlich geändert hatten.

Da du irgendwo erklärt hast, dass Computergrafik dein Steckenpferd ist, hast du einen Tipp, wie ich da eine Textur drüber legen kann?

Ich dachte an Cubemaps, aber bin noch am Probieren, wie ich überhaupt mit Texturen umgehen muss. Wie ganz oben geschrieben, sind das meine ersten Schritte in OpenGL. Immerhin habe ich jetzt schon mal die Projektions-, Welt- und Viewmatrix drin, so dass die Kugel jetzt auch eine Kugel ist und ich mich im Raum bewegen kann.


----------



## httpdigest (23. Jan 2021)

temi hat gesagt.:


> Da du ja bereits irgendwo erklärt hast, dass Computergrafik dein Steckenpferd ist...


Nicht nur das, bin auch LWJGL Committer, hauptsächlich für das lwjgl3-demos Repository: https://github.com/LWJGL/lwjgl3-demos
Da sind für dich vielleicht interessante Sachen drin. Z.B. die hier: https://github.com/LWJGL/lwjgl3-demos/tree/main/src/org/lwjgl/demo/opengl/textures

In OpenGL gibt es aber unzählige Wege, dasselbe zu erreichen. Also z.B. nutzt du Shader? Nutzt du noch die alte fixed-function Pipeline? Nutzt du Vertex Array Objects mit Vertex Buffer Objects oder den immediate mode mit glBegin/glEnd, etc. etc.


----------



## temi (23. Jan 2021)

httpdigest hat gesagt.:


> Nicht nur das, bin auch LWJGL Committer, hauptsächlich für das lwjgl3-demos Repository: https://github.com/LWJGL/lwjgl3-demos
> Da sind für dich vielleicht interessante Sachen drin. Z.B. das hier: https://github.com/LWJGL/lwjgl3-dem.../demo/opengl/textures/SimpleTexturedQuad.java
> 
> In OpenGL gibt es aber unzählige Wege, dasselbe zu erreichen. Also z.B. nutzt du Shader? Nutzt du noch die alte fixed-function Pipeline? Nutzt du Vertex Array Objects mit Vertex Buffer Objects oder den immediate mode mit glBegin/glEnd, etc. etc.


Sehr cool!

Ich nutze LWJGL und Shader und VAOs und VBOs. Ich halte mich derzeit etwas an https://ahbejarano.gitbook.io/lwjglgamedev/, das scheint mir recht ordentlich zu sein. Ich habe mir aber heute auch "Learning OpenGL" von https://learnopengl.com/ heruntergeladen und bin da auf die Cubemaps gestoßen.

Ich muss allerdings jetzt erst mal noch ein paar Schritte zurück gehen und erst mal einfache Texturen ausprobieren.


----------



## httpdigest (23. Jan 2021)

Nice! Na dann, viel Spass und Erfolg dabei! 

Falls du nähere Fragen zu LWJGL und/oder OpenGL hast, die vielleicht zu spezifisch für java-forum.org sind, dann sind viele Leute auch aktiv bei:
- java-gaming.org
- forum.lwjgl.org
- slack.lwjgl.org (Registrierungsseite und Weiterleitung auf den LWJGL Slack Channel)
- und es gibt noch einen inoffiziellen LWJGL Discord Channel (meistens Minecraft-spielende Schulkinder mit Minecraft-problemen oder Minecraft-clone-Projekten)


----------

