Added ShaderProgram and ChunkRenderer

This commit is contained in:
Andrew Lalis 2022-07-04 22:15:27 +02:00
parent dfbcf44725
commit 297fcb5a1e
7 changed files with 184 additions and 88 deletions

View File

@ -1,11 +1,7 @@
package nl.andrewl.aos2_client; package nl.andrewl.aos2_client;
import nl.andrewl.aos_core.FileUtils;
import nl.andrewl.aos_core.model.Chunk; import nl.andrewl.aos_core.model.Chunk;
import org.joml.Matrix3f; import org.joml.Vector3i;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.Version; import org.lwjgl.Version;
import org.lwjgl.glfw.Callbacks; import org.lwjgl.glfw.Callbacks;
import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWErrorCallback;
@ -13,14 +9,59 @@ import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLUtil; import org.lwjgl.opengl.GLUtil;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.util.Random; import java.util.Random;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL46.*; import static org.lwjgl.opengl.GL46.*;
public class Aos2Client { public class Aos2Client {
public static void main(String[] args) throws IOException, InterruptedException { public static void main(String[] args) {
long windowHandle = initUI();
Chunk chunk = Chunk.random(new Vector3i(0, 0, 0), new Random(1));
Camera cam = new Camera();
glfwSetCursorPosCallback(windowHandle, cam);
for (int i = 0; i < 16; i++) {
chunk.setBlockAt(i, 0, 0, (byte) 8);
chunk.setBlockAt(0, i, 0, (byte) 40);
chunk.setBlockAt(0, 0, i, (byte) 120);
}
chunk.setBlockAt(0, 15, 0, (byte) 0);
chunk.setBlockAt(1, 15, 0, (byte) 0);
chunk.setBlockAt(2, 15, 0, (byte) 0);
chunk.setBlockAt(2, 15, 1, (byte) 0);
chunk.setBlockAt(0, 0, 0, (byte) 0);
ChunkRenderer chunkRenderer = new ChunkRenderer();
ChunkMesh mesh = new ChunkMesh(chunk);
chunkRenderer.addChunkMesh(mesh);
while (!glfwWindowShouldClose(windowHandle)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
chunkRenderer.draw(cam);
glfwSwapBuffers(windowHandle);
glfwPollEvents();
if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD);
if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD);
if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT);
if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT);
if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP);
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN);
}
chunkRenderer.free();
Callbacks.glfwFreeCallbacks(windowHandle);
glfwDestroyWindow(windowHandle);
glfwTerminate();
glfwSetErrorCallback(null).free();
}
private static long initUI() {
System.out.println("LWJGL Version: " + Version.getVersion()); System.out.println("LWJGL Version: " + Version.getVersion());
GLFWErrorCallback.createPrint(System.err).set(); GLFWErrorCallback.createPrint(System.err).set();
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW"); if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW");
@ -53,77 +94,6 @@ public class Aos2Client {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glCullFace(GL_BACK); glCullFace(GL_BACK);
Chunk chunk = Chunk.random(new Random(1)); return windowHandle;
Camera cam = new Camera();
glfwSetCursorPosCallback(windowHandle, cam);
for (int i = 0; i < 16; i++) {
chunk.setBlockAt(i, 0, 0, (byte) 8);
chunk.setBlockAt(0, i, 0, (byte) 40);
chunk.setBlockAt(0, 0, i, (byte) 120);
}
chunk.setBlockAt(0, 15, 0, (byte) 0);
chunk.setBlockAt(1, 15, 0, (byte) 0);
chunk.setBlockAt(2, 15, 0, (byte) 0);
chunk.setBlockAt(2, 15, 1, (byte) 0);
chunk.setBlockAt(0, 0, 0, (byte) 0);
Matrix4f projectionTransform = new Matrix4f().perspective(70, 800 / 600.0f, 0.01f, 100.0f);
ChunkMesh mesh = new ChunkMesh(chunk);
int shaderProgram = createShaderProgram();
int projectionTransformUniform = glGetUniformLocation(shaderProgram, "projectionTransform");
int viewTransformUniform = glGetUniformLocation(shaderProgram, "viewTransform");
int normalTransformUniform = glGetUniformLocation(shaderProgram, "normalTransform");
glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16]));
long t = 0;
while (!glfwWindowShouldClose(windowHandle)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData());
Matrix3f normalTransform = new Matrix3f();
glUniformMatrix3fv(normalTransformUniform, false, normalTransform.get(new float[9]));
mesh.draw();
glfwSwapBuffers(windowHandle);
glfwPollEvents();
// float n = (float) (8 * Math.sin(Math.toRadians(t)) + 8);
// cam.setPosition(n, -1, n);
// cam.setOrientationDegrees(0, 0);
// Thread.sleep(20);
// t++;
// if (t >= 360) t = 0;
if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD);
if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD);
if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT);
if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT);
if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP);
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN);
}
Callbacks.glfwFreeCallbacks(windowHandle);
glfwDestroyWindow(windowHandle);
glfwTerminate();
glfwSetErrorCallback(null).free();
}
private static int createShaderProgram() throws IOException {
int prog = glCreateProgram();
int fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader, FileUtils.readClasspathFile("shader/fragment.glsl"));
glCompileShader(fragShader);
glAttachShader(prog, fragShader);
int vertShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertShader, FileUtils.readClasspathFile("shader/vertex.glsl"));
glCompileShader(vertShader);
glAttachShader(prog, vertShader);
glValidateProgram(prog);
glLinkProgram(prog);
glUseProgram(prog);
return prog;
} }
} }

