Licht in OpenGL

Hier mal mein Projekt als Zip:Licht.zip

Wenn Du noch Probleme mit Matrizen hast, macht es das einfacher wenn Du alles löschst was nicht unbedingt notwendig ist. Je kleiner das wird, desto einfacher sind Fehler zu finden.

Unnötiges gelöscht und nur das nötigste korrigiert:



import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glDrawElements;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.PixelFormat;


public class ShaderTest {

    public static void main(final String[] args) throws LWJGLException, IOException {

        new ShaderTest();

    }


    private static final int VORNE = Keyboard.KEY_W;
    private static final int HINTEN = Keyboard.KEY_S;
    private static final int LINKS = Keyboard.KEY_A;
    private static final int RECHTS = Keyboard.KEY_D;

    private static final float[] VERTICES = {

            -1.0f, -1.0f, -10.0f,
            -1.0f, +1.0f, -10.0f,
            +1.0f, -1.0f, -10.0f,
            +1.0f, +1.0f, -10.0f

    };

    private static final int[] INDEX = {

            0, 1, 2, 2, 1, 3

    };

    private final FloatBuffer viewMatrix = BufferUtils.createFloatBuffer(16);
    private final FloatBuffer projMat = BufferUtils.createFloatBuffer(16);

    private float rotV;
    private float rotX;
    private float rotY;
    private float rotZ;

    private float posX;
    private float posY;
    private float posZ;
    private long stepTime;

    private final int vaoID;
    private final int vboID;
    private final Shader s;


