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); +}