From 2219b8c919216c56e12d6d478ef564cf6d4acee8 Mon Sep 17 00:00:00 2001 From: Samadi van Koten Date: Fri, 5 Feb 2021 14:35:31 +0000 Subject: [PATCH] Add order-independent transparency for translucent blocks The algorithm used is "Weighted, Blended Order-Independent Transparency"[1], which allows for a fast and simple implementation at a small cost to color accuracy. This should solve all issues of translucent faces not rendering behind others, such as with water behind stained glass or with blocks containing translucent faces, such as slime and honey blocks. More testing is required to confirm compatibility with limited OpenGL 2.0 implementations, such as those of macOS or older iGPUs, as well as to determine the impact on performance. Fixes #38 [1]: https://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html --- .../client/gl/TranslucencyFramebuffer.java | 162 ++++++++++++++++++ .../sodium/client/gl/shader/ShaderLoader.java | 9 - .../client/render/SodiumWorldRenderer.java | 28 +++ .../render/chunk/ChunkRenderBackend.java | 2 +- .../render/chunk/ChunkRenderManager.java | 54 +++++- .../backends/gl20/GL20ChunkRenderBackend.java | 5 +- .../ChunkRenderBackendMultiDraw.java | 15 +- .../oneshot/ChunkRenderBackendOneshot.java | 21 ++- .../render/chunk/shader/ChunkProgram.java | 12 ++ .../shader/ChunkRenderShaderBackend.java | 45 +++-- .../chunk/shader/TranslucencyProgram.java | 24 +++ .../chunk_rendering/MixinWorldRenderer.java | 5 + .../assets/sodium/shaders/chunk_gl20.f.glsl | 25 ++- .../sodium/shaders/fullscreen_gl20.v.glsl | 7 + .../shaders/translucency_clear_gl20.f.glsl | 6 + .../translucency_composite_gl20.f.glsl | 11 ++ 16 files changed, 395 insertions(+), 36 deletions(-) create mode 100644 src/main/java/me/jellysquid/mods/sodium/client/gl/TranslucencyFramebuffer.java create mode 100644 src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/TranslucencyProgram.java create mode 100644 src/main/resources/assets/sodium/shaders/fullscreen_gl20.v.glsl create mode 100644 src/main/resources/assets/sodium/shaders/translucency_clear_gl20.f.glsl create mode 100644 src/main/resources/assets/sodium/shaders/translucency_composite_gl20.f.glsl diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gl/TranslucencyFramebuffer.java b/src/main/java/me/jellysquid/mods/sodium/client/gl/TranslucencyFramebuffer.java new file mode 100644 index 0000000000..7a7581e317 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/sodium/client/gl/TranslucencyFramebuffer.java @@ -0,0 +1,162 @@ +package me.jellysquid.mods.sodium.client.gl; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.FramebufferInfo; +import com.mojang.blaze3d.systems.RenderSystem; +import me.jellysquid.mods.sodium.client.gl.shader.GlProgram; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderConstants; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderType; +import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.texture.TextureUtil; +import net.minecraft.util.Identifier; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.ARBTextureFloat; +import org.lwjgl.system.MemoryUtil; + +import java.nio.IntBuffer; + +public class TranslucencyFramebuffer extends Framebuffer { + private static GlProgram clearProgram = null; + + private int accumAttachment; + private int revealAttachment; + private int depthAttachment; + + public TranslucencyFramebuffer(int width, int height, boolean useDepth, boolean getError) { + super(width, height, useDepth, getError); + this.accumAttachment = -1; + this.revealAttachment = -1; + this.depthAttachment = -1; + } + + @Override + public void delete() { + super.delete(); + if (this.accumAttachment > -1) { + TextureUtil.deleteId(this.accumAttachment); + this.accumAttachment = -1; + } + if (this.revealAttachment > -1) { + TextureUtil.deleteId(this.revealAttachment); + this.revealAttachment = -1; + } + if (this.depthAttachment > -1) { + TextureUtil.deleteId(this.depthAttachment); + this.depthAttachment = -1; + } + } + + @Override + public void initFbo(int width, int height, boolean getError) { + RenderSystem.assertThread(RenderSystem::isOnRenderThreadOrInit); + this.viewportWidth = width; + this.viewportHeight = height; + this.textureWidth = width; + this.textureHeight = height; + + if (this.useDepthAttachment) { + this.depthAttachment = TextureUtil.generateId(); + GlStateManager.bindTexture(this.depthAttachment); + GlStateManager.texImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_DEPTH_COMPONENT, width, height, 0, GL20.GL_DEPTH_COMPONENT, GL20.GL_FLOAT, null); + } + + this.accumAttachment = TextureUtil.generateId(); + this.revealAttachment = TextureUtil.generateId(); + // This duplicates a vanilla bug, where framebuffer attachments change back to linear filtering when resized. + // Shouldn't affect us because the translucency compositor samples texels directly, bypassing filtering altogether. + this.setTexFilter(GL20.GL_LINEAR); + + GlStateManager.bindTexture(this.accumAttachment); + GlStateManager.texImage2D(GL20.GL_TEXTURE_2D, 0, ARBTextureFloat.GL_RGBA16F_ARB, width, height, 0, GL20.GL_RGBA, GL20.GL_FLOAT, null); + GlStateManager.bindTexture(this.revealAttachment); + GlStateManager.texImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA8, width, height, 0, GL20.GL_ALPHA, GL20.GL_UNSIGNED_BYTE, null); + + this.fbo = GlStateManager.genFramebuffers(); + GlStateManager.bindFramebuffer(FramebufferInfo.FRAME_BUFFER, this.fbo); + GlStateManager.framebufferTexture2D(FramebufferInfo.FRAME_BUFFER, FramebufferInfo.COLOR_ATTACHMENT, GL20.GL_TEXTURE_2D, this.accumAttachment, 0); + GlStateManager.framebufferTexture2D(FramebufferInfo.FRAME_BUFFER, FramebufferInfo.COLOR_ATTACHMENT+1, GL20.GL_TEXTURE_2D, this.revealAttachment, 0); + if (this.useDepthAttachment) { + GlStateManager.framebufferTexture2D(FramebufferInfo.FRAME_BUFFER, FramebufferInfo.DEPTH_ATTACHMENT, GL20.GL_TEXTURE_2D, this.depthAttachment, 0); + } + this.checkFramebufferStatus(); + + IntBuffer attachments = MemoryUtil.memAllocInt(2); + attachments.put(0, FramebufferInfo.COLOR_ATTACHMENT); + attachments.put(1, FramebufferInfo.COLOR_ATTACHMENT+1); + GL20.glDrawBuffers(attachments); + + this.clear(getError); + this.endRead(); + } + + @Override + public void setTexFilter(int filter) { + RenderSystem.assertThread(RenderSystem::isOnRenderThreadOrInit); + this.texFilter = filter; + + GlStateManager.bindTexture(this.accumAttachment); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, filter); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, filter); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_R, GL20.GL_CLAMP); + + GlStateManager.bindTexture(this.revealAttachment); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, filter); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, filter); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP); + GlStateManager.texParameter(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_R, GL20.GL_CLAMP); + + GlStateManager.bindTexture(0); + } + + @Override + public void beginRead() { + RenderSystem.assertThread(RenderSystem::isOnRenderThread); + GlStateManager.bindTexture(this.accumAttachment); + } + + @Override + public void clear(boolean getError) { + RenderSystem.assertThread(RenderSystem::isOnRenderThreadOrInit); + this.beginWrite(true); + + if (TranslucencyFramebuffer.clearProgram == null) { + ShaderConstants empty = ShaderConstants.builder().build(); + TranslucencyFramebuffer.clearProgram = GlProgram.builder(new Identifier("sodium", "translucency_clear")) + .attachShader(ShaderLoader.loadShader(ShaderType.VERTEX, new Identifier("sodium", "fullscreen_gl20.v.glsl"), empty)) + .attachShader(ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "translucency_clear_gl20.f.glsl"), empty)) + .build((program, name) -> new GlProgram(program, name) {}); + } + // clearColor is ignored, but that shouldn't cause problems unless someone misuses this class + // If we do need it in future, it can be done through a uniform + TranslucencyFramebuffer.clearProgram.bind(); + GlStateManager.disableDepthTest(); + GlStateManager.disableBlend(); + GL20.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + GlStateManager.enableBlend(); + GlStateManager.enableDepthTest(); + TranslucencyFramebuffer.clearProgram.unbind(); + + if (this.useDepthAttachment) { + GlStateManager.clear(GL20.GL_DEPTH_BUFFER_BIT, getError); + } + this.endWrite(); + } + + @Override + public int getColorAttachment() { + return this.accumAttachment; + } + @Override + public int getDepthAttachment() { + return this.depthAttachment; + } + + public int getAccumAttachment() { + return this.accumAttachment; + } + public int getRevealAttachment() { + return this.revealAttachment; + } +} diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderLoader.java b/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderLoader.java index 15c1f41d0e..f3a312bc77 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderLoader.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderLoader.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.List; public class ShaderLoader { /** @@ -23,14 +22,6 @@ public static GlShader loadShader(ShaderType type, Identifier name, ShaderConsta return new GlShader(type, name, getShaderSource(getShaderPath(name)), constants); } - /** - * Use {@link ShaderLoader#loadShader(ShaderType, Identifier, ShaderConstants)} instead. This will be removed. - */ - @Deprecated - public static GlShader loadShader(ShaderType type, Identifier name, List constants) { - return new GlShader(type, name, getShaderSource(getShaderPath(name)), ShaderConstants.fromStringList(constants)); - } - private static String getShaderPath(Identifier name) { return String.format("/assets/%s/shaders/%s", name.getNamespace(), name.getPath()); } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java b/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java index 5c96d2433a..af170fc1ae 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java @@ -6,6 +6,11 @@ import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import me.jellysquid.mods.sodium.client.SodiumClientMod; +import me.jellysquid.mods.sodium.client.gl.TranslucencyFramebuffer; +import me.jellysquid.mods.sodium.client.gl.shader.GlProgram; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderConstants; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderType; import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions; import me.jellysquid.mods.sodium.client.model.vertex.type.ChunkVertexType; import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderBackend; @@ -17,6 +22,7 @@ import me.jellysquid.mods.sodium.client.render.chunk.format.DefaultModelVertexFormats; import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPass; import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPassManager; +import me.jellysquid.mods.sodium.client.render.chunk.shader.TranslucencyProgram; import me.jellysquid.mods.sodium.client.render.pipeline.context.GlobalRenderContext; import me.jellysquid.mods.sodium.client.util.math.FrustumExtended; import me.jellysquid.mods.sodium.client.world.ChunkStatusListener; @@ -24,6 +30,7 @@ import me.jellysquid.mods.sodium.common.util.ListUtil; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.Framebuffer; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.render.*; import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher; @@ -31,6 +38,7 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; import net.minecraft.util.math.*; import net.minecraft.util.profiler.Profiler; @@ -43,6 +51,9 @@ public class SodiumWorldRenderer implements ChunkStatusListener { private static SodiumWorldRenderer instance; + public final TranslucencyFramebuffer translucentFramebuffer; + public final TranslucencyProgram translucencyProgram; + private final MinecraftClient client; private ClientWorld world; @@ -86,6 +97,19 @@ public static SodiumWorldRenderer getInstance() { private SodiumWorldRenderer(MinecraftClient client) { this.client = client; + + Framebuffer fb = this.client.getFramebuffer(); + this.translucentFramebuffer = new TranslucencyFramebuffer(fb.viewportWidth, fb.viewportHeight, true, MinecraftClient.IS_SYSTEM_MAC); + + ShaderConstants empty = ShaderConstants.builder().build(); + this.translucencyProgram = GlProgram.builder(new Identifier("sodium", "translucency_compositor")) + .attachShader(ShaderLoader.loadShader(ShaderType.VERTEX, new Identifier("sodium", "fullscreen_gl20.v.glsl"), empty)) + .attachShader(ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "translucency_composite_gl20.f.glsl"), empty)) + .build((program, name) -> new TranslucencyProgram(program, name)); + } + + public Framebuffer getMainFramebuffer() { + return this.client.getFramebuffer(); } public void setWorld(ClientWorld world) { @@ -240,6 +264,10 @@ public void reload() { this.initRenderer(); } + public void onResized(int w, int h) { + this.translucentFramebuffer.resize(w, h, MinecraftClient.IS_SYSTEM_MAC); + } + private void initRenderer() { if (this.chunkRenderManager != null) { this.chunkRenderManager.destroy(); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderBackend.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderBackend.java index ce4145837a..78c7ac2873 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderBackend.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderBackend.java @@ -30,7 +30,7 @@ public interface ChunkRenderBackend { void createShaders(); - void begin(MatrixStack matrixStack); + void begin(MatrixStack matrixStack, boolean translucent); void end(MatrixStack matrixStack); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java index 3a85e22323..82c1d0c360 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java @@ -1,5 +1,7 @@ package me.jellysquid.mods.sodium.client.render.chunk; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -9,6 +11,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; import me.jellysquid.mods.sodium.client.SodiumClientMod; +import me.jellysquid.mods.sodium.client.gl.TranslucencyFramebuffer; import me.jellysquid.mods.sodium.client.gl.util.GlFogHelper; import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer; import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildResult; @@ -28,6 +31,8 @@ import me.jellysquid.mods.sodium.common.util.IdTable; import me.jellysquid.mods.sodium.common.util.collections.FutureDequeDrain; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.Framebuffer; import net.minecraft.client.render.Camera; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; @@ -35,6 +40,7 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.world.chunk.ChunkSection; +import org.lwjgl.opengl.GL20; import java.util.ArrayDeque; import java.util.Collection; @@ -389,9 +395,51 @@ public void renderLayer(MatrixStack matrixStack, BlockRenderPass pass, double x, ChunkRenderList chunkRenderList = this.chunkRenderLists[pass.ordinal()]; ChunkRenderListIterator iterator = chunkRenderList.iterator(pass.isTranslucent()); - this.backend.begin(matrixStack); - this.backend.render(iterator, new ChunkCameraContext(x, y, z)); - this.backend.end(matrixStack); + if (pass.isTranslucent()) { + Framebuffer mfb = this.renderer.getMainFramebuffer(); + mfb.endWrite(); + + TranslucencyFramebuffer fb = this.renderer.translucentFramebuffer; + fb.clear(MinecraftClient.IS_SYSTEM_MAC); + fb.copyDepthFrom(mfb); + + fb.beginWrite(false); + RenderSystem.depthFunc(GL20.GL_ALWAYS); + this.backend.begin(matrixStack, pass.isTranslucent()); + + GlStateManager.activeTexture(GL20.GL_TEXTURE5); + GlStateManager.bindTexture (mfb.getDepthAttachment()); + + this.backend.render(iterator, new ChunkCameraContext(x, y, z)); + + this.backend.end(matrixStack); + RenderSystem.depthFunc(GL20.GL_GREATER); + fb.endWrite(); + + mfb.copyDepthFrom(fb); + mfb.beginWrite(false); + this.compositeOIT(fb); + } else { + this.backend.begin(matrixStack, pass.isTranslucent()); + this.backend.render(iterator, new ChunkCameraContext(x, y, z)); + this.backend.end(matrixStack); + } + } + + public void compositeOIT(TranslucencyFramebuffer fb) { + GlStateManager.disableDepthTest(); + this.renderer.translucencyProgram.bind(); + this.renderer.translucencyProgram.setup(); + + GlStateManager.activeTexture(GL20.GL_TEXTURE3); + GlStateManager.bindTexture(fb.getAccumAttachment()); + GlStateManager.activeTexture(GL20.GL_TEXTURE4); + GlStateManager.bindTexture(fb.getRevealAttachment()); + + GL20.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + + this.renderer.translucencyProgram.unbind(); + GlStateManager.enableDepthTest(); } public void tickVisibleRenders() { diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/backends/gl20/GL20ChunkRenderBackend.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/backends/gl20/GL20ChunkRenderBackend.java index 9fb6f3c8d3..c74af5070e 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/backends/gl20/GL20ChunkRenderBackend.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/backends/gl20/GL20ChunkRenderBackend.java @@ -17,9 +17,8 @@ public GL20ChunkRenderBackend(ChunkVertexType format) { } @Override - public void begin(MatrixStack matrixStack) { - super.begin(matrixStack); - + public void begin(MatrixStack matrixStack, boolean translucent) { + super.begin(matrixStack, translucent); this.vertexFormat.enableVertexAttributes(); } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/multidraw/ChunkRenderBackendMultiDraw.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/multidraw/ChunkRenderBackendMultiDraw.java index e057484cc9..2b008ec744 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/multidraw/ChunkRenderBackendMultiDraw.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/multidraw/ChunkRenderBackendMultiDraw.java @@ -23,18 +23,27 @@ protected ChunkProgramMultiDraw createShaderProgram(Identifier name, int handle, @Override protected GlShader createVertexShader(ChunkFogMode fogMode) { return ShaderLoader.loadShader(ShaderType.VERTEX, new Identifier("sodium", "chunk_gl20.v.glsl"), - this.createShaderConstants(fogMode)); + this.createShaderConstants(fogMode, false)); } @Override protected GlShader createFragmentShader(ChunkFogMode fogMode) { return ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "chunk_gl20.f.glsl"), - this.createShaderConstants(fogMode)); + this.createShaderConstants(fogMode, false)); } - private ShaderConstants createShaderConstants(ChunkFogMode fogMode) { + @Override + protected GlShader createTranslucencyFragmentShader(ChunkFogMode fogMode) { + return ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "chunk_gl20.f.glsl"), + this.createShaderConstants(fogMode, true)); + } + + private ShaderConstants createShaderConstants(ChunkFogMode fogMode, boolean translucency) { ShaderConstants.Builder constants = ShaderConstants.builder(); constants.define("USE_MULTIDRAW"); + if (translucency) { + constants.define("USE_TRANSLUCENCY"); + } fogMode.addConstants(constants); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/oneshot/ChunkRenderBackendOneshot.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/oneshot/ChunkRenderBackendOneshot.java index 72e497c948..00b989186d 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/oneshot/ChunkRenderBackendOneshot.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/oneshot/ChunkRenderBackendOneshot.java @@ -1,6 +1,7 @@ package me.jellysquid.mods.sodium.client.render.chunk.oneshot; import me.jellysquid.mods.sodium.client.gl.shader.GlShader; +import me.jellysquid.mods.sodium.client.gl.shader.ShaderConstants; import me.jellysquid.mods.sodium.client.gl.shader.ShaderLoader; import me.jellysquid.mods.sodium.client.gl.shader.ShaderType; import me.jellysquid.mods.sodium.client.gl.util.BufferSlice; @@ -41,12 +42,28 @@ protected ChunkProgramOneshot createShaderProgram(Identifier name, int handle, C @Override protected GlShader createVertexShader(ChunkFogMode fogMode) { - return ShaderLoader.loadShader(ShaderType.VERTEX, new Identifier("sodium", "chunk_gl20.v.glsl"), fogMode.getDefines()); + return ShaderLoader.loadShader(ShaderType.VERTEX, new Identifier("sodium", "chunk_gl20.v.glsl"), this.createShaderConstants(fogMode, false)); } @Override protected GlShader createFragmentShader(ChunkFogMode fogMode) { - return ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "chunk_gl20.f.glsl"), fogMode.getDefines()); + return ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "chunk_gl20.f.glsl"), this.createShaderConstants(fogMode, false)); + } + + @Override + protected GlShader createTranslucencyFragmentShader(ChunkFogMode fogMode) { + return ShaderLoader.loadShader(ShaderType.FRAGMENT, new Identifier("sodium", "chunk_gl20.f.glsl"), this.createShaderConstants(fogMode, true)); + } + + private ShaderConstants createShaderConstants(ChunkFogMode fogMode, boolean translucency) { + ShaderConstants.Builder constants = ShaderConstants.builder(); + if (translucency) { + constants.define("USE_TRANSLUCENCY"); + } + + fogMode.addConstants(constants); + + return constants.build(); } @Override diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram.java index 4864743141..3c2fcb44ec 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/ChunkProgram.java @@ -23,6 +23,7 @@ public abstract class ChunkProgram extends GlProgram { private final int uModelScale; private final int uBlockTex; private final int uLightTex; + private final int uDepthTex; // The fog shader component used by this program in order to setup the appropriate GL state private final ChunkShaderFogComponent fogShader; @@ -36,12 +37,23 @@ protected ChunkProgram(Identifier name, int handle, Function implements ChunkRenderBackend { private final EnumMap programs = new EnumMap<>(ChunkFogMode.class); + private final EnumMap translucencyPrograms = new EnumMap<>(ChunkFogMode.class); protected final ChunkVertexType vertexType; protected final GlVertexFormat vertexFormat; @@ -28,17 +33,26 @@ public ChunkRenderShaderBackend(ChunkVertexType vertexType) { @Override public final void createShaders() { - this.programs.put(ChunkFogMode.NONE, this.createShader(ChunkFogMode.NONE, this.vertexFormat)); - this.programs.put(ChunkFogMode.LINEAR, this.createShader(ChunkFogMode.LINEAR, this.vertexFormat)); - this.programs.put(ChunkFogMode.EXP2, this.createShader(ChunkFogMode.EXP2, this.vertexFormat)); + this.createShader(ChunkFogMode.NONE, this.vertexFormat, false); + this.createShader(ChunkFogMode.LINEAR, this.vertexFormat, false); + this.createShader(ChunkFogMode.EXP2, this.vertexFormat, false); + + this.createShader(ChunkFogMode.NONE, this.vertexFormat, true); + this.createShader(ChunkFogMode.LINEAR, this.vertexFormat, true); + this.createShader(ChunkFogMode.EXP2, this.vertexFormat, true); } - private P createShader(ChunkFogMode fogMode, GlVertexFormat format) { + private void createShader(ChunkFogMode fogMode, GlVertexFormat format, boolean translucent) { GlShader vertShader = this.createVertexShader(fogMode); - GlShader fragShader = this.createFragmentShader(fogMode); + GlShader fragShader; + if (translucent) { + fragShader = this.createTranslucencyFragmentShader(fogMode); + } else { + fragShader = this.createFragmentShader(fogMode); + } try { - return GlProgram.builder(new Identifier("sodium", "chunk_shader")) + P prog = GlProgram.builder(new Identifier("sodium", "chunk_shader")) .attachShader(vertShader) .attachShader(fragShader) .bindAttribute("a_Pos", format.getAttribute(ChunkMeshAttribute.POSITION)) @@ -46,6 +60,11 @@ private P createShader(ChunkFogMode fogMode, GlVertexFormat .bindAttribute("a_TexCoord", format.getAttribute(ChunkMeshAttribute.TEXTURE)) .bindAttribute("a_LightCoord", format.getAttribute(ChunkMeshAttribute.LIGHT)) .build((program, name) -> this.createShaderProgram(program, name, fogMode)); + if (translucent) { + this.translucencyPrograms.put(fogMode, prog); + } else { + this.programs.put(fogMode, prog); + } } finally { vertShader.delete(); fragShader.delete(); @@ -53,14 +72,19 @@ private P createShader(ChunkFogMode fogMode, GlVertexFormat } protected abstract GlShader createFragmentShader(ChunkFogMode fogMode); - + protected abstract GlShader createTranslucencyFragmentShader(ChunkFogMode fogMode); protected abstract GlShader createVertexShader(ChunkFogMode fogMode); - protected abstract P createShaderProgram(Identifier name, int handle, ChunkFogMode fogMode); @Override - public void begin(MatrixStack matrixStack) { - this.activeProgram = this.programs.get(ChunkFogMode.getActiveMode()); + public void begin(MatrixStack matrixStack, boolean translucent) { + if (translucent) { + this.activeProgram = this.translucencyPrograms.get(ChunkFogMode.getActiveMode()); + GlStateManager.blendFunc(GL20.GL_ONE, GL20.GL_ONE); + ARBDrawBuffersBlend.glBlendFunciARB(1, GL20.GL_ZERO, GL20.GL_ONE_MINUS_SRC_COLOR); + } else { + this.activeProgram = this.programs.get(ChunkFogMode.getActiveMode()); + } this.activeProgram.bind(); this.activeProgram.setup(matrixStack); } @@ -69,6 +93,7 @@ public void begin(MatrixStack matrixStack) { public void end(MatrixStack matrixStack) { this.activeProgram.unbind(); this.activeProgram = null; + RenderSystem.defaultBlendFunc(); } @Override diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/TranslucencyProgram.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/TranslucencyProgram.java new file mode 100644 index 0000000000..55e2a251d0 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/shader/TranslucencyProgram.java @@ -0,0 +1,24 @@ +package me.jellysquid.mods.sodium.client.render.chunk.shader; + +import me.jellysquid.mods.sodium.client.gl.shader.GlProgram; +import org.lwjgl.opengl.GL20; +import net.minecraft.util.Identifier; + +/** + * A compositing program for flattening weighted, blended OIT + */ +public class TranslucencyProgram extends GlProgram { + private final int uAccumTex; + private final int uRevealTex; + + public TranslucencyProgram(Identifier name, int handle) { + super(name, handle); + this.uAccumTex = this.getUniformLocation("u_AccumTex"); + this.uRevealTex = this.getUniformLocation("u_RevealTex"); + } + + public void setup() { + GL20.glUniform1i(this.uAccumTex, 3); + GL20.glUniform1i(this.uRevealTex, 4); + } +} diff --git a/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java b/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java index 4293ca2db2..2789433edc 100644 --- a/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java @@ -130,6 +130,11 @@ private void onReload(CallbackInfo ci) { this.renderer.reload(); } + @Inject(method = "onResized", at = @At("RETURN")) + private void onResized(int w, int h, CallbackInfo ci) { + this.renderer.onResized(w, h); + } + @Inject(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/WorldRenderer;noCullingBlockEntities:Ljava/util/Set;", shift = At.Shift.BEFORE, ordinal = 0)) private void onRenderTileEntities(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) { this.renderer.renderTileEntities(matrices, this.bufferBuilders, this.blockBreakingProgressions, camera, tickDelta); diff --git a/src/main/resources/assets/sodium/shaders/chunk_gl20.f.glsl b/src/main/resources/assets/sodium/shaders/chunk_gl20.f.glsl index c9649f24c5..8e3fa60029 100644 --- a/src/main/resources/assets/sodium/shaders/chunk_gl20.f.glsl +++ b/src/main/resources/assets/sodium/shaders/chunk_gl20.f.glsl @@ -1,4 +1,4 @@ -#version 110 +#version 130 varying vec4 v_Color; // The interpolated vertex color varying vec2 v_TexCoord; // The interpolated block texture coordinates @@ -7,6 +7,10 @@ varying vec2 v_LightCoord; // The interpolated light map texture coordinates uniform sampler2D u_BlockTex; // The block texture sampler uniform sampler2D u_LightTex; // The light map texture sampler +#ifdef USE_TRANSLUCENCY +uniform sampler2D u_DepthTex; // The opaque depth buffer sampler +#endif + #ifdef USE_FOG varying float v_FragDistance; uniform vec4 u_FogColor; // The color of the fog @@ -43,12 +47,23 @@ void main() { vec4 diffuseColor = sampleBlockTex * sampleLightTex * v_Color; #ifdef USE_FOG + // Fog is used, so the fragment color needs to be mixed with the fog + // FIXME: this may not be the correct way to do fog for translucent blocks float fogFactor = clamp(getFogFactor(), 0.0, 1.0); + diffuseColor.rgb = mix(u_FogColor.rgb, diffuseColor.rgb, fogFactor); +#endif + +#ifdef USE_TRANSLUCENCY + // We do depth testing in the shader because we're testing against the opaque depth buffer + if (gl_FragCoord.z > texelFetch(u_DepthTex, ivec2(gl_FragCoord.xy), 0).r) discard; - gl_FragColor = mix(u_FogColor, diffuseColor, fogFactor); - gl_FragColor.a = diffuseColor.a; + diffuseColor.rgb *= diffuseColor.a; // Premultiply alpha + float a = min(1.0, diffuseColor.a)*8.0 + 0.01; + float b = 1.0 - 0.95*gl_FragCoord.z; + float w = clamp(a*a*a * 1e8 * b*b*b, 1e-2, 3e2); + gl_FragData[0] = diffuseColor * w; + gl_FragData[1] = vec4(diffuseColor.a); #else - // No fog is being used, so the fragment color is just that of the blended texture color - gl_FragColor = diffuseColor; + gl_FragData[0] = diffuseColor; #endif } diff --git a/src/main/resources/assets/sodium/shaders/fullscreen_gl20.v.glsl b/src/main/resources/assets/sodium/shaders/fullscreen_gl20.v.glsl new file mode 100644 index 0000000000..13affa1d48 --- /dev/null +++ b/src/main/resources/assets/sodium/shaders/fullscreen_gl20.v.glsl @@ -0,0 +1,7 @@ +#version 130 + +void main() { + float x = (gl_VertexID & 1)*4 - 1; + float y = (gl_VertexID & 2)*2 - 1; + gl_Position = vec4(x, y, 0, 1); +} diff --git a/src/main/resources/assets/sodium/shaders/translucency_clear_gl20.f.glsl b/src/main/resources/assets/sodium/shaders/translucency_clear_gl20.f.glsl new file mode 100644 index 0000000000..875042f1b0 --- /dev/null +++ b/src/main/resources/assets/sodium/shaders/translucency_clear_gl20.f.glsl @@ -0,0 +1,6 @@ +#version 110 + +void main() { + gl_FragData[0] = vec4(0); + gl_FragData[1] = vec4(1); +} diff --git a/src/main/resources/assets/sodium/shaders/translucency_composite_gl20.f.glsl b/src/main/resources/assets/sodium/shaders/translucency_composite_gl20.f.glsl new file mode 100644 index 0000000000..ff80505f0d --- /dev/null +++ b/src/main/resources/assets/sodium/shaders/translucency_composite_gl20.f.glsl @@ -0,0 +1,11 @@ +#version 130 + +uniform sampler2D u_AccumTex; +uniform sampler2D u_RevealTex; + +void main() { + float reveal = 1.0 - texelFetch(u_RevealTex, ivec2(gl_FragCoord.xy), 0).a; + if (reveal == 0.0) discard; // completely transparent, ignore this fragment + vec4 accum = texelFetch(u_AccumTex, ivec2(gl_FragCoord.xy), 0); + gl_FragColor = vec4(accum.rgb / max(accum.a, 1e-5), reveal); +}