From 2ea75821a1951a6fb13e36f9972a6b5e23896dd7 Mon Sep 17 00:00:00 2001 From: notalpha Date: Mon, 12 Jun 2023 11:37:18 +0200 Subject: [PATCH] feat: SSAO --- .../com/seibel/lod/core/config/Config.java | 5 + .../lod/core/render/renderer/LodRenderer.java | 9 +- .../core/render/renderer/SSAORenderer.java | 196 ++++++++++++++++++ .../minecraft/IMinecraftRenderWrapper.java | 1 + .../main/resources/assets/lod/lang/en_us.json | 4 + .../main/resources/shaders/ssao/ao-frag.frag | 67 ++++++ .../main/resources/shaders/ssao/ao-vert.vert | 10 + .../resources/shaders/ssao/apply-frag.frag | 13 ++ 8 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/com/seibel/lod/core/render/renderer/SSAORenderer.java create mode 100644 core/src/main/resources/shaders/ssao/ao-frag.frag create mode 100644 core/src/main/resources/shaders/ssao/ao-vert.vert create mode 100644 core/src/main/resources/shaders/ssao/apply-frag.frag diff --git a/core/src/main/java/com/seibel/lod/core/config/Config.java b/core/src/main/java/com/seibel/lod/core/config/Config.java index 7d66f3419..6a40335d3 100644 --- a/core/src/main/java/com/seibel/lod/core/config/Config.java +++ b/core/src/main/java/com/seibel/lod/core/config/Config.java @@ -186,6 +186,11 @@ public class Config + "Higher settings will render higher quality fake chunks farther away, \n" + " but will increase memory and GPU usage.") .build(); + + public static ConfigEntry ssao = new ConfigEntry.Builder() + .set(true) + .comment("Enable Screen Space Ambient Occlusion") + .build(); public static ConfigEntry horizontalQuality = new ConfigEntry.Builder() .set(EHorizontalQuality.MEDIUM) diff --git a/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java index b0c9a8013..d4a86b088 100644 --- a/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/lod/core/render/renderer/LodRenderer.java @@ -20,7 +20,6 @@ package com.seibel.lod.core.render.renderer; import com.seibel.lod.core.config.Config; -import com.seibel.lod.core.config.types.ConfigEntry; import com.seibel.lod.api.enums.rendering.EDebugMode; import com.seibel.lod.api.enums.rendering.EFogColorMode; import com.seibel.lod.core.dependencyInjection.SingletonInjector; @@ -242,6 +241,10 @@ public class LodRenderer //TODO: Directional culling bufferHandler.renderOpaque(this); + if (Config.Client.Advanced.Graphics.Quality.ssao.get()) { + SSAORenderer.INSTANCE.render(partialTicks); + } + //======================// // render transparency // //======================// @@ -254,7 +257,7 @@ public class LodRenderer } //if (drawCall==0) // tickLogger.info("DrawCall Count: {}", drawCount); - + //================// // render cleanup // //================// @@ -263,7 +266,7 @@ public class LodRenderer LagSpikeCatcher drawCleanup = new LagSpikeCatcher(); lightmap.unbind(); if (ENABLE_IBO) quadIBO.unbind(); - + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); shaderProgram.unbind(); diff --git a/core/src/main/java/com/seibel/lod/core/render/renderer/SSAORenderer.java b/core/src/main/java/com/seibel/lod/core/render/renderer/SSAORenderer.java new file mode 100644 index 000000000..338950e7b --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/render/renderer/SSAORenderer.java @@ -0,0 +1,196 @@ +package com.seibel.lod.core.render.renderer; + +import com.seibel.lod.api.enums.config.EGpuUploadMethod; +import com.seibel.lod.core.dependencyInjection.SingletonInjector; +import com.seibel.lod.core.render.glObject.GLState; +import com.seibel.lod.core.render.glObject.buffer.GLVertexBuffer; +import com.seibel.lod.core.render.glObject.shader.ShaderProgram; +import com.seibel.lod.core.render.glObject.vertexAttribute.VertexAttribute; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.util.RenderUtil; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.lod.coreapi.util.math.Mat4f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL32; + +import javax.swing.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.function.Consumer; + +public class SSAORenderer { + public static SSAORenderer INSTANCE = new SSAORenderer(); + + public SSAORenderer() { + } + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + + private static final float[] box_vertices = { + -1, -1, + 1, -1, + 1, 1, + -1, -1, + 1, 1, + -1, 1, + }; + + ShaderProgram ssaoShader; + ShaderProgram applyShader; + GLVertexBuffer boxBuffer; + VertexAttribute va; + boolean init = false; + + private static final int MAX_KERNEL_SIZE = 32; + private float[] kernel = new float[MAX_KERNEL_SIZE * 3]; + + public void init() { + if (init) return; + + init = true; + va = VertexAttribute.create(); + va.bind(); + // Pos + va.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addVec2Pointer(false)); + va.completeAndCheck(Float.BYTES * 2); + ssaoShader = new ShaderProgram("shaders/ssao/ao-vert.vert", "shaders/ssao/ao-frag.frag", + "fragColor", new String[]{"vPosition"}); + + applyShader = new ShaderProgram("shaders/ssao/ao-vert.vert", "shaders/ssao/apply-frag.frag", + "fragColor", new String[]{"vPosition"}); + + + // Generate kernel + kernel = genKernel(); + // Framebuffer + createBuffer(); + } + + private int width = -1; + private int height = -1; + private int ssaoFramebuffer = -1; + + private int ssaoTexture = -1; + + private void createFramebuffer(int width, int height) { + if (ssaoFramebuffer != -1) { + GL32.glDeleteFramebuffers(ssaoFramebuffer); + ssaoFramebuffer = -1; + } + + if (ssaoTexture != -1) { + GL32.glDeleteTextures(ssaoTexture); + ssaoTexture = -1; + } + + ssaoFramebuffer = GL32.glGenFramebuffers(); + GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, ssaoFramebuffer); + + ssaoTexture = GL32.glGenTextures(); + GL32.glBindTexture(GL32.GL_TEXTURE_2D, ssaoTexture); + GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RED, width, height, 0, GL32.GL_RED, GL32.GL_FLOAT, (ByteBuffer) null); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST); + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, ssaoTexture, 0); + } + + private void createBuffer() { + ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES); + buffer.order(ByteOrder.nativeOrder()); + buffer.asFloatBuffer().put(box_vertices); + buffer.rewind(); + boxBuffer = new GLVertexBuffer(false); + boxBuffer.bind(); + boxBuffer.uploadBuffer(buffer, box_vertices.length, EGpuUploadMethod.DATA, box_vertices.length * Float.BYTES); + } + + private static float[] genKernel() { + float[] kernel = new float[MAX_KERNEL_SIZE * 3]; + for (int i = 0; i < MAX_KERNEL_SIZE; i++) { + float sampleX = (float) (Math.random() * 2.0 - 1.0); + float sampleY = (float) (Math.random() * 2.0 - 1.0); + float sampleZ = (float) Math.random(); + + + // Normalize + float magnitude = (float) Math.sqrt(Math.pow(sampleX, 2) + Math.pow(sampleY, 2) + Math.pow(sampleZ, 2)); + sampleX /= magnitude; + sampleY /= magnitude; + sampleZ /= magnitude; + + float scale = i / (float) MAX_KERNEL_SIZE; + float interpolatedScale = (float) (0.1 + (scale * scale) * (0.9)); + + sampleX *= interpolatedScale; + sampleY *= interpolatedScale; + sampleZ *= interpolatedScale; + kernel[i * 3] = sampleX; + kernel[i * 3 + 1] = sampleY; + kernel[i * 3 + 2] = sampleZ; + } + return kernel; + } + + public void render(float partialTicks) { + GLState state = new GLState(); + init(); + //GL32.glDepthMask(false); + int width = MC_RENDER.getTargetFrameBufferViewportWidth(); + int height = MC_RENDER.getTargetFrameBufferViewportHeight(); + + if (this.width != width || this.height != height) { + this.width = width; + this.height = height; + createFramebuffer(width, height); + } + + GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, ssaoFramebuffer); + GL32.glViewport(0, 0, width, height); + GL32.glDisable(GL32.GL_DEPTH_TEST); + GL32.glDisable(GL32.GL_BLEND); + GL32.glDisable(GL32.GL_SCISSOR_TEST); + + + Mat4f perspective = Mat4f.perspective( + (float) MC_RENDER.getFov(partialTicks), + MC_RENDER.getTargetFrameBufferViewportWidth() / (float) MC_RENDER.getTargetFrameBufferViewportHeight(), + RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks), + (float) ((RenderUtil.getFarClipPlaneDistanceInBlocks() + LodUtil.REGION_WIDTH) * Math.sqrt(2))); + + ssaoShader.bind(); + ssaoShader.setUniform(ssaoShader.getUniformLocation("gProj"), perspective); + ssaoShader.setUniform(ssaoShader.getUniformLocation("gSampleRad"), 3.0f); + ssaoShader.setUniform(ssaoShader.getUniformLocation("gFactor"), 0.8f); + ssaoShader.setUniform(ssaoShader.getUniformLocation("gPower"), 1.0f); + va.bind(); + va.bindBufferToAllBindingPoint(boxBuffer.getId()); + + GL32.glActiveTexture(GL32.GL_TEXTURE0); + GL32.glBindTexture(GL32.GL_TEXTURE_2D, MC_RENDER.getDepthTextureId()); + + GL32.glUniform3fv(ssaoShader.getUniformLocation("gKernel"), kernel); + GL32.glUniform1i(ssaoShader.getUniformLocation("gDepthMap"), 0); + GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, 6); + + applyShader.bind(); + GL32.glEnable(GL11.GL_BLEND); + GL32.glBlendFunc(GL32.GL_ZERO, GL32.GL_SRC_ALPHA); + GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer()); + GL32.glActiveTexture(GL32.GL_TEXTURE0); + GL32.glBindTexture(GL32.GL_TEXTURE_2D, ssaoTexture); + GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, 6); + + //GL32.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, ssaoFramebuffer); + //GL32.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer()); + //GL32.glBlitFramebuffer( + // 0, 0, width, height, + // 0, 0, width, height, + // GL11.GL_COLOR_BUFFER_BIT, + // GL11.GL_NEAREST + //); + + state.restore(); + } + + +} diff --git a/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java index 86e81ec63..6a667b914 100644 --- a/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java +++ b/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java @@ -74,6 +74,7 @@ public interface IMinecraftRenderWrapper extends IBindable int getScreenHeight(); int getTargetFrameBuffer(); + int getDepthTextureId(); int getTargetFrameBufferViewportWidth(); int getTargetFrameBufferViewportHeight(); diff --git a/core/src/main/resources/assets/lod/lang/en_us.json b/core/src/main/resources/assets/lod/lang/en_us.json index 226ac4ee0..c1187332e 100644 --- a/core/src/main/resources/assets/lod/lang/en_us.json +++ b/core/src/main/resources/assets/lod/lang/en_us.json @@ -97,6 +97,10 @@ "Horizontal Scale", "lod.config.client.advanced.graphics.quality.horizontalScale.@tooltip": "How quickly LODs drop off in quality.\n\nLarger numbers will improve how distant terrain looks\nbut will increase memory and GPU usage.", + "lod.config.client.advanced.graphics.quality.ssao": + "SSAO", + "lod.config.client.advanced.graphics.quality.ssao.@tooltip": + "Screen Space Ambient Occlusion adds depth to the lighting of blocks.", "lod.config.client.advanced.graphics.quality.horizontalQuality": "Horizontal Quality", "lod.config.client.advanced.graphics.quality.horizontalQuality.@tooltip": diff --git a/core/src/main/resources/shaders/ssao/ao-frag.frag b/core/src/main/resources/shaders/ssao/ao-frag.frag new file mode 100644 index 000000000..70b647df5 --- /dev/null +++ b/core/src/main/resources/shaders/ssao/ao-frag.frag @@ -0,0 +1,67 @@ +#version 150 core + +in vec2 TexCoord; +in vec2 ViewRay; + +out vec4 fragColor; + +uniform sampler2D gDepthMap; +uniform float gSampleRad; +uniform float gFactor; +uniform float gPower; +uniform mat4 gProj; + +const int MAX_KERNEL_SIZE = 32; +const float INV_MAX_KERNEL_SIZE_F = 1.0 / float(MAX_KERNEL_SIZE); +const vec2 HALF_2 = vec2(0.5); +uniform vec3 gKernel[MAX_KERNEL_SIZE]; + +vec3 calcViewPosition(vec2 coords) { + float fragmentDepth = texture(gDepthMap, coords).r; + + vec4 ndc = vec4( + coords.x * 2.0 - 1.0, + coords.y * 2.0 - 1.0, + fragmentDepth * 2.0 - 1.0, + 1.0 + ); + + vec4 vs_pos = inverse(gProj) * ndc; + vs_pos.xyz = vs_pos.xyz / vs_pos.w; + return vs_pos.xyz; +} + +void main() +{ + vec3 viewPos = calcViewPosition(TexCoord); + vec3 viewNormal = normalize(cross(dFdy(viewPos.xyz), dFdx(viewPos.xyz)) * -1.0); + + vec3 randomVec = vec3( + 0.0, + -1.0, + 0.0 + ); + + vec3 tangent = normalize(randomVec - viewNormal * dot(randomVec, viewNormal)); + vec3 bitangent = cross(viewNormal, tangent); + mat3 TBN = mat3(tangent, bitangent, viewNormal); + float occlusion_factor = 0.0; + for (int i = 0; i < MAX_KERNEL_SIZE; i++) { + vec3 samplePos = vec3(0.0) + (TBN * gKernel[i]); + samplePos = viewPos + samplePos * gSampleRad; + + vec4 offset = vec4(samplePos, 1.0); + offset = gProj * offset; + offset.xy /= offset.w; + offset.xy = offset.xy * HALF_2 + HALF_2; + + float geometryDepth = calcViewPosition(offset.xy).z; + + float rangeCheck = smoothstep(0.0, 1.0, gSampleRad / abs(viewPos.z - geometryDepth)); + occlusion_factor += float(geometryDepth >= samplePos.z + 0.0) * rangeCheck; + + } + + float visibility_factor = 1.0 - (occlusion_factor / MAX_KERNEL_SIZE); + fragColor = vec4(clamp(1.0 - ((1.0 - pow(visibility_factor, gFactor)) * gPower), 0.1, 1.0)); +} \ No newline at end of file diff --git a/core/src/main/resources/shaders/ssao/ao-vert.vert b/core/src/main/resources/shaders/ssao/ao-vert.vert new file mode 100644 index 000000000..a2e967ae0 --- /dev/null +++ b/core/src/main/resources/shaders/ssao/ao-vert.vert @@ -0,0 +1,10 @@ +#version 150 core + +in vec2 vPosition; +out vec2 TexCoord; + +void main() +{ + gl_Position = vec4(vPosition, 1.0, 1.0); + TexCoord = vPosition.xy * 0.5 + 0.5; +} \ No newline at end of file diff --git a/core/src/main/resources/shaders/ssao/apply-frag.frag b/core/src/main/resources/shaders/ssao/apply-frag.frag new file mode 100644 index 000000000..db95aebd8 --- /dev/null +++ b/core/src/main/resources/shaders/ssao/apply-frag.frag @@ -0,0 +1,13 @@ +#version 150 core + +in vec2 TexCoord; +in vec2 ViewRay; + +out vec4 fragColor; + +uniform sampler2D gSSAOMap; + +void main() +{ + fragColor = vec4(0.0, 0.0, 0.0, texture(gSSAOMap, TexCoord).r); +} \ No newline at end of file