diff --git a/doc/rendering/ShaderData.md b/doc/rendering/ShaderData.md new file mode 100644 index 000000000..b72289fea --- /dev/null +++ b/doc/rendering/ShaderData.md @@ -0,0 +1,4 @@ +Relative Chunk position: 3x 0-15 (3x 4bit = 12bit) +Side: 0-6 (3 bits) +Light level: 1x 0-15 (4bit) +Texture index: ? diff --git a/pom.xml b/pom.xml index 653375807..19f57494e 100644 --- a/pom.xml +++ b/pom.xml @@ -127,6 +127,13 @@ 3.2.3 + + + jitpack.io + https://jitpack.io + + + com.google.code.gson @@ -207,6 +214,10 @@ org.lwjgl lwjgl-opengl + + org.lwjgl + lwjgl-stb + org.lwjgl lwjgl @@ -227,5 +238,20 @@ lwjgl-opengl ${lwjgl.natives} + + org.lwjgl + lwjgl-stb + ${lwjgl.natives} + + + org.l33tlabs.twl + pngdecoder + 1.0 + + + com.github.kotlin-graphics + glm + 1.0.1 + diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt new file mode 100644 index 000000000..b5bd4c77b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt @@ -0,0 +1,86 @@ +package de.bixilon.minosoft.gui.rendering + +import glm_.glm +import glm_.mat4x4.Mat4 +import glm_.vec3.Vec3 +import org.lwjgl.glfw.GLFW +import kotlin.math.cos +import kotlin.math.sin + +class Camera(private var fov: Float, private val windowId: Long) { + private var mouseSensitivity = 0.1 + private var movementSpeed = 7 + private var cameraFront = Vec3(0.0f, 0.0f, -1.0f) + private var cameraPosition = Vec3(0.0f, 0.0f, 3.0f) + private var lastMouseX = 0.0 + private var lastMouseY = 0.0 + private var yaw = 0.0 + private var pitch = 0.0 + + fun mouseCallback(xPos: Double, yPos: Double) { + var xOffset = xPos - this.lastMouseX + var yOffset = this.lastMouseY - yPos // reversed since y-coordinates go from bottom to top + lastMouseX = xPos + lastMouseY = yPos + xOffset *= mouseSensitivity + yOffset *= mouseSensitivity + yaw += xOffset + pitch += yOffset + + // make sure that when pitch is out of bounds, screen doesn't get flipped + if (this.pitch > 89.0) { + this.pitch = 89.0 + } else if (this.pitch < -89.0) { + this.pitch = -89.0 + } + cameraFront = Vec3((cos(glm.radians(yaw)) * cos(glm.radians(pitch))).toFloat(), sin(glm.radians(pitch)).toFloat(), (sin(glm.radians(yaw)) * cos(glm.radians(pitch))).toFloat()).normalize() + } + + fun handleInput(deltaTime: Double) { + val cameraSpeed = movementSpeed * deltaTime + val currentY = cameraPosition.y + if (GLFW.glfwGetKey(windowId, GLFW.GLFW_KEY_W) == GLFW.GLFW_PRESS) { + cameraPosition = cameraPosition + cameraFront * cameraSpeed + } + if (GLFW.glfwGetKey(windowId, GLFW.GLFW_KEY_S) == GLFW.GLFW_PRESS) { + cameraPosition = cameraPosition - cameraFront * cameraSpeed + } + if (GLFW.glfwGetKey(windowId, GLFW.GLFW_KEY_A) == GLFW.GLFW_PRESS) { + cameraPosition = cameraPosition - (cameraFront.cross(CAMERA_UP_VEC3).normalize()) * cameraSpeed + } + if (GLFW.glfwGetKey(windowId, GLFW.GLFW_KEY_D) == GLFW.GLFW_PRESS) { + cameraPosition = cameraPosition + (cameraFront.cross(CAMERA_UP_VEC3).normalize()) * cameraSpeed + } + this.cameraPosition.y = currentY // stay on xz line when moving (aka. no clip) + if (GLFW.glfwGetKey(windowId, GLFW.GLFW_KEY_LEFT_SHIFT) == GLFW.GLFW_PRESS) { + cameraPosition = cameraPosition - CAMERA_UP_VEC3 * cameraSpeed + } + if (GLFW.glfwGetKey(windowId, GLFW.GLFW_KEY_SPACE) == GLFW.GLFW_PRESS) { + cameraPosition = cameraPosition + CAMERA_UP_VEC3 * cameraSpeed + } + } + + fun calculateProjectionMatrix(screenWidth: Int, screenHeight: Int, shader: Shader) { + shader.use().setMat4("projection", calculateProjectionMatrix(screenWidth, screenHeight)) + } + + private fun calculateProjectionMatrix(screenWidth: Int, screenHeight: Int): Mat4 { + return glm.perspective(glm.radians(fov), screenWidth.toFloat() / screenHeight.toFloat(), 0.1f, 100f) + } + + fun calculateViewMatrix(shader: Shader) { + shader.use().setMat4("view", calculateViewMatrix()) + } + + private fun calculateViewMatrix(): Mat4 { + return glm.lookAt(cameraPosition, cameraPosition + cameraFront, CAMERA_UP_VEC3) + } + + fun setFOV(fov: Float) { + this.fov = fov + } + + companion object { + private val CAMERA_UP_VEC3 = Vec3(0.0f, 1.0f, 0.0f) + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/DummyRender.java b/src/main/java/de/bixilon/minosoft/gui/rendering/DummyRender.java index f8412f4ff..c293ee842 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/DummyRender.java +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/DummyRender.java @@ -13,6 +13,5 @@ public class DummyRender { renderWindow.init(); renderWindow.startLoop(); renderWindow.exit(); - } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.java b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.java index bfa5a3518..dce682b97 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.java +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.java @@ -1,10 +1,13 @@ package de.bixilon.minosoft.gui.rendering; import de.bixilon.minosoft.gui.rendering.exceptions.ShaderLoadingException; +import glm_.glm; +import glm_.mat4x4.Mat4; +import glm_.vec3.Vec3; import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWVidMode; import org.lwjgl.glfw.GLFWWindowSizeCallback; -import org.lwjgl.opengl.*; +import org.lwjgl.opengl.GL; import org.lwjgl.system.MemoryStack; import java.io.IOException; @@ -14,7 +17,8 @@ import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL15.*; -import static org.lwjgl.opengl.GL20.*; +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 static org.lwjgl.system.MemoryStack.stackPush; @@ -22,46 +26,74 @@ import static org.lwjgl.system.MemoryUtil.NULL; public class RenderWindow { - float[] vertices = { - 0.5f, 0.5f, 0.0f, // top right - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f // top left + private final float[] vertices = { + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, + + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, + + -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f }; - int[] indices = { // note that we start from 0! - 0, 1, 3, // first Triangle - 1, 2, 3 // second Triangle + private final Vec3[] cubePositions = { + new Vec3(0.0f, 0.0f, 0.0f), + new Vec3(2.0f, 5.0f, -15.0f), + new Vec3(-1.5f, -2.2f, -2.5f), + new Vec3(-3.8f, -2.0f, -12.3f), + new Vec3(2.4f, -0.4f, -3.5f), + new Vec3(-1.7f, 3.0f, -7.5f), + new Vec3(1.3f, -2.0f, -2.5f), + new Vec3(1.5f, 2.0f, -2.5f), + new Vec3(1.5f, 0.2f, -1.5f), + new Vec3(-1.3f, 1.0f, -1.5f) }; - boolean polygonEnabled; - private long window; + private int screenWidth = 800; + private int screenHeight = 600; + private float visibilityLevel; + private boolean polygonEnabled; + private Shader shader; + private Texture texture0; + private Texture texture1; + private long windowId; - private static int loadShaders() throws ShaderLoadingException, IOException { - int vertexShader = ShaderUtil.createShader("/vertex.glsl", ARBVertexShader.GL_VERTEX_SHADER_ARB); - int fragmentShader = ShaderUtil.createShader("/fragment.glsl", ARBFragmentShader.GL_FRAGMENT_SHADER_ARB); + private double deltaTime; // time between current frame and last frame + private double lastFrame; - int shaderId = ARBShaderObjects.glCreateProgramObjectARB(); - - if (shaderId == NULL) { - throw new ShaderLoadingException(); - } - - ARBShaderObjects.glAttachObjectARB(shaderId, vertexShader); - ARBShaderObjects.glAttachObjectARB(shaderId, fragmentShader); - - ARBShaderObjects.glLinkProgramARB(shaderId); - if (ARBShaderObjects.glGetObjectParameteriARB(shaderId, ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) { - throw new ShaderLoadingException(OpenGLUtil.getLogInfo(shaderId)); - } - - ARBShaderObjects.glValidateProgramARB(shaderId); - if (ARBShaderObjects.glGetObjectParameteriARB(shaderId, ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) { - throw new ShaderLoadingException(OpenGLUtil.getLogInfo(shaderId)); - } - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - return shaderId; - } + private Camera camera; public void init() { // Setup an error callback. The default implementation @@ -82,38 +114,42 @@ public class RenderWindow { glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable // Create the window - this.window = glfwCreateWindow(800, 600, "Hello World!", NULL, NULL); - if (this.window == NULL) { + this.windowId = glfwCreateWindow(this.screenWidth, this.screenHeight, "Hello World!", NULL, NULL); + if (this.windowId == NULL) { glfwTerminate(); throw new RuntimeException("Failed to create the GLFW window"); } + this.camera = new Camera(45f, this.windowId); - glfwSetWindowSizeCallback(this.window, new GLFWWindowSizeCallback() { + + glfwSetWindowSizeCallback(this.windowId, new GLFWWindowSizeCallback() { @Override public void invoke(long window, int width, int height) { glViewport(0, 0, width, height); + RenderWindow.this.screenWidth = width; + RenderWindow.this.screenHeight = height; + RenderWindow.this.camera.calculateProjectionMatrix(RenderWindow.this.screenWidth, RenderWindow.this.screenHeight, RenderWindow.this.shader); } }); // Setup a key callback. It will be called every time a key is pressed, repeated or released. - glfwSetKeyCallback(this.window, (window, key, scancode, action, mods) -> { - if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { - glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop + glfwSetKeyCallback(this.windowId, (window, key, scancode, action, mods) -> { + if (action != GLFW_RELEASE) { return; } - if (key == GLFW_KEY_P && action == GLFW_RELEASE) { - if (this.polygonEnabled) { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } else { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - this.polygonEnabled = !this.polygonEnabled; - return; + switch (key) { + case GLFW_KEY_ESCAPE -> glfwSetWindowShouldClose(this.windowId, true); + case GLFW_KEY_P -> switchPolygonMode(); } }); + glfwSetInputMode(this.windowId, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + + glfwSetCursorPosCallback(this.windowId, ((window, xPos, yPos) -> this.camera.mouseCallback(xPos, yPos))); + // Get the thread stack and push a new frame try (MemoryStack stack = stackPush()) { @@ -121,33 +157,33 @@ public class RenderWindow { IntBuffer pHeight = stack.mallocInt(1); // int* // Get the window size passed to glfwCreateWindow - glfwGetWindowSize(this.window, pWidth, pHeight); + glfwGetWindowSize(this.windowId, pWidth, pHeight); // Get the resolution of the primary monitor GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); // Center the window - glfwSetWindowPos(this.window, (videoMode.width() - pWidth.get(0)) / 2, (videoMode.height() - pHeight.get(0)) / 2); + glfwSetWindowPos(this.windowId, (videoMode.width() - pWidth.get(0)) / 2, (videoMode.height() - pHeight.get(0)) / 2); } // the stack frame is popped automatically // Make the OpenGL context current - glfwMakeContextCurrent(this.window); + glfwMakeContextCurrent(this.windowId); // Enable v-sync glfwSwapInterval(1); // Make the window visible - glfwShowWindow(this.window); + glfwShowWindow(this.windowId); GL.createCapabilities(); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); + + glEnable(GL_DEPTH_TEST); } public void startLoop() throws IOException, ShaderLoadingException { - int vAO = glGenVertexArrays(); int vBO = glGenBuffers(); - int eBO = glGenBuffers(); // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). glBindVertexArray(vAO); @@ -155,12 +191,13 @@ public class RenderWindow { glBindBuffer(GL_ARRAY_BUFFER, vBO); glBufferData(GL_ARRAY_BUFFER, this.vertices, GL_STATIC_DRAW); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, this.indices, GL_STATIC_DRAW); - glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0L); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * Float.BYTES, 0L); glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * Float.BYTES, (3 * Float.BYTES)); + glEnableVertexAttribArray(1); + // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -171,34 +208,98 @@ public class RenderWindow { // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary. glBindVertexArray(0); + this.texture0 = new Texture("/textures/emerald_block.png"); + this.texture0.load(); + this.texture1 = new Texture("/textures/brown_wool.png"); + this.texture1.load(); - int shaderId = loadShaders(); + this.shader = new Shader("vertex.glsl", "fragment.glsl"); + this.shader.load(); + this.shader.use(); + + + this.shader.setInt("texture0", 0); + this.shader.setInt("texture1", 1); + + this.shader.setFloat("visibility", this.visibilityLevel); + + this.camera.calculateProjectionMatrix(this.screenWidth, this.screenHeight, this.shader); + this.camera.calculateViewMatrix(this.shader); + + + while (!glfwWindowShouldClose(this.windowId)) { - while (!glfwWindowShouldClose(this.window)) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer - glUseProgram(shaderId); + double currentFrame = glfwGetTime(); + + + this.deltaTime = currentFrame - this.lastFrame; + this.lastFrame = currentFrame; + + this.camera.calculateViewMatrix(this.shader); + + + this.texture0.use(GL_TEXTURE0); + this.texture1.use(GL_TEXTURE1); + + this.shader.use(); + glBindVertexArray(vAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized - glDrawElements(GL_TRIANGLES, this.indices.length, GL_UNSIGNED_INT, 0); - glfwSwapBuffers(this.window); // swap the color buffers + for (int i = 0; i < this.cubePositions.length; i++) { + float angle = 50.0f * (i + 1) * (float) glfwGetTime(); + Mat4 model = new Mat4().translate(this.cubePositions[i]).rotate(glm.INSTANCE.radians(angle), new Vec3(i / 0.5f + 0.1f, i / 0.3f + 0.1f, i / 0.1f + 0.1f)); + this.shader.setMat4("model", model); + + glDrawArrays(GL_TRIANGLES, 0, 36); + } + + + glfwSwapBuffers(this.windowId); // swap the color buffers // Poll for window events. The key callback above will only be // invoked during this call. glfwPollEvents(); + handleInput(); + this.camera.handleInput(this.deltaTime); } } public void exit() { // Free the window callbacks and destroy the window - glfwFreeCallbacks(this.window); - glfwDestroyWindow(this.window); + glfwFreeCallbacks(this.windowId); + glfwDestroyWindow(this.windowId); // Terminate GLFW and free the error callback glfwTerminate(); glfwSetErrorCallback(null).free(); } + + private void switchPolygonMode() { + glPolygonMode(GL_FRONT_AND_BACK, (this.polygonEnabled ? GL_LINE : GL_FILL)); + this.polygonEnabled = !this.polygonEnabled; + } + + private void handleInput() { + if (glfwGetKey(this.windowId, GLFW_KEY_UP) == GLFW_PRESS) { + this.shader.use(); + this.visibilityLevel += 0.1f; + if (this.visibilityLevel > 1.0f) { + this.visibilityLevel = 1.0f; + } + this.shader.setFloat("visibility", this.visibilityLevel); + } + if (glfwGetKey(this.windowId, GLFW_KEY_DOWN) == GLFW_PRESS) { + this.shader.use(); + this.visibilityLevel -= 0.1f; + if (this.visibilityLevel < 0.0f) { + this.visibilityLevel = 0.0f; + } + this.shader.setFloat("visibility", this.visibilityLevel); + } + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Shader.java b/src/main/java/de/bixilon/minosoft/gui/rendering/Shader.java new file mode 100644 index 000000000..f650372a4 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Shader.java @@ -0,0 +1,86 @@ +package de.bixilon.minosoft.gui.rendering; + +import de.bixilon.minosoft.gui.rendering.exceptions.ShaderLoadingException; +import glm_.mat4x4.Mat4; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.ARBFragmentShader; +import org.lwjgl.opengl.ARBShaderObjects; +import org.lwjgl.opengl.ARBVertexShader; +import org.lwjgl.opengl.GL11; + +import java.io.IOException; + +import static org.lwjgl.opengl.GL20.*; +import static org.lwjgl.system.MemoryUtil.NULL; + +public class Shader { + private static Shader usedShader; + private final String vertex; + private final String fragment; + private int programId; + + public Shader(String vertex, String fragment) { + this.vertex = vertex; + this.fragment = fragment; + } + + public int load() throws ShaderLoadingException, IOException { + int vertexShader = ShaderUtil.createShader(this.vertex, ARBVertexShader.GL_VERTEX_SHADER_ARB); + int fragmentShader = ShaderUtil.createShader(this.fragment, ARBFragmentShader.GL_FRAGMENT_SHADER_ARB); + + this.programId = ARBShaderObjects.glCreateProgramObjectARB(); + + if (this.programId == NULL) { + throw new ShaderLoadingException(); + } + + ARBShaderObjects.glAttachObjectARB(this.programId, vertexShader); + ARBShaderObjects.glAttachObjectARB(this.programId, fragmentShader); + + ARBShaderObjects.glLinkProgramARB(this.programId); + if (ARBShaderObjects.glGetObjectParameteriARB(this.programId, ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) { + throw new ShaderLoadingException(OpenGLUtil.getLogInfo(this.programId)); + } + + ARBShaderObjects.glValidateProgramARB(this.programId); + if (ARBShaderObjects.glGetObjectParameteriARB(this.programId, ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) { + throw new ShaderLoadingException(OpenGLUtil.getLogInfo(this.programId)); + } + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + return this.programId; + } + + public int getProgramId() { + return this.programId; + } + + public Shader use() { + if (usedShader != this) { + glUseProgram(this.programId); + usedShader = this; + } + return this; + } + + public int getUniformLocation(String variableName) { + return glGetUniformLocation(this.programId, variableName); + } + + public void setFloat(String name, float value) { + glUniform1f(getUniformLocation(name), value); + } + + public void setInt(String name, int value) { + glUniform1i(getUniformLocation(name), value); + } + + public void set4f(String variableName, float[] floats) { + glUniformMatrix4fv(glGetUniformLocation(this.programId, variableName), false, floats); + } + + public void setMat4(String variableName, Mat4 mat4) { + glUniformMatrix4fv(glGetUniformLocation(this.programId, variableName), false, mat4.to(BufferUtils.createFloatBuffer(16))); + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/ShaderUtil.java b/src/main/java/de/bixilon/minosoft/gui/rendering/ShaderUtil.java index b2119401e..9a9d2d21a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/ShaderUtil.java +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/ShaderUtil.java @@ -18,7 +18,7 @@ public class ShaderUtil { throw new ShaderLoadingException(); } - ARBShaderObjects.glShaderSourceARB(shaderId, Util.readAsset("/rendering/shader" + shaderPath)); + ARBShaderObjects.glShaderSourceARB(shaderId, Util.readAsset("/rendering/shader/" + shaderPath)); ARBShaderObjects.glCompileShaderARB(shaderId); if (ARBShaderObjects.glGetObjectParameteriARB(shaderId, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Texture.java b/src/main/java/de/bixilon/minosoft/gui/rendering/Texture.java new file mode 100644 index 000000000..a003f839b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Texture.java @@ -0,0 +1,49 @@ +package de.bixilon.minosoft.gui.rendering; + +import de.matthiasmann.twl.utils.PNGDecoder; +import org.lwjgl.BufferUtils; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.lwjgl.opengl.GL20.*; +import static org.lwjgl.opengl.GL30.glGenerateMipmap; + +public class Texture { + private final String texturePath; + private int textureId; + + public Texture(String texturePath) { + this.texturePath = texturePath; + } + + + public int load() throws IOException { + this.textureId = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, this.textureId); + // set the texture wrapping/filtering options (on the currently bound texture object) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // load and generate the texture + + PNGDecoder decoder = new PNGDecoder(OpenGLUtil.class.getResourceAsStream(this.texturePath)); + ByteBuffer buffer = BufferUtils.createByteBuffer(decoder.getWidth() * decoder.getHeight() * 4); + decoder.decode(buffer, decoder.getWidth() * 4, PNGDecoder.Format.RGBA); + buffer.flip(); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, decoder.getWidth(), decoder.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + glGenerateMipmap(GL_TEXTURE_2D); + return this.textureId; + } + + public int getTextureId() { + return this.textureId; + } + + public void use(int textureMode) { + glActiveTexture(textureMode); // activate the texture unit first before binding texture + glBindTexture(GL_TEXTURE_2D, this.textureId); + } +} diff --git a/src/main/resources/assets/rendering/shader/fragment.glsl b/src/main/resources/assets/rendering/shader/fragment.glsl index d801e68e4..cd93807fe 100644 --- a/src/main/resources/assets/rendering/shader/fragment.glsl +++ b/src/main/resources/assets/rendering/shader/fragment.glsl @@ -1,6 +1,16 @@ #version 330 core out vec4 FragColor; +in vec3 vertexColor; +in vec2 TexCoord; + +// texture sampler +uniform sampler2D texture0; +uniform sampler2D texture1; + +uniform float visibility; + void main() { - FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); + vec2 newTextCoord = vec2(TexCoord.x, -TexCoord.y); + FragColor = mix(texture(texture0, newTextCoord), texture(texture1, newTextCoord), visibility); } diff --git a/src/main/resources/assets/rendering/shader/vertex.glsl b/src/main/resources/assets/rendering/shader/vertex.glsl index e390af09c..ffbd4cd76 100644 --- a/src/main/resources/assets/rendering/shader/vertex.glsl +++ b/src/main/resources/assets/rendering/shader/vertex.glsl @@ -1,6 +1,16 @@ #version 330 core layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; + +out vec3 vertexColor; +out vec2 TexCoord; + + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; void main() { - gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); + gl_Position = projection * view * model * vec4(aPos, 1.0); + TexCoord = vec2(aTexCoord.x, aTexCoord.y); }