Add far clip fading

This commit is contained in:
James Seibel
2025-10-25 11:06:19 -05:00
parent e33fa3cb5e
commit 789306ccff
10 changed files with 581 additions and 7 deletions
@@ -312,6 +312,14 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> dhFadeFarClipPlane = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "Should DH fade out before reaching the far clip plane? \n"
+ "This is helpful to prevent DH clouds from cutting off in the distance. \n"
+ "")
.build();
public static ConfigEntry<Double> brightnessMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
.set(1.0)
.comment(""
@@ -30,7 +30,6 @@ import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.*;
@@ -97,6 +96,15 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiMcRenderingFadeMode.DOUBLE_PASS);
this.put(EDhApiQualityPreset.EXTREME, EDhApiMcRenderingFadeMode.DOUBLE_PASS);
}});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhFadeFarClipPlane = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, false);
this.put(EDhApiQualityPreset.LOW, false);
this.put(EDhApiQualityPreset.MEDIUM, true);
this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, true);
}});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhDither = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.ditherDhFade,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
@@ -140,6 +148,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.configList.add(this.horizontalQuality);
this.configList.add(this.transparency);
this.configList.add(this.ssaoEnabled);
this.configList.add(this.dhFadeFarClipPlane);
this.configList.add(this.vanillaFade);
this.configList.add(this.dhDither);
this.configList.add(this.caveCulling);
@@ -0,0 +1,161 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.renderer.shaders.DhFadeApplyShader;
import com.seibel.distanthorizons.core.render.renderer.shaders.DhFadeShader;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
/**
* Handles fading MC and DH together via {@link DhFadeShader} and {@link DhFadeApplyShader}. <br><br>
*
* {@link DhFadeShader} - draws the Fade to a texture. <br>
* {@link DhFadeApplyShader} - draws the Fade texture to MC's FrameBuffer. <br>
*/
public class DhFadeRenderer
{
public static DhFadeRenderer INSTANCE = new DhFadeRenderer();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private boolean init = false;
private int width = -1;
private int height = -1;
private int fadeFramebuffer = -1;
private int fadeTexture = -1;
//=============//
// constructor //
//=============//
private DhFadeRenderer() { }
public void init()
{
if (this.init) return;
this.init = true;
DhFadeShader.INSTANCE.init();
DhFadeApplyShader.INSTANCE.init();
}
private void createFramebuffer(int width, int height)
{
if (this.fadeFramebuffer != -1)
{
GL32.glDeleteFramebuffers(this.fadeFramebuffer);
this.fadeFramebuffer = -1;
}
this.fadeFramebuffer = GL32.glGenFramebuffers();
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer);
if (this.fadeTexture != -1)
{
GLMC.glDeleteTextures(this.fadeTexture);
this.fadeTexture = -1;
}
this.fadeTexture = GL32.glGenTextures();
GLMC.glBindTexture(this.fadeTexture);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
}
//========//
// render //
//========//
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
{
GLState mcState = new GLState();
try
{
profiler.push("Fade Generate");
this.init();
// resize the framebuffer if necessary
int width = MC_RENDER.getTargetFramebufferViewportWidth();
int height = MC_RENDER.getTargetFramebufferViewportHeight();
if (this.width != width || this.height != height)
{
this.width = width;
this.height = height;
this.createFramebuffer(width, height);
}
DhFadeShader.INSTANCE.frameBuffer = this.fadeFramebuffer;
DhFadeShader.INSTANCE.setProjectionMatrix(mcModelViewMatrix, mcProjectionMatrix, partialTicks);
DhFadeShader.INSTANCE.render(partialTicks);
// restored so we can write the fade texture to the main frame buffer
//mcState.restore();
profiler.popPush("Fade Apply");
DhFadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
DhFadeApplyShader.INSTANCE.render(partialTicks);
}
catch (Exception e)
{
LOGGER.error("Unexpected error during fade render, error: ["+e.getMessage()+"].", e);
}
finally
{
// make sure we always revert to MC's state to prevent GL state corruption
// this is especially important on MC 1.16.5 or when other rendering mods are present
mcState.restore();
profiler.pop();
}
}
}
@@ -242,6 +242,15 @@ public class LodRenderer
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ false);
}
// far plane clip fading
if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get())
{
profiler.popPush("Fade Far Clip Fade");
DhFadeRenderer.INSTANCE.render(
new Mat4f(renderParams.mcModelViewMatrix), new Mat4f(renderParams.mcProjectionMatrix),
renderParams.partialTicks, profiler);
}
// fog
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get())
{
@@ -122,14 +122,14 @@ public class VanillaFadeRenderer
{
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-RenderLevel");
profiler.push("DH-Vanilla Fade");
GLState mcState = new GLState();
try
{
profiler.push("Fade Generate");
profiler.push("Vanilla Fade Generate");
this.init();
@@ -149,10 +149,7 @@ public class VanillaFadeRenderer
VanillaFadeShader.INSTANCE.setLevelMaxHeight(level.getMaxHeight());
VanillaFadeShader.INSTANCE.render(partialTicks);
// restored so we can write the fade texture to the main frame buffer
//mcState.restore();
profiler.popPush("Fade Apply");
profiler.popPush("Vanilla Fade Apply");
// Applying the fade texture is only needed if MC is drawing to their own frame buffer,
// otherwise we can directly render to their texture
@@ -0,0 +1,128 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.DhFadeRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32;
/**
* Draws the Fade texture onto Minecraft's FrameBuffer. <br><br>
*
* See Also: <br>
* {@link DhFadeRenderer} - Parent to this shader. <br>
* {@link DhFadeShader} - draws the Fade texture. <br>
*/
public class DhFadeApplyShader extends AbstractShaderRenderer
{
public static DhFadeApplyShader INSTANCE = new DhFadeApplyShader();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
public int fadeTexture;
// uniforms
public int uFadeColorTextureUniform = -1;
public int uDhDepthTextureUniform = -1;
public int uMcDepthTextureUniform = -1;
//=============//
// constructor //
//=============//
@Override
public void onInit()
{
this.shader = new ShaderProgram(
"shaders/normal.vert",
"shaders/dhFade/apply.frag",
"fragColor",
new String[]{ "vPosition" });
// uniform setup
this.uFadeColorTextureUniform = this.shader.getUniformLocation("uFadeColorTextureUniform");
this.uDhDepthTextureUniform = this.shader.getUniformLocation("uDhDepthTextureUniform");
this.uMcDepthTextureUniform = this.shader.getUniformLocation("uMcDepthTextureUniform");
}
//=============//
// render prep //
//=============//
@Override
protected void onApplyUniforms(float partialTicks)
{
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(this.fadeTexture);
GL32.glUniform1i(this.uFadeColorTextureUniform, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
GLMC.glBindTexture(LodRenderer.INSTANCE.getActiveDepthTextureId());
GL32.glUniform1i(this.uDhDepthTextureUniform, 1);
GLMC.glActiveTexture(GL32.GL_TEXTURE2);
GLMC.glBindTexture(MC_RENDER.getDepthTextureId());
GL32.glUniform1i(this.uMcDepthTextureUniform, 2);
}
//========//
// render //
//========//
@Override
protected void onRender()
{
GLMC.disableBlend();
// Depth testing must be disabled otherwise this application shader won't apply anything.
// setting this isn't necessary in vanilla, but some mods may change this, requiring it to be set manually,
// it should be automatically restored after rendering is complete.
GLMC.disableDepthTest();
// apply the rendered Fade to Minecraft's framebuffer
GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, DhFadeShader.INSTANCE.frameBuffer);
GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.INSTANCE.getActiveFramebufferId());
ScreenQuad.INSTANCE.render();
GLMC.enableDepthTest();
}
}
@@ -0,0 +1,165 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32;
public class DhFadeShader extends AbstractShaderRenderer
{
public static DhFadeShader INSTANCE = new DhFadeShader();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
public int frameBuffer = -1;
private Mat4f inverseDhMvmProjMatrix;
// Uniforms
/** Inverted Model View Projection matrix */
public int uDhInvMvmProj = -1;
public int uDhDepthTexture = -1;
public int uMcColorTexture = -1;
public int uDhColorTexture = -1;
public int uStartFadeBlockDistance = -1;
public int uEndFadeBlockDistance = -1;
//=============//
// constructor //
//=============//
public DhFadeShader() { }
@Override
public void onInit()
{
this.shader = new ShaderProgram(
"shaders/normal.vert", "shaders/dhFade/fade.frag",
"fragColor", new String[]{"vPosition"}
);
// all uniforms should be tryGet...
// because disabling fade can cause the GLSL to optimize out most (if not all) uniforms
// near fade
this.uDhInvMvmProj = this.shader.tryGetUniformLocation("uDhInvMvmProj");
this.uDhDepthTexture = this.shader.tryGetUniformLocation("uDhDepthTexture");
this.uMcColorTexture = this.shader.tryGetUniformLocation("uMcColorTexture");
this.uDhColorTexture = this.shader.tryGetUniformLocation("uDhColorTexture");
this.uStartFadeBlockDistance = this.shader.tryGetUniformLocation("uStartFadeBlockDistance");
this.uEndFadeBlockDistance = this.shader.tryGetUniformLocation("uEndFadeBlockDistance");
}
//=============//
// render prep //
//=============//
@Override
protected void onApplyUniforms(float partialTicks)
{
this.shader.setUniform(this.uDhInvMvmProj, this.inverseDhMvmProjMatrix);
float dhFarClipDistance = RenderUtil.getFarClipPlaneDistanceInBlocks();
// measured in blocks
float fadeStartDistance = dhFarClipDistance * 0.5f;
float fadeEndDistance = dhFarClipDistance * 0.9f;
this.shader.setUniform(this.uStartFadeBlockDistance, fadeStartDistance);
this.shader.setUniform(this.uEndFadeBlockDistance, fadeEndDistance);
}
public void setProjectionMatrix(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{
Mat4f dhProjectionMatrix = RenderUtil.createLodProjectionMatrix(mcProjectionMatrix, partialTicks);
Mat4f dhModelViewMatrix = RenderUtil.createLodModelViewMatrix(mcModelViewMatrix);
Mat4f inverseDhModelViewProjectionMatrix = new Mat4f(dhProjectionMatrix);
inverseDhModelViewProjectionMatrix.multiply(dhModelViewMatrix);
inverseDhModelViewProjectionMatrix.invert();
this.inverseDhMvmProjMatrix = inverseDhModelViewProjectionMatrix;
}
//========//
// render //
//========//
@Override
protected void onRender()
{
int depthTextureId = LodRenderer.INSTANCE.getActiveDepthTextureId();
int colorTextureId = LodRenderer.INSTANCE.getActiveColorTextureId();
if (depthTextureId == -1
|| colorTextureId == -1)
{
// the renderer is currently being re-built and/or inactive,
// we don't need to/can't render fading
return;
}
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer);
GLMC.disableScissorTest();
GLMC.disableDepthTest();
GLMC.disableBlend();
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(depthTextureId);
GL32.glUniform1i(this.uDhDepthTexture, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
GLMC.glBindTexture(MC_RENDER.getColorTextureId());
GL32.glUniform1i(this.uMcColorTexture, 1);
GLMC.glActiveTexture(GL32.GL_TEXTURE2);
GLMC.glBindTexture(colorTextureId);
GL32.glUniform1i(this.uDhColorTexture, 2);
ScreenQuad.INSTANCE.render();
}
}
@@ -158,6 +158,10 @@
"Vanilla Fade Mode",
"distanthorizons.config.client.advanced.graphics.quality.vanillaFadeMode.@tooltip":
"How should vanilla Minecraft fade into Distant Horizons LODs? \n\nNONE: Fastest, there will be a pronounced border between DH and MC rendering. \nSINGLE_PASS: Fades after MC's transparent pass, opaque blocks underwater won't be faded. \nDOUBLE_PASS: Slowest, fades after both MC's opaque and transparent passes, provides the smoothest transition.",
"distanthorizons.config.client.advanced.graphics.quality.dhFadeFarClipPlane":
"Fade Before Far Clip Plane",
"distanthorizons.config.client.advanced.graphics.quality.dhFadeFarClipPlane.@tooltip":
"Should DH fade out before reaching the far plane? \nThis is helpful to prevent DH clouds from cutting off in the distance.",
"distanthorizons.config.client.advanced.graphics.quality.brightnessMultiplier":
"Brightness Multiplier",
"distanthorizons.config.client.advanced.graphics.quality.brightnessMultiplier.@tooltip":
@@ -0,0 +1,27 @@
#version 150 core
in vec2 TexCoord;
out vec4 fragColor;
uniform sampler2D uFadeColorTextureUniform;
uniform sampler2D uDhDepthTextureUniform;
uniform sampler2D uMcDepthTextureUniform;
void main()
{
fragColor = vec4(1.0);
float dhFragmentDepth = textureLod(uDhDepthTextureUniform, TexCoord, 0).r;
float mcFragmentDepth = textureLod(uMcDepthTextureUniform, TexCoord, 0).r;
// a fragment depth of "1" means the fragment wasn't drawn to,
// only update fragments that were drawn to
// TODO this check is currently ignored as a test, this may need to be re-enabled later
if (dhFragmentDepth != 10 && mcFragmentDepth != 0)
{
fragColor = texture(uFadeColorTextureUniform, TexCoord);
}
}
@@ -0,0 +1,66 @@
#version 150 core
in vec2 TexCoord;
out vec4 fragColor;
// inverted model view matrix and projection matrix
uniform mat4 uDhInvMvmProj;
uniform sampler2D uDhDepthTexture;
uniform sampler2D uMcColorTexture;
uniform sampler2D uDhColorTexture;
uniform float uStartFadeBlockDistance;
uniform float uEndFadeBlockDistance;
vec3 calcViewPosition(float fragmentDepth, mat4 invMvmProj)
{
// normalized device coordinates
vec4 ndc = vec4(TexCoord.xy, fragmentDepth, 1.0);
ndc.xyz = ndc.xyz * 2.0 - 1.0;
vec4 eyeCoord = invMvmProj * ndc;
return eyeCoord.xyz / eyeCoord.w;
}
/**
* Used to fade out vanilla chunks so the transition
* between DH and vanilla is smoother.
*/
void main()
{
// includes both the vanilla chunks as well as DH
vec4 combinedMcDhColor = texture(uMcColorTexture, TexCoord);
// just the DH render pass
vec4 dhColor = texture(uDhColorTexture, TexCoord);
// the DH texture will have white if nothing was written to that pixel.
// TODO replace with a depth texture check, this feels janky
if (dhColor == vec4(1))
{
// if not done vanilla clouds will render incorrectly at night
dhColor = combinedMcDhColor;
}
float dhFragmentDepth = texture(uDhDepthTexture, TexCoord).r;
vec3 dhVertexWorldPos = calcViewPosition(dhFragmentDepth, uDhInvMvmProj);
float dhFragmentDistance = length(dhVertexWorldPos.xzy);
float startFade = uEndFadeBlockDistance;
float endFade = uStartFadeBlockDistance;
// Smoothly transition between combinedMcDhColor and uDhColorTexture
// as the depth increases from the camera
float fadeStep = smoothstep(startFade, endFade, dhFragmentDistance);
fragColor = mix(combinedMcDhColor, dhColor, fadeStep);
fragColor.a = 1.0; // TODO is setting the alpha needed?
}