View File

@ -156,4 +156,10 @@ public class ChunkMesh {
glDrawElements(GL_TRIANGLES, indiciesCount, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, indiciesCount, GL_UNSIGNED_INT, 0);
} }
public void free() {
glDeleteBuffers(vboId);
glDeleteBuffers(eboId);
glDeleteVertexArrays(vaoId);
}
} }

View File

@ -0,0 +1,52 @@
package nl.andrewl.aos2_client;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.opengl.GL46.*;
public class ChunkRenderer {
private final ShaderProgram shaderProgram;
private final int projectionTransformUniform;
private final int viewTransformUniform;
private final int normalTransformUniform;
private final Matrix4f projectionTransform = new Matrix4f().perspective(70, 800 / 600.0f, 0.01f, 100.0f);
private final List<ChunkMesh> chunkMeshes = new ArrayList<>();
public ChunkRenderer() {
this.shaderProgram = new ShaderProgram.Builder()
.withShader("shader/chunk/vertex.glsl", GL_VERTEX_SHADER)
.withShader("shader/chunk/fragment.glsl", GL_FRAGMENT_SHADER)
.build();
shaderProgram.use();
this.projectionTransformUniform = shaderProgram.getUniform("projectionTransform");
this.viewTransformUniform = shaderProgram.getUniform("viewTransform");
this.normalTransformUniform = shaderProgram.getUniform("normalTransform");
// Preemptively load projection transform, which doesn't change much.
glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16]));
}
public void addChunkMesh(ChunkMesh mesh) {
this.chunkMeshes.add(mesh);
}
public void draw(Camera cam) {
glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData());
Matrix3f normalTransform = new Matrix3f();
glUniformMatrix3fv(normalTransformUniform, false, normalTransform.get(new float[9]));
shaderProgram.use();
for (var mesh : chunkMeshes) mesh.draw();
}
public void free() {
for (var mesh : chunkMeshes) mesh.free();
shaderProgram.free();
}
}

View File