    public ShaderTest() throws LWJGLException, IOException {

        final DisplayMode displayMode = new DisplayMode(800, 600);
        final PixelFormat pixelFormat = new PixelFormat();
        final ContextAttribs contextAttribs = new ContextAttribs(3, 3).withProfileCore(true);
        Display.setDisplayMode(displayMode);
        Display.create(pixelFormat, contextAttribs);
        Display.setVSyncEnabled(true);
        Mouse.setGrabbed(true);

        s = new Shader("vs.vertex", "fs.fragment");
        s.use();

        final IntBuffer indecies = putIntCoords(INDEX);

        vaoID = glGenVertexArrays();
        glBindVertexArray(vaoID);

        final FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(VERTICES.length);
        verticesBuffer.put(VERTICES).flip();
        vboID = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vboID);
        glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);

        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

        projMat.put(projection(45, (float) Display.getWidth() / (float) Display.getHeight(), 0.1f, 100));
        projMat.rewind();

        final int projectionLocation = GL20.glGetUniformLocation(s.program, "projection");
        final int modelviewLocation = GL20.glGetUniformLocation(s.program, "modelview");

        while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            final float deltaTime = -(stepTime - (stepTime = System.nanoTime())) / 100000000.0f;
            firstPersonControls(deltaTime, viewMatrix);

            GL20.glUniformMatrix4(projectionLocation, false, projMat);
            GL20.glUniformMatrix4(modelviewLocation, false, viewMatrix);

            glDrawElements(GL_TRIANGLES, indecies);

            Display.update();

        }

        Display.destroy();

    }


    private void firstPersonControls(final float deltaTime, final FloatBuffer viewMatrix) {

        final double keySpeed = deltaTime * 0.5;
        final double mouseSpeed = 0.002;

        final float rotVmax = 1;
        final float rotVMin = -1;

        rotY += Mouse.getDX() * mouseSpeed;
        rotV -= Mouse.getDY() * mouseSpeed;

        if (rotV > rotVmax)
            rotV = rotVmax;

        if (rotV < rotVMin)
            rotV = rotVMin;

        if (Keyboard.isKeyDown(VORNE)) {
            posX -= Math.sin(rotY) * keySpeed;
            posZ += Math.cos(rotY) * keySpeed;
        }

        if (Keyboard.isKeyDown(HINTEN)) {
            posX += Math.sin(rotY) * keySpeed;
            posZ -= Math.cos(rotY) * keySpeed;
        }

        if (Keyboard.isKeyDown(LINKS)) {
            posX += Math.cos(rotY) * keySpeed;
            posZ += Math.sin(rotY) * keySpeed;
        }

        if (Keyboard.isKeyDown(RECHTS)) {
            posX -= Math.cos(rotY) * keySpeed;
            posZ -= Math.sin(rotY) * keySpeed;
        }

        rotX = (float) (Math.cos(rotY) * rotV);
        rotZ = (float) (Math.sin(rotY) * rotV);

        final float[] mv = new float[16];

        final double sinX = Math.sin(rotX);
        final double sinY = Math.sin(rotY);
        final double sinZ = Math.sin(rotZ);

        final double cosX = Math.cos(rotX);
        final double cosY = Math.cos(rotY);
        final double cosZ = Math.cos(rotZ);

        mv[0] = (float) (cosY * cosZ + sinY * sinX * sinZ);
        mv[1] = (float) (cosX * sinZ);
        mv[2] = (float) (-sinY * cosZ + cosY * sinX * sinZ);
        mv[4] = (float) (-cosY * sinZ + sinY * sinX * cosZ);
        mv[5] = (float) (cosX * cosZ);
        mv[6] = (float) (sinY * sinZ + cosY * sinX * cosZ);
        mv[8] = (float) (sinY * cosX);
        mv[9] = (float) (-sinX);
        mv[10] = (float) (cosY * cosX);
        mv[12] = mv[0] * posX + mv[4] * posY + mv[8] * posZ;
        mv[13] = mv[1] * posX + mv[5] * posY + mv[9] * posZ;
        mv[14] = mv[2] * posX + mv[6] * posY + mv[10] * posZ;
        mv[15] = 1;

        viewMatrix.position(0);
        viewMatrix.put(mv);
        viewMatrix.flip();

    }


    public static IntBuffer putIntCoords(final int[] ints) {

        final ByteBuffer vbb = ByteBuffer.allocateDirect(ints.length * 4);
        vbb.order(ByteOrder.nativeOrder());
        final IntBuffer vertexBuffer = vbb.asIntBuffer();
        vertexBuffer.put(ints);
        vertexBuffer.position(0);
        return vertexBuffer;

    }


    public static FloatBuffer putFloatCoords(final float[] coords) {

        final ByteBuffer vbb = ByteBuffer.allocateDirect(coords.length * 4);
        vbb.order(ByteOrder.nativeOrder());
        final FloatBuffer vertexBuffer = vbb.asFloatBuffer();
        vertexBuffer.put(coords);
        vertexBuffer.position(0);
        return vertexBuffer;

    }


    private float[] projection(final float fovy, final float aspect, final float zNear, final float zFar) {

        final double f = (1.0 / Math.tan(Math.toRadians(fovy / 2.0)));

        final float[] m = new float[16];

        m[0] = (float) (f / aspect);
        m[5] = (float) (f);
        m[10] = (zFar + zNear) / (zNear - zFar);
        m[11] = (-1);
        m[14] = (2 * zFar * zNear) / (zNear - zFar);
        m[15] = 0;

        return m;

    }


}
#version 330

uniform mat4 projection;                
uniform mat4 modelview;

layout (location = 0) in vec3 in_Position;

void main() {

    gl_Position = projection * modelview * vec4(in_Position, 1);

}

#version 330

out vec4 color;

void main() {

	color = vec4(1, 1, 1, 1);

}

Ansonsten wäre es vermutlich keine schlechte Idee, wenn Du Dir meine Beispiele nochmal ansiehst.

Gruß
Fancy

Ok, ich habe jetzt im Testprojekt einen Fragment Shader gebastelt, der eineigermaßen gutes Licht macht. Nun möchte ich mein Hauptprojekt auf Shader umstellen, kann zur Transformation usw. nicht mehr glTranslate oder ähnliches verwenden, darum mache ich es jetzt so, wobei ich dem Shader hinterher die modelMatrix übergebe:


