Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add post processing #4

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions shaders/2d_do_nothing.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#version 410 core

in vec2 outputTextureCoordinate;
uniform sampler2D textureSampler;

out vec4 fragColor;

void main() {
fragColor = texture(textureSampler, outputTextureCoordinate);
}
12 changes: 12 additions & 0 deletions shaders/2d_do_nothing.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#version 410 core

layout (location = 0) in vec2 position;
layout (location = 1) in vec2 textureCoordinate;
layout (location = 2) in vec4 color;

out vec2 outputTextureCoordinate;

void main() {
outputTextureCoordinate = textureCoordinate;
gl_Position = vec4(position, 0, 1);
}
11 changes: 11 additions & 0 deletions src/main/java/org/crafter/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.crafter.engine.delta.Delta;
import org.crafter.engine.gui.font.Font;
import org.crafter.engine.mesh.MeshStorage;
import org.crafter.engine.postprocessing.PipelineItem;
import org.crafter.engine.postprocessing.PostProcessing;
import org.crafter.engine.shader.ShaderStorage;
import org.crafter.engine.texture.TextureStorage;
import org.crafter.engine.window.Window;
Expand Down Expand Up @@ -67,9 +69,15 @@ public static void main(String[] args) {

initialize();

PostProcessing postProcessing = new PostProcessing();

try {
while(!Window.shouldClose()) {
postProcessing.start();
mainLoop();
postProcessing.end();

postProcessing.update();
}
} catch (Exception e) {
// Game must shut down external threads or it WILL hang
Expand Down Expand Up @@ -100,6 +108,9 @@ private static void initialize() {
ShaderStorage.createShader("2d", "shaders/2d_vertex.vert", "shaders/2d_fragment.frag");
ShaderStorage.createUniform("2d", new String[]{"cameraMatrix", "objectMatrix"});

// Used for post-processing
ShaderStorage.createShader("2d_do_nothing", "shaders/2d_do_nothing.vert", "shaders/2d_do_nothing.frag");

Font.createFont("fonts/totally_original", "mc", true);
Font.setShadowOffset(0.75f,0.75f);

Expand Down
136 changes: 136 additions & 0 deletions src/main/java/org/crafter/engine/postprocessing/PipelineItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.crafter.engine.postprocessing;

import org.crafter.engine.shader.ShaderStorage;
import org.crafter.engine.window.Window;

import java.nio.ByteBuffer;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL30.glDeleteFramebuffers;

public class PipelineItem implements AutoCloseable {
private int rectVAO;
private int rectVBO;

private final int framebuffer;
private final int framebufferTexture;
private final String shaderID;

/**
* Creates a pipeline item to be used in post-processing.
* @param shaderID The shader program to be used for post-processing. Any vertex shaders passed should not have a
* projection matrix, view matrix, model matrix, or any other matrix.
*/
public PipelineItem(String shaderID) {
this.shaderID = shaderID;

this.framebuffer = glGenFramebuffers();
this.bind();
this.framebufferTexture = glGenTextures();
this.refreshFramebufferTexture();

errorCheck();

this.unbind();

this.genRect();
}

/**
* Sets the rectVAO and rectVBO member variables.
*/
private void genRect() {
final float[] coords = new float[]{
// Coords Tex coords
-1, 1, 0, 1,
-1, -1, 0, 0,
1, -1, 1, 0,

-1, 1, 0, 1,
1, -1, 1, 0,
1, 1, 1, 1,
};

this.rectVAO = glGenVertexArrays();
this.rectVBO = glGenBuffers();

glBindVertexArray(this.rectVAO);
glBindBuffer(GL_ARRAY_BUFFER, this.rectVBO);

int sizeOfFloat = Float.SIZE / 8;

glBufferData(GL_ARRAY_BUFFER, coords, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 4 * sizeOfFloat, 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 4 * sizeOfFloat, 2 * sizeOfFloat);
}

private void refreshFramebufferTexture() {
this.bind();

glBindTexture(GL_TEXTURE_2D, this.framebufferTexture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Window.getWindowWidth(), Window.getWindowHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, (ByteBuffer) null);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this.framebufferTexture, 0);

this.unbind();
}

private static void errorCheck() {
int fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);

if (fboStatus != GL_FRAMEBUFFER_COMPLETE) {
System.err.println("FBO status has an error. Error code: " + fboStatus);
}
}

public String getShaderID() {
return this.shaderID;
}

public void bind() {
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
}

public void unbind() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

public void draw() {
glBindTexture(GL_TEXTURE_2D, this.framebufferTexture);
ShaderStorage.start(this.shaderID);

glBindVertexArray(this.rectVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);

ShaderStorage.stop();
glBindTexture(GL_TEXTURE_2D, 0);
}

/**
* Changes the frame buffer texture size to the window's new size if it is rescaled
*/
public void refresh() {
this.refreshFramebufferTexture();
}

/**
* Warning: this doesn't close the shader program associated with the pipeline item since it is not created
* by the PipelineItem itself. Make sure to do this.getShaderProgram().close() before running this
*/
@Override
public void close() {
glDeleteFramebuffers(this.framebuffer);
glDeleteTextures(this.framebufferTexture);

glDeleteVertexArrays(this.rectVAO);
glDeleteBuffers(this.rectVBO);
}
}
106 changes: 106 additions & 0 deletions src/main/java/org/crafter/engine/postprocessing/PostProcessing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.crafter.engine.postprocessing;

import org.crafter.engine.window.Window;
import org.joml.Vector2f;

import java.util.ArrayList;
import java.util.List;

import static org.lwjgl.opengl.GL11.*;

public class PostProcessing implements AutoCloseable {
private final List<PipelineItem> pipeline;
private Vector2f windowSize;

public PostProcessing() {
this.pipeline = new ArrayList<>();

this.addToPipeline(
new PipelineItem("2d_do_nothing")
);
}

/**
* Adds a new pipeline item to the end of the pipeline.
* @param item The pipeline item to add.
*/
public void addToPipeline(PipelineItem item) {
pipeline.add(item);
}

/**
* WARNING: the pipeline must have at least one item to render the screen properly.
* @return The list of pipeline items. You may modify this, but make sure that there is at least one pipeline item at all times
*/
public List<PipelineItem> getPipeline() {
return this.pipeline;
}

/**
* Run this before rendering anything, including clearing the screen.
* @throws IllegalStateException If the pipeline has 0 items.
*/
public void start() throws IllegalStateException {
if (this.pipeline.size() == 0) {
throw new IllegalStateException("The pipeline must have at least one item");
}

this.pipeline.get(0).bind();
}

/**
* Run this at the end of the frame.
* @throws IllegalStateException If the pipeline has 0 items.
*/
public void end() throws IllegalStateException {
glDisable(GL_DEPTH_TEST);
// glDisable(GL_CULL_FACE);

if (this.pipeline.size() == 0) {
throw new IllegalStateException("The pipeline must have at least one item");
}

for (int i = 1; i < this.pipeline.size(); i++) {
PipelineItem item = this.pipeline.get(i);
item.bind();
this.pipeline.get(i - 1).draw();
}

this.pipeline.get(0).unbind(); // it doesn't matter which one to call to unbind
this.pipeline.get(this.pipeline.size() - 1).draw();

// glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}

/**
* Changes the texture size if the window is resized.
*/
public void update() {
// Since Window.wasResized() always evaluates to false this is my workaround

if (this.windowSize == null) {
this.windowSize = Window.getWindowSize();
}

if (this.windowSize.equals(Window.getWindowSize())) {
return;
}

for (PipelineItem item : this.getPipeline()) {
item.refresh();
}

this.windowSize = Window.getWindowSize();
}

/**
* Closes all pipeline items.
*/
@Override
public void close() {
for (PipelineItem pipelineItem : this.getPipeline()) {
pipelineItem.close();
}
}
}