@ -0,0 +1,57 @@
package nl.andrewl.aos2_client;
import nl.andrewl.aos_core.FileUtils;
import java.io.IOException;
import static org.lwjgl.opengl.GL46.*;
/**
* Represents a shader program with one or more individual shaders.
*/
public class ShaderProgram {
private final int id;
public ShaderProgram(int id) {
this.id = id;
}
public void use() {
glUseProgram(id);
}
public int getUniform(String name) {
return glGetUniformLocation(id, name);
}
public void free() {
glDeleteProgram(id);
}
public static class Builder {
private final int id;
public Builder() {
this.id = glCreateProgram();
}
public Builder withShader(String resource, int type) {
int shaderId = glCreateShader(type);
try {
glShaderSource(shaderId, FileUtils.readClasspathFile(resource));
} catch (IOException e) {
throw new RuntimeException(e);
}
glCompileShader(shaderId);
glAttachShader(id, shaderId);
glDeleteShader(shaderId);
return this;
}
public ShaderProgram build() {
glValidateProgram(id);
glLinkProgram(id);
return new ShaderProgram(id);
}
}
}

View File

@ -11,7 +11,7 @@ void main() {
vec3 lightColor = vec3(1.0, 1.0, 0.9); // TODO: Add this via a uniform. vec3 lightColor = vec3(1.0, 1.0, 0.9); // TODO: Add this via a uniform.
vec3 ambientComponent = vec3(0.1, 0.1, 0.1); vec3 ambientComponent = vec3(0.1, 0.1, 0.1);
vec3 diffuseComponent = max(dot(vertexNormal, lightDirection), 0.0) * lightColor; vec3 diffuseComponent = max(dot(vertexNormal * -1, lightDirection), 0.0) * lightColor;
// No specular component. // No specular component.
fragmentColor = vec4((ambientComponent + diffuseComponent) * vertexColor, 1.0); fragmentColor = vec4((ambientComponent + diffuseComponent) * vertexColor, 1.0);

View File

@ -6,6 +6,9 @@ import org.joml.Vector3i;
import java.util.Arrays; import java.util.Arrays;
import java.util.Random; import java.util.Random;
/**
* Holds information about a uniform "chunk" of the voxel world.
*/
public class Chunk { public class Chunk {
/** /**
* The size of a chunk, in terms of the number of blocks on one axis of the cube. * The size of a chunk, in terms of the number of blocks on one axis of the cube.
@ -14,6 +17,20 @@ public class Chunk {
public static final int TOTAL_SIZE = SIZE * SIZE * SIZE; public static final int TOTAL_SIZE = SIZE * SIZE * SIZE;
private final byte[] blocks = new byte[TOTAL_SIZE]; private final byte[] blocks = new byte[TOTAL_SIZE];
private final Vector3i position;
public Chunk(int cx, int cy, int cz) {
this.position = new Vector3i(cx, cy, cz);
}
public Chunk(Vector3i position) {
this.position = new Vector3i(position);
}
public Chunk(Chunk other) {
this(other.position);
System.arraycopy(other.blocks, 0, this.blocks, 0, TOTAL_SIZE);
}
public byte getBlockAt(int x, int y, int z) { public byte getBlockAt(int x, int y, int z) {
if (x < 0 || x >= SIZE || y < 0 || y >= SIZE || z < 0 || z >= SIZE) return 0; if (x < 0 || x >= SIZE || y < 0 || y >= SIZE || z < 0 || z >= SIZE) return 0;
@ -50,14 +67,8 @@ public class Chunk {
return sb.toString(); return sb.toString();
} }
public static Chunk of(byte value) { public static Chunk random(Vector3i position, Random rand) {
Chunk c = new Chunk(); Chunk c = new Chunk(position);
Arrays.fill(c.blocks, value);
return c;
}
public static Chunk random(Random rand) {
Chunk c = new Chunk();
for (int i = 0; i < TOTAL_SIZE; i++) { for (int i = 0; i < TOTAL_SIZE; i++) {
c.blocks[i] = (byte) rand.nextInt(1, 128); c.blocks[i] = (byte) rand.nextInt(1, 128);
} }