glScale --> modelMatrix = matrixMultiply(modelMatrix, putFloatCoords(scale(scaleFactor, scaleFactor, scaleFactor)));

glRotate --> modelMatrix = matrixMultiply(modelMatrix, putFloatCoords(rotation(rotationAngle, rotDirectionX, rotDirectionY, rotDirectionZ)));

glTranslate --> modelMatrix = matrixMultiply(modelMatrix, putFloatCoords(translate(x, y, z)));

Wobei ich diese Methoden geschrieben habe, wo aber der Wurm drin sein muss, da ich wenn ich diese Matrix im Vertexshader mit einbeziehe nichts angezeigt wird.


public static float[] rotation(float angle, float x, float y, float z){
		float[] mx = new float[16];
		float[] my = new float[16];
		float[] mz = new float[16];
		if(x > 0){
			mx[0] = 1;
			mx[1] = 0;
			mx[2] = 0;
			mx[3] = 0;
			mx[4] = 0;
			mx[5] = (float) Math.cos(angle);
			mx[6] = (float) (-1*Math.sin(angle));
			mx[7] = 0;
			mx[8] = 0;
			mx[9] = (float) Math.sin(angle);
			mx[10] = (float) Math.cos(angle);
			mx[11] = 0;
			mx[12] = 0;
			mx[13] = 0;
			mx[14] = 0;
			mx[15] = 1;
		}else if(y > 0){
			my[0] = (float) Math.cos(angle);
			my[1] = 0;
			my[2] = (float) Math.sin(angle);
			my[3] = 0;
			my[4] = 0;
			my[5] = 1;
			my[6] = 0;
			my[7] = 0;
			my[8] = (float)(-1*Math.sin(angle));
			my[9] = 0;
			my[10] = (float) Math.cos(angle);
			my[11] = 0;
			my[12] = 0;
			my[13] = 0;
			my[14] = 0;
			my[15] = 1;
		}else if(z > 0){
			mz[0] = (float) Math.cos(angle);
			mz[1] = (float) (-1*Math.sin(angle));
			mz[2] = 0;
			mz[3] = 0;
			mz[4] = (float) Math.sin(angle);
			mz[5] = (float) Math.cos(angle);
			mz[6] = 0;
			mz[7] = 0;
			mz[8] = 0;
			mz[9] = 0;
			mz[10] = 1;
			mz[11] = 0;
			mz[12] = 0;
			mz[13] = 0;
			mz[14] = 0;
			mz[15] = 1;
		}
		FloatBuffer mat = matrixMultiply(putFloatCoords(mz), matrixMultiply(putFloatCoords(mx), putFloatCoords(my)));
		return floatBufferToArray(mat);
	}
	
	public static float[] scale(float x, float y, float z){
		float[] mat = new float[16];
		mat[0] = x;
		mat[1] = 0;
		mat[2] = 0;
		mat[3] = 0;
		
		mat[4] = 0;
		mat[5] = y;
		mat[6] = 0;
		mat[7] = 0;
		
		mat[8] = 0;
		mat[9] = 0;
		mat[10] = z;
		mat[11] = 0;
		
		mat[12] = 0;
		mat[13] = 0;
		mat[14] = 0;
		mat[15] = 1;
		
		return mat;
	}
	
	public static float[] translate(float x, float y, float z){
		float[] mat = new float[16];
		
		mat[0] = 1;
		mat[1] = 0;
		mat[2] = 0;
		mat[3] = x;
		
		mat[4] = 0;
		mat[5] = 1;
		mat[6] = 0;
		mat[7] = y;
		
		mat[8] = 0;
		mat[9] = 0;
		mat[10] = 1;
		mat[11] = z;
		
		mat[12] = 0;
		mat[13] = 0;
		mat[14] = 0;
		mat[15] = 1;
		
		return mat;
	}

	public static FloatBuffer matrixMultiply(FloatBuffer mat1,
			FloatBuffer mat2) {
		FloatBuffer result = BufferUtils.createFloatBuffer(16);
		float[] m1 = floatBufferToArray(mat1);
		float[] m2 = floatBufferToArray(mat2);
		float[] mr = new float[16];
		
		mr[0] = m1[0]*m2[0]+m1[1]*m2[4]+m1[2]*m2[8]+m1[3]*m2[12];
		mr[1] = m1[0]*m2[1]+m1[1]*m2[5]+m1[2]*m2[9]+m1[3]*m2[13];
		mr[2] = m1[0]*m2[2]+m1[1]*m2[6]+m1[2]*m2[10]+m1[3]*m2[14];
		mr[3] = m1[0]*m2[3]+m1[1]*m2[7]+m1[2]*m2[11]+m1[3]*m2[15];
		
		mr[4] = m1[4]*m2[0]+m1[5]*m2[4]+m1[6]*m2[8]+m1[7]*m2[12];
		mr[5] = m1[4]*m2[1]+m1[5]*m2[5]+m1[6]*m2[9]+m1[7]*m2[13];
		mr[6] = m1[4]*m2[2]+m1[5]*m2[6]+m1[6]*m2[10]+m1[7]*m2[14];
		mr[7] = m1[4]*m2[3]+m1[5]*m2[7]+m1[6]*m2[11]+m1[7]*m2[15];
		
		mr[8] = m1[8]*m2[0]+m1[9]*m2[4]+m1[10]*m2[8]+m1[11]*m2[12];
		mr[9] = m1[8]*m2[1]+m1[9]*m2[5]+m1[10]*m2[9]+m1[11]*m2[13];
		mr[10] = m1[8]*m2[2]+m1[9]*m2[6]+m1[10]*m2[10]+m1[11]*m2[14];
		mr[11] = m1[8]*m2[3]+m1[9]*m2[7]+m1[10]*m2[11]+m1[11]*m2[15];
		
		mr[12] = m1[12]*m2[0]+m1[13]*m2[4]+m1[14]*m2[8]+m1[15]*m2[12];
		mr[13] = m1[12]*m2[1]+m1[13]*m2[5]+m1[14]*m2[9]+m1[15]*m2[13];
		mr[14] = m1[12]*m2[2]+m1[13]*m2[6]+m1[14]*m2[10]+m1[15]*m2[14];
		mr[15] = m1[12]*m2[3]+m1[13]*m2[7]+m1[14]*m2[11]+m1[15]*m2[15];
		
		result.put(mr).flip();
		return result;
	}

