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

Shaders! (WIP) #270

Open
wants to merge 3 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
Binary file added jars/joml-1.8.2.jar
Binary file not shown.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@
<artifactId>json</artifactId>
<version>20201115</version>
</dependency>
<dependency>
<groupId>org.joml</groupId>
<artifactId>joml</artifactId>
<version>1.8.2</version>
</dependency>

</dependencies>

Expand Down
128 changes: 128 additions & 0 deletions src/main/java/org/qme/client/vis/gl/Mesh.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.qme.client.vis.gl;

import org.lwjgl.system.MemoryUtil;
import org.qme.client.vis.tex.TextureManager;
import org.qme.io.Logger;
import org.qme.io.Severity;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

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

/**
* A mesh with texture coordinates, position coordinates, etc. This contains the
* data OpenGL actually uses to render stuff.
*/
public final class Mesh {

/**
* A bunch of IDs! vaoID is the ID of all of the data's "linking point", so
* to speak. All of the xxxVboID are the IDs are various sets of data GL
* uses.
*/
private final int vaoId,
posVboId, idxVboId, texVboId,
vertexCount
;

/**
* Set up all of the arrays into buffers and such.
* @param positions the locations that things get drawn at
* @param indices the indices of the position buffer to draw, so we don't
* repeat data.
* @param texPositions the texture coordinates to use.
*/
public Mesh(float[] positions, int[] indices, float[] texPositions) {

// The buffers we'll place the data in
FloatBuffer verticesBuffer, texBuffer;
IntBuffer indexBuffer;

vertexCount = indices.length;

vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);

// Vertices
posVboId = glGenBuffers();
verticesBuffer = MemoryUtil.memAllocFloat(positions.length);
verticesBuffer.put(positions).flip();
glBindBuffer(GL_ARRAY_BUFFER, posVboId);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
MemoryUtil.memFree(verticesBuffer);

// Indices
idxVboId = glGenBuffers();
indexBuffer = MemoryUtil.memAllocInt(indices.length);
indexBuffer.put(indices).flip();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxVboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL_STATIC_DRAW);
MemoryUtil.memFree(indexBuffer);

// Texture coordinates
texVboId = glGenBuffers();
texBuffer = MemoryUtil.memAllocFloat(texPositions.length);
texBuffer.put(texPositions).flip();
glBindBuffer(GL_ARRAY_BUFFER, texVboId);
glBufferData(GL_ARRAY_BUFFER, texBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);

// We're not placing data into the vao anymore
glBindVertexArray(0);

}

/**
* Draw this mesh using the texture given.
* TODO: optimize this so we don't load and unload the same texture 3721897482394 times.
* @param textureName which texture
*/
public void render(String textureName) {

// Activate first texture unit
glActiveTexture(GL_TEXTURE0);
// Bind the texture (this line is expensive to run)
Integer texID = TextureManager.getTexture(textureName);
if (texID == null) {
Logger.log("Attempted to load nonexistent texture! Texture: " + textureName, Severity.FATAL);
}
glBindTexture(GL_TEXTURE_2D, texID);

// Draw the mesh
glBindVertexArray(vaoId);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawElements(GL_TRIANGLES, vertexCount, GL_UNSIGNED_INT, 0);

// Clear everything for next mesh
glDisableVertexAttribArray(0);
glBindVertexArray(0);

}

/**
* Clear all of the mesh data. Because OpenGL deals with this, Java does not
* garbage collect so please call this manually.
*/
public void delete() {

glDisableVertexAttribArray(0);

// Clear the VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);

// Flush all data
glDeleteBuffers(posVboId);
glDeleteBuffers(idxVboId);
glDeleteBuffers(texVboId);

// Clear the VAO
glBindVertexArray(0);
glDeleteVertexArrays(vaoId);

}

}
188 changes: 188 additions & 0 deletions src/main/java/org/qme/client/vis/gl/Shader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package org.qme.client.vis.gl;

import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack;

import java.nio.FloatBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

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

