Add experimental DH/vanilla fading

This commit is contained in:
James Seibel
2024-09-30 21:59:19 -05:00
parent db524efba0
commit f63e3d4b6d
10 changed files with 571 additions and 2 deletions
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
@@ -550,6 +551,15 @@ public class ClientApi
}
}
/** should be called after DH and MC finish rendering so we can smooth the transition between the two */
public void renderFade(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{
if (Config.Client.Advanced.Graphics.Quality.fadeOutVanillaRendering.get())
{
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks);
}
}
@@ -255,6 +255,16 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> fadeOutVanillaRendering = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true vanilla chunks will fade out the further away they are \n"
+ "smoothing the transition between Distant Horizons and vanilla rendering. \n"
+ "")
.setPerformance(EConfigEntryPerformance.LOW)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
// TODO fixme
// public static ConfigEntry<Integer> lodBiomeBlending = new ConfigEntry.Builder<Integer>()
// .setMinDefaultMax(0,1,7)
@@ -0,0 +1,166 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.renderer.shaders.FadeApplyShader;
import com.seibel.distanthorizons.core.render.renderer.shaders.FadeShader;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
/**
* Handles fading MC and DH together via {@link FadeShader} and {@link FadeApplyShader}. <br><br>
*
* {@link FadeShader} - draws the Fade to a texture. <br>
* {@link FadeApplyShader} - draws the Fade texture to MC's FrameBuffer. <br>
*/
public class FadeRenderer
{
public static FadeRenderer INSTANCE = new FadeRenderer();
private static final Logger LOGGER = LogManager.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private boolean init = false;
private int width = -1;
private int height = -1;
private int fadeFramebuffer = -1;
private int fadeTexture = -1;
//=============//
// constructor //
//=============//
private FadeRenderer() { }
public void init()
{
if (this.init) return;
this.init = true;
FadeShader.INSTANCE.init();
FadeApplyShader.INSTANCE.init();
}
private void createFramebuffer(int width, int height)
{
if (this.fadeFramebuffer != -1)
{
GL32.glDeleteFramebuffers(this.fadeFramebuffer);
this.fadeFramebuffer = -1;
}
if (this.fadeTexture != -1)
{
GL32.glDeleteTextures(this.fadeTexture);
this.fadeTexture = -1;
}
this.fadeFramebuffer = GL32.glGenFramebuffers();
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer);
this.fadeTexture = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, 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 = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-RenderLevel");
GLState mcState = new GLState();
GLState state = 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);
}
FadeShader.INSTANCE.frameBuffer = this.fadeFramebuffer;
FadeShader.INSTANCE.setProjectionMatrix(mcModelViewMatrix, mcProjectionMatrix);
FadeShader.INSTANCE.render(partialTicks);
// restored so we can write the fade texture to the main frame buffer
mcState.restore();
profiler.popPush("Fade Apply");
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
FadeApplyShader.INSTANCE.render(partialTicks);
profiler.pop();
}
catch (Exception e)
{
LOGGER.error("Unexpected error during fade render, error: ["+e.getMessage()+"].", e);
}
finally
{
state.restore();
}
}
public void free()
{
FadeShader.INSTANCE.free();
FadeApplyShader.INSTANCE.free();
}
}
@@ -98,7 +98,7 @@ public class FogRenderer
// render //
//========//
public void render(GLState primaryState, Mat4f projectionMatrix, float partialTicks)
public void render(GLState primaryState, Mat4f modelViewProjectionMatrix, float partialTicks)
{
GLState state = new GLState();
this.init();
@@ -114,7 +114,7 @@ public class FogRenderer
}
FogShader.INSTANCE.frameBuffer = this.fogFramebuffer;
FogShader.INSTANCE.setProjectionMatrix(projectionMatrix);
FogShader.INSTANCE.setProjectionMatrix(modelViewProjectionMatrix);
FogShader.INSTANCE.render(partialTicks);
// restored so we can write the SSAO texture to the main frame buffer
@@ -0,0 +1,121 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.FadeRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
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 FadeRenderer} - Parent to this shader. <br>
* {@link FadeShader} - draws the Fade texture. <br>
*/
public class FadeApplyShader extends AbstractShaderRenderer
{
public static FadeApplyShader INSTANCE = new FadeApplyShader();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.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/fade/apply.frag",
"fragColor",
new String[]{ "vPosition" });
// uniform setup
this.uFadeColorTextureUniform = this.shader.getUniformLocation("uFadeColorTextureUniform");
this.uMcDepthTextureUniform = this.shader.getUniformLocation("uMcDepthTextureUniform");
}
//=============//
// render prep //
//=============//
@Override
protected void onApplyUniforms(float partialTicks)
{
GL32.glActiveTexture(GL32.GL_TEXTURE0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.fadeTexture);
GL32.glUniform1i(this.uFadeColorTextureUniform, 0);
GL32.glActiveTexture(GL32.GL_TEXTURE1);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.uDhDepthTextureUniform, 1);
GL32.glActiveTexture(GL32.GL_TEXTURE2);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, MC_RENDER.getDepthTextureId());
GL32.glUniform1i(this.uMcDepthTextureUniform, 2);
}
//========//
// render //
//========//
@Override
protected void onRender()
{
GL32.glEnable(GL32.GL_BLEND);
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GL32.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
// 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.
GL32.glDisable(GL32.GL_DEPTH_TEST);
// apply the rendered Fade to Minecraft's framebuffer
GL32.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, FadeShader.INSTANCE.frameBuffer);
GL32.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
ScreenQuad.INSTANCE.render();
}
}
@@ -0,0 +1,160 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.GLState;
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.LodUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32;
public class FadeShader extends AbstractShaderRenderer
{
public static FadeShader INSTANCE = new FadeShader();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public int frameBuffer = -1;
private Mat4f inverseMvmProjMatrix;
// Uniforms
public int uMcDepthTexture = -1;
public int uCombinedMcDhColorTexture = -1;
public int uDhColorTexture = -1;
/** Inverted Model View Projection matrix */
public int uInvMvmProj = -1;
public int uStartFadeBlockDistance = -1;
public int uEndFadeBlockDistance = -1;
//=============//
// constructor //
//=============//
public FadeShader() { }
@Override
public void onInit()
{
this.shader = new ShaderProgram(
"shaders/normal.vert", "shaders/fade/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.uInvMvmProj = this.shader.tryGetUniformLocation("uInvMvmProj");
this.uMcDepthTexture = this.shader.tryGetUniformLocation("uMcDepthMap");
this.uCombinedMcDhColorTexture = this.shader.tryGetUniformLocation("uCombinedMcDhColorTexture");
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)
{
if (this.inverseMvmProjMatrix != null)
{
this.shader.setUniform(this.uInvMvmProj, this.inverseMvmProjMatrix);
}
int vanillaBlockRenderDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
// measured in blocks
float fadeStartDistance = vanillaBlockRenderDistance * 0.5f;
float fadeEndDistance = vanillaBlockRenderDistance * 0.8f;
if (this.uStartFadeBlockDistance != -1) this.shader.setUniform(this.uStartFadeBlockDistance, fadeStartDistance);
if (this.uEndFadeBlockDistance != -1) this.shader.setUniform(this.uEndFadeBlockDistance, fadeEndDistance);
}
public void setProjectionMatrix(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix)
{
Mat4f inverseModelViewProjectionMatrix = new Mat4f(mcProjectionMatrix);
inverseModelViewProjectionMatrix.multiply(mcModelViewMatrix);
inverseModelViewProjectionMatrix.invert();
this.inverseMvmProjMatrix = inverseModelViewProjectionMatrix;
}
//========//
// render //
//========//
@Override
protected void onRender()
{
GLState state = new GLState();
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer);
GL32.glDisable(GL32.GL_SCISSOR_TEST);
GL32.glDisable(GL32.GL_DEPTH_TEST);
GL32.glDisable(GL32.GL_BLEND);
GL32.glActiveTexture(GL32.GL_TEXTURE0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, MC_RENDER.getDepthTextureId());
GL32.glUniform1i(this.uMcDepthTexture, 0);
GL32.glActiveTexture(GL32.GL_TEXTURE1);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, MC_RENDER.getColorTextureId());
GL32.glUniform1i(this.uCombinedMcDhColorTexture, 1);
GL32.glActiveTexture(GL32.GL_TEXTURE2);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveColorTextureId());
GL32.glUniform1i(this.uDhColorTexture, 2);
// this is necessary for MC 1.16 (IE Legacy OpenGL)
// otherwise the framebuffer isn't cleared correctly and the fade smears across the screen
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
ScreenQuad.INSTANCE.render();
state.restore();
}
}
@@ -64,6 +64,7 @@ public interface IMinecraftRenderWrapper extends IBindable
int getTargetFrameBuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
int getDepthTextureId();
int getColorTextureId();
int getTargetFrameBufferViewportWidth();
int getTargetFrameBufferViewportHeight();
@@ -114,6 +114,10 @@
"Tint With Avoided Blocks",
"distanthorizons.config.client.advanced.graphics.quality.tintWithAvoidedBlocks.@tooltip":
"§4Note: makes snow, carpet, and trapdoors look really bad§r\nShould the blocks underneath avoided blocks gain the color of the avoided block?\n§6True:§r a red flower on grass will tint the grass below it red\n§6False:§r skipped blocks will not change color of surface below them",
"distanthorizons.config.client.advanced.graphics.quality.fadeOutVanillaRendering":
"Fade Out Vanilla Rendering",
"distanthorizons.config.client.advanced.graphics.quality.fadeOutVanillaRendering.@tooltip":
"If true vanilla chunks will fade out the further away they are\nsmoothing the transition between Distant Horizons and vanilla rendering.",
"distanthorizons.config.client.advanced.graphics.quality.lodBiomeBlending":
"Biome Blending",
"distanthorizons.config.client.advanced.graphics.quality.lodBiomeBlending.@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,70 @@
#version 150 core
in vec2 TexCoord;
out vec4 fragColor;
uniform sampler2D uMcDepthTexture;
uniform sampler2D uCombinedMcDhColorTexture;
uniform sampler2D uDhColorTexture;
// inverted model view matrix and projection matrix
uniform mat4 uInvMvmProj;
uniform float uStartFadeBlockDistance;
uniform float uEndFadeBlockDistance;
vec3 calcViewPosition(float fragmentDepth)
{
// normalized device coordinates
vec4 ndc = vec4(TexCoord.xy, fragmentDepth, 1.0);
ndc.xyz = ndc.xyz * 2.0 - 1.0;
vec4 eyeCoord = uInvMvmProj * 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(uCombinedMcDhColorTexture, 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;
}
// a fragment depth of "1" means the fragment wasn't drawn to,
// we only want to fade vanilla rendered objects, not to the sky or LODs
float mcFragmentDepth = texture(uMcDepthTexture, TexCoord).r;
if (mcFragmentDepth < 1.0)
{
// fade based on distance from the camera
vec3 vertexWorldPos = calcViewPosition(mcFragmentDepth);
float fragmentDistance = length(vertexWorldPos.xzy);
// Smoothly transition between combinedMcDhColor and uDhColorTexture
// as the depth increases from the camera
float fadeStep = smoothstep(uStartFadeBlockDistance, uEndFadeBlockDistance, fragmentDistance);
fragColor = mix(combinedMcDhColor, dhColor, fadeStep);
fragColor.a = 1.0; // TODO is setting the alpha needed?
}
else
{
fragColor = vec4(combinedMcDhColor.rgb, 0.0);
}
}