Das Matrizen-Wissen habe ich mir hier angeeignet:
https://open.gl/transformations

Wohohoooo ganz schön übersichtlich.
Habe auch mal meine eigene Matrizenklasse geschrieben gehabt mithilfe C++ und JNI (C++ ist halt um einiges schneller dabei als Java bzw. um genau zu sein fast exakt doppelt so schnell).

Und irgendwie sieht deine Matrizenberechnung für mich irgendwie nicht richtig aus im allgemeinen und um ehrlich zu sein war ich froh als ich meine Klasse fertig hatte und nurnoch eine Methode aufrufen musste.

Die Frage wäre jetzt ob es denn richtig funktioniert wenn du eine einfache Matrix dem Shader übergibst?
[1][0][0][0]
[0][1][0][0]
[0][0][1][0]
[0][0][0][1]
OGL verwendet ja dieses grauenhafte System bei denen die Matrix irgendwie/wohin/wasauchimmer gedreht ist entsprechend ist index 0 oben links und index 1 eins drunter.
Aber das ist eine einfache Matrix die einfach ohne irgendwelche transformationen oder sonstiges auf den Bildchirm zeichnet. Übrigens solltest du auch mit solch einer Matrix starten, ein Array nur mit Nullern befüllt macht garnichts.

Machst du die Matrixmultiplikationen auch in der richtigen Reihenfolge? Du weißt das es einen Unterschied macht wenn man Matrix A mit B multipliziert oder wenn man Matrix B mit A multipliziert?
AxB=C, BxA = D