/**
* Yeah, Cameron, I know you're going to be reading this frantically trying to
* figure out how shaders work. Or someone else, maybe. So I'm writing it here
* the way I understand.
*
* OpenGL can compile GLSL code into GPU-assembly language. You can then bind
* the shader so sets of vertices are rendered with it. OpenGL renders shaders
* by compiling a String of source code, which we generate on-the-fly from a
* file. The vertex shader (_vert.glsl) is first, which produces the position of
* the vertex being processed. I *think* the fragment shader makes the output
* color? Or something? Idk. The "real" rendering work is done by meshes.
*
* @author adamhutchings
* @since 0.4
*/
public class Shader {

private final int programId, vertId, fragId;

private final Map<String, Integer> uniforms = new HashMap<>();

/**
* Load a string from a file.
* @param fileName the path to the file
*/
public static String loadFileAsString(String fileName) throws Exception {
return Files.readString(Path.of(fileName));
}

/**
* Load a shader from files.
* @param fileBase load vertex code from src/shader/fileBase_vert.glsl
* and src/shader/fileBase_frag.glsl
*/
public Shader(String fileBase) throws Exception {

programId = glCreateProgram();
if (programId == 0) {
throw new Exception("Unable to create parent shader");
}

vertId = createShader(
loadFileAsString("src/shader/" + fileBase + "_vert.glsl"), GL_VERTEX_SHADER
);
fragId = createShader(
loadFileAsString("src/shader/" + fileBase + "_frag.glsl"), GL_FRAGMENT_SHADER
);

link();

}

/**
* Link everything together.
*/
private void link() throws Exception {

glLinkProgram(programId);
if (glGetProgrami(programId, GL_LINK_STATUS) == 0) {
throw new Exception("Error linking shader code: " + glGetProgramInfoLog(programId, 1024));
}

// These pieces of shaders aren't "needed" anymore, so we can detach
// them from the final linked program.
if (vertId != 0) {
glDetachShader(programId, vertId);
}
if (fragId != 0) {
glDetachShader(programId, fragId);
}

glValidateProgram(programId);
if (glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) {
// THESE WARNINGS ARE NOT NECESSARILY A SIGN OF A FAILING COMPILATION.
// Just so y'all know :D
System.err.println("Warning validating Shader code: " + glGetProgramInfoLog(programId, 1024));
}

}

/**
* Create an individual shader of a given type.
*/
private int createShader(String code, int type) throws Exception {

int shaderId = glCreateShader(type);

if (shaderId == 0) {
throw new Exception("Could not create shader of type " + type);
}

glShaderSource(shaderId, code);
glCompileShader(shaderId);

if (glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) {
throw new Exception("Error compiling shader code: " + glGetShaderInfoLog(shaderId, 1024));
}

glAttachShader(programId, shaderId);

return shaderId;

}

/**
* Set this as the shader to be used.
*/
public void bind() {
glUseProgram(programId);
}

/**
* Stop using this shader.
*/
public void unbind() {
glUseProgram(0);
}

/**
* Call this to delete the shader.
*/
public void cleanup() {
unbind();
// This check should technically be unnecessary.
if (programId != 0) {
glDeleteProgram(programId);
}
}

/**
* For us to pass data into the shader from the program, we need to create a
* "uniform", which like all other OpenGL code is set using integers. Here
* we interface with it using strings so that we can access it more intuitively
* and easily.
* @param uniformName the name of the new uniform to create
* @throws Exception if the uniform is not defined *in the shader program*.
*/
public void createUniform(String uniformName) throws Exception {
int uniformLocation = glGetUniformLocation(programId,
uniformName);
if (uniformLocation < 0) {
throw new Exception("Could not find uniform:" +
uniformName);
}
uniforms.put(uniformName, uniformLocation);
}

/**
* Set a uniform to an int value
* @param uniformName which uniform
* @param value the value to set it to
*/
public void setUniform(String uniformName, int value) {
glUniform1i(uniforms.get(uniformName), value);
}

/**
* Set a uniform to an float value
* @param uniformName which uniform
* @param value the value to set it to
*/
public void setUniform(String uniformName, float value) {
glUniform1f(uniforms.get(uniformName), value);
}

/**
* Set a uniform to an float array value
* @param uniformName which uniform
* @param value the value to set it to
*/
public void setUniform(String uniformName, float[] value) {
glUniform2fv(uniforms.get(uniformName), value);
}

}