Irgendwas drittes was auch sehr eklig bei Matrizen ist… hach vielleicht fällts mir später wieder ein.

Grüße

  1. War das mit der Übersichtlichkeit ironisch gemeint?
  2. Mit der Einheitsmatrix funktioniert’s, bloß sitzen alle Objekte inneinander (verständlich).
  3. ich multipliziere sie so:

gl_Position = projection*model*view*vec4(in_Position, 1);

4.Die berechnung müsste laut der verlinkten Seite stimmen, nur mit der Rotation bin ich mir nicht sicher, das sind ja drei, für jede Achse eine. Ich mache es ja so, dass ich, wenn für eine Achse eine Zahl größer 0 angegeben wird berechne ich die Matrix dafür und multipliziere diese drei dann. Möglicherweise ist diese Multiplikationsmethode auch fehlerhaft, du kannst ja mal deinen Code posten.

Ich habe die Matrizen/Methoden für Projektion, Rotation, Verschiebung und Multiplikation bereits in diesem Thread gepostet. Man müsste also nur lesen… Und wenn man es macht, sollte was auffallen.

Gruße
Fancy

Ahh, ich hab die Matrizen von rechts nach links, Zeile für Zeile geschrieben, aber auch, wenn ich sie so wie in Fancy’s Code mache, wird trotzdem nichts angezeigt.

Jetzt ist mir aufgefallen, die Matrix kann nur überall 0 sein, da ich die Matrizen nur Multipliziere, mit mit der aktuellen Model-Matrix, und die ist ja am Anfang überall 0. Es funktioniert, wenn ich die Model-Matrix am Anfang als Einheitsmatrix setzte.
EDIT
Mir ist grad Aufgefallen, dass wenn ich das Fenster vergrößere, sich der Inhalt nicht anpasst, ich glaube es liegt an diesem “Viewport”, bin mir aber nicht sicher und weiß nicht wie ich das umsetzen sollte, bisher schrieb ich nur glViewport(0, w, h, 0), aber das funktioniert nun ja nicht mehr.

was meinst du mit “inhalt anpassen” soll der skaliert werden? erweitert werden? oder was?

Wenn ich das Fenster anfänglich habe sieht es so aus:

Und wenn ich es vergrößere(mit der Maus), so:

Wenn ich es allerdings vergrößert starte, dann ist es so wie beim ersten Bild

glViewport(0, 0, width, height) gibt es auch weiterhin und muss bei einer Größenänderung auch aufgerufen werden. Die Viewport Transformation wird erst nach dem Fragment Shader angewendet.

Außerdem muss die Projektionsmatrix aktuallisiert werden, da darin das Höhen-/Breitenverhältnis eingeht.

Gruß
Fancy

Danke, jetzt vergrößert sich der Inhalt, wenn man es mit der Maus größer zieht. Nur wenn man auf den “Großes Fenster”-Knopf am Fenster klickt, oder es zum Bildschirmrand hin vergrößert, so dass es sich “anpasst”(ich kanns nicht besser beschreiben), dann ist das Problem noch da, die Vergrößerung fange ich mit einem HierarchyBoundsListener und der Methode ancastorResized ab.

An der Stelle könnte ich mir vorstellen das du das entweder nicht registrierst (aber du hast das sicherlich mithilfe eines simplen System.out.print ausgeschlossen) oder dass der Inhalt deines Fensters nicht neu gezeichnet beim direkten Vergrößern.
Vorraussetzen würde das natürlich das du das OS Eventsystem verwendest zum neuzeichnen und keine Schleife.

Der Fancy weiß bestimmt mal wieder mehr.

Ach, hat sich schon erledigt, heute(Wiso gestern nicht, weiß ich nicht) kam eine Exception, dass im EDT kein LWJGL-Kontext ist. Dort lade ich nämlich auch meinen 2D-Kram neu und da müssen VBOs und VAOs erstellt werden.

Ich habe jetzt einen Fragmentshader geschrieben, der gut funktionieren sollte, aber, wie man auf den schon geposteten Bildern erkennen kann, gibt es einige falsch beleuchtete Flächen. Dies führe ich auf die Normalenberechnung zurück. Die folgende Methode berechnet die Normalen für ein Mesh, das als Dreiecke gezeichnet wird(also Indexe wie 0, 1, 2, 2, 1, 3 für ein Viereck). Vielleicht weiß jemand, was ich besser machen kann(Die Berechnung basiert auf der schon früher verlinkten Seite):

Und was genau ist “falsch”?
Und wie sollte es eigentlich sein?

Manche Flächen, die logischerweise hell sein sollten sind dunkel und umgekehrt.
Und Sorry, der Code wurde irgendwie nicht mitgepostet:


ArrayList<Float> normals = new ArrayList<Float>(); 
		ArrayList<int[]> ipd = new ArrayList<int[]>();
		for(int i = 0; i < indexBuffer.capacity(); i += 3){
			int[] ind = {
					indexBuffer.get(i),indexBuffer.get(i+1),indexBuffer.get(i+2)
			};
			ipd.add(ind);
		}
		for(int d = 0; d < ipd.size(); d++){
			float nx = 0, ny = 0, nz = 0;
			ArrayList<Vector3f> coord_vectors = new ArrayList<Vector3f>();
			for(int i = 0; i < vertexBuffer.capacity(); i += 3){
				coord_vectors.add(new Vector3f(vertexBuffer.get(i), vertexBuffer.get(i+1), vertexBuffer.get(i+2)));
			}
			Vector3f U = new Vector3f();
			Vector3f.sub(coord_vectors.get(ipd.get(d)[1]), coord_vectors.get(ipd.get(d)[0]), U);
			Vector3f V = new Vector3f();
			Vector3f.sub(coord_vectors.get(ipd.get(d)[2]), coord_vectors.get(ipd.get(d)[0]), V);
			
			nx = U.y*V.z-U.z*V.y;
			ny = U.z*V.x-U.x*V.z;
			nz = U.x*V.y-U.y*V.x;
			
			for(int i = 0; i < 3; i++){
				normals.add(nx);
				normals.add(ny);
				normals.add(nz);
			}
			
		}
		float[] ns = new float[normals.size()];
		for(int i = 0; i < ns.length; i++){
			ns** = normals.get(i).floatValue();
		}
		return Main.putFloatCoords(ns);

Hab’s jetzt nicht getestet, aber den Symptomen nach ist das der Klassiker: Falsche Indexreihenfolge. Die Normale eines Dreiecks (v0,v1,v2) zeigt genau in die entgegengesetzte Richtung wie die eines Dreieck (v2,v1,v0). Schau’ mal nach, was genau in deinem IndexBuffer steht (oder ob das allgemein zutreffen könnte. Wenn es ein “genereller” Fehler ist, hat man bei einem ursprünglich aus Vierecken bestehenden Mesh dann oft so ein regelmäßiges Muster aus hellen/dunklen Dreiecken drin, aber das muss nicht bei allen Meshes so auffällig sein)

Das scheint es nicht zu sein, ich habe mal die Normalenpunkte anzeigen lassen und es scheint, dass für ein paar Flächen nicht die Normalen berechnet werden, und die fehlen dann. Also muss doch ein Fehler in der Methode sein.

Baim Drüberschauen (ist ein bißchen schwer zu lesen) sehe ich nichts Auffälliges, aber das muß nichts heißen. Was für eine Geometrie verwendest du? (Und kannst du zum Testen/Debuggen ggf. sowas wie “Ein Quadrat aus zwei Dreiecken” verwenden?)