From c09a3a52acaffbcb389ea6d7005916f59f73f1db Mon Sep 17 00:00:00 2001 From: x2048 Date: Wed, 28 Jun 2023 05:30:08 +0200 Subject: [PATCH] Add antialiasing filters (FXAA, SSAA) (#13253) --- builtin/settingtypes.txt | 29 +++-- client/shaders/fxaa/opengl_fragment.glsl | 116 ++++++++++++++++++ client/shaders/fxaa/opengl_vertex.glsl | 27 ++++ .../shaders/second_stage/opengl_fragment.glsl | 10 ++ src/client/game.cpp | 9 +- src/client/render/plain.cpp | 5 + src/client/render/secondstage.cpp | 41 ++++++- src/client/renderingengine.cpp | 3 +- src/client/shader.cpp | 6 + src/defaultsettings.cpp | 1 + 10 files changed, 230 insertions(+), 17 deletions(-) create mode 100644 client/shaders/fxaa/opengl_fragment.glsl create mode 100644 client/shaders/fxaa/opengl_vertex.glsl diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index d32b30750..573715a26 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -342,14 +342,27 @@ texture_clean_transparent (Clean transparent textures) bool false # texture autoscaling. texture_min_size (Minimum texture size) int 64 1 32768 -# Use multi-sample antialiasing (MSAA) to smooth out block edges. -# This algorithm smooths out the 3D viewport while keeping the image sharp, -# but it doesn't affect the insides of textures -# (which is especially noticeable with transparent textures). -# Visible spaces appear between nodes when shaders are disabled. -# If set to 0, MSAA is disabled. -# A restart is required after changing this option. -fsaa (FSAA) enum 0 0,1,2,4,8,16 +# Select the antialiasing method to apply. +# +# * None - No antialiasing (default) +# +# * FSAA - Hardware-provided full-screen antialiasing (incompatible with shaders) +# A.K.A multi-sample antialiasing (MSAA) +# Smoothens out block edges but does not affect the insides of textures. +# A restart is required to change this option. +# +# * FXAA - Fast approximate antialiasing (requires shaders) +# Applies a post-processing filter to detect and smoothen high-contrast edges. +# Provides balance between speed and image quality. +# +# * SSAA - Super-sampling antialiasing (requires shaders) +# Renders higher-resolution image of the scene, then scales down to reduce +# the aliasing effects. This is the slowest and the most accurate method. +antialiasing (Antialiasing method) enum none none,fsaa,fxaa,ssaa + +# Defines size of the sampling grid for FSAA and SSAA antializasing methods. +# Value of 2 means taking 2x2 = 4 samples. +fsaa (Anti-aliasing scale) enum 2 2,4,8,16 [**Occlusion Culling] diff --git a/client/shaders/fxaa/opengl_fragment.glsl b/client/shaders/fxaa/opengl_fragment.glsl new file mode 100644 index 000000000..130e689ea --- /dev/null +++ b/client/shaders/fxaa/opengl_fragment.glsl @@ -0,0 +1,116 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform vec2 texelSize0; + +varying vec2 sampleNW; +varying vec2 sampleNE; +varying vec2 sampleSW; +varying vec2 sampleSE; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +/** +Basic FXAA implementation based on the code on geeks3d.com with the +modification that the texture2DLod stuff was removed since it's +unsupported by WebGL. +-- +From: +https://github.com/mitsuhiko/webgl-meincraft +Copyright (c) 2011 by Armin Ronacher. +Some rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FXAA_REDUCE_MIN + #define FXAA_REDUCE_MIN (1.0/ 128.0) +#endif +#ifndef FXAA_REDUCE_MUL + #define FXAA_REDUCE_MUL (1.0 / 8.0) +#endif +#ifndef FXAA_SPAN_MAX + #define FXAA_SPAN_MAX 8.0 +#endif + +//optimized version for mobile, where dependent +//texture reads can be a bottleneck +vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 inverseVP, + vec2 v_rgbNW, vec2 v_rgbNE, + vec2 v_rgbSW, vec2 v_rgbSE, + vec2 v_rgbM) { + vec4 color; + vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; + vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; + vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; + vec3 rgbSE = texture2D(tex, v_rgbSE).xyz; + vec4 texColor = texture2D(tex, v_rgbM); + vec3 rgbM = texColor.xyz; + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot(rgbM, luma); + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + mediump vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * + (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), + dir * rcpDirMin)) * inverseVP; + + vec3 rgbA = 0.5 * ( + texture2D(tex, fragCoord + dir * (1.0 / 3.0 - 0.5)).xyz + + texture2D(tex, fragCoord + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * 0.5 + 0.25 * ( + texture2D(tex, fragCoord + dir * -0.5).xyz + + texture2D(tex, fragCoord + dir * 0.5).xyz); + + float lumaB = dot(rgbB, luma); + if ((lumaB < lumaMin) || (lumaB > lumaMax)) + color = vec4(rgbA, 1.0); + else + color = vec4(rgbB, 1.0); + return color; +} + +void main(void) +{ + vec2 uv = varTexCoord.st; + + gl_FragColor = fxaa(rendered, uv, texelSize0, + sampleNW, sampleNE, sampleSW, sampleSE, uv); +} diff --git a/client/shaders/fxaa/opengl_vertex.glsl b/client/shaders/fxaa/opengl_vertex.glsl new file mode 100644 index 000000000..26913c28e --- /dev/null +++ b/client/shaders/fxaa/opengl_vertex.glsl @@ -0,0 +1,27 @@ +uniform vec2 texelSize0; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +varying vec2 sampleNW; +varying vec2 sampleNE; +varying vec2 sampleSW; +varying vec2 sampleSE; + +/* +Based on +https://github.com/mattdesl/glsl-fxaa/ +Portions Copyright (c) 2011 by Armin Ronacher. +*/ +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + sampleNW = varTexCoord.st + vec2(-1.0, -1.0) * texelSize0; + sampleNE = varTexCoord.st + vec2(1.0, -1.0) * texelSize0; + sampleSW = varTexCoord.st + vec2(-1.0, 1.0) * texelSize0; + sampleSE = varTexCoord.st + vec2(1.0, 1.0) * texelSize0; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/second_stage/opengl_fragment.glsl b/client/shaders/second_stage/opengl_fragment.glsl index ac83c34eb..928e408e2 100644 --- a/client/shaders/second_stage/opengl_fragment.glsl +++ b/client/shaders/second_stage/opengl_fragment.glsl @@ -8,6 +8,8 @@ struct ExposureParams { uniform sampler2D rendered; uniform sampler2D bloom; +uniform vec2 texelSize0; + uniform ExposureParams exposureParams; uniform lowp float bloomIntensity; uniform lowp float saturation; @@ -80,7 +82,15 @@ vec3 applySaturation(vec3 color, float factor) void main(void) { vec2 uv = varTexCoord.st; +#ifdef ENABLE_SSAA + vec4 color = vec4(0.); + for (float dx = 1.; dx < SSAA_SCALE; dx += 2.) + for (float dy = 1.; dy < SSAA_SCALE; dy += 2.) + color += texture2D(rendered, uv + texelSize0 * vec2(dx, dy)).rgba; + color /= SSAA_SCALE * SSAA_SCALE / 4.; +#else vec4 color = texture2D(rendered, uv).rgba; +#endif // translate to linear colorspace (approximate) color.rgb = pow(color.rgb, vec3(2.2)); diff --git a/src/client/game.cpp b/src/client/game.cpp index 48ec15d91..0ea712cca 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -387,7 +387,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_texture1; CachedPixelShaderSetting m_texture2; CachedPixelShaderSetting m_texture3; - CachedPixelShaderSetting m_texel_size0; + CachedVertexShaderSetting m_texel_size0_vertex; + CachedPixelShaderSetting m_texel_size0_pixel; std::array m_texel_size0_values; CachedStructPixelShaderSetting m_exposure_params_pixel; float m_user_exposure_compensation; @@ -445,7 +446,8 @@ public: m_texture1("texture1"), m_texture2("texture2"), m_texture3("texture3"), - m_texel_size0("texelSize0"), + m_texel_size0_vertex("texelSize0"), + m_texel_size0_pixel("texelSize0"), m_exposure_params_pixel("exposureParams", std::array { "luminanceMin", "luminanceMax", "exposureCorrection", @@ -547,7 +549,8 @@ public: tex_id = 3; m_texture3.set(&tex_id, services); - m_texel_size0.set(m_texel_size0_values.data(), services); + m_texel_size0_vertex.set(m_texel_size0_values.data(), services); + m_texel_size0_pixel.set(m_texel_size0_values.data(), services); const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure; std::array exposure_buffer = { diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp index 60abbb97a..dccdaedb3 100644 --- a/src/client/render/plain.cpp +++ b/src/client/render/plain.cpp @@ -130,6 +130,11 @@ RenderStep* addUpscaling(RenderPipeline *pipeline, RenderStep *previousStep, v2f if (downscale_factor.X == 1.0f && downscale_factor.Y == 1.0f) return previousStep; + // When shaders are enabled, post-processing pipeline takes care of rescaling + if (g_settings->getBool("enable_shaders")) + return previousStep; + + // Initialize buffer TextureBuffer *buffer = pipeline->createOwned(); buffer->setTexture(TEXTURE_UPSCALE, downscale_factor, "upscale", video::ECF_A8R8G8B8); diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index ee8d41c38..096dfd15c 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -118,9 +118,23 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep static const u8 TEXTURE_BLOOM = 2; static const u8 TEXTURE_EXPOSURE_1 = 3; static const u8 TEXTURE_EXPOSURE_2 = 4; + static const u8 TEXTURE_FXAA = 5; static const u8 TEXTURE_BLOOM_DOWN = 10; static const u8 TEXTURE_BLOOM_UP = 20; + // Super-sampling is simply rendering into a larger texture. + // Downscaling is done by the final step when rendering to the screen. + const std::string antialiasing = g_settings->get("antialiasing"); + const bool enable_bloom = g_settings->getBool("enable_bloom"); + const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure"); + const bool enable_ssaa = antialiasing == "ssaa"; + const bool enable_fxaa = antialiasing == "fxaa"; + + if (enable_ssaa) { + u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); + scale *= ssaa_scale; + } + buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", color_format); buffer->setTexture(TEXTURE_EXPOSURE_1, core::dimension2du(1,1), "exposure_1", color_format, /*clear:*/ true); buffer->setTexture(TEXTURE_EXPOSURE_2, core::dimension2du(1,1), "exposure_2", color_format, /*clear:*/ true); @@ -135,8 +149,6 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // Number of mipmap levels of the bloom downsampling texture const u8 MIPMAP_LEVELS = 4; - const bool enable_bloom = g_settings->getBool("enable_bloom"); - const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure"); // post-processing stage @@ -175,6 +187,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep } } + // Bloom pt 2 if (enable_bloom) { // upsample shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH); @@ -188,6 +201,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep } } + // Dynamic Exposure pt2 if (enable_auto_exposure) { shader_id = client->getShaderSource()->getShader("update_exposure", TILE_MATERIAL_PLAIN, NDT_MESH); auto update_exposure = pipeline->addStep(shader_id, std::vector { TEXTURE_EXPOSURE_1, u8(TEXTURE_BLOOM_DOWN + MIPMAP_LEVELS - 1) }); @@ -196,11 +210,28 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep update_exposure->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_EXPOSURE_2)); } - // final post-processing + // FXAA + u8 final_stage_source = TEXTURE_COLOR; + + if (enable_fxaa) { + final_stage_source = TEXTURE_FXAA; + + buffer->setTexture(TEXTURE_FXAA, scale, "fxaa", color_format); + shader_id = client->getShaderSource()->getShader("fxaa", TILE_MATERIAL_PLAIN); + PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { TEXTURE_COLOR }); + pipeline->addStep(effect); + effect->setBilinearFilter(0, true); + effect->setRenderSource(buffer); + effect->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_FXAA)); + } + + // final merge shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); - PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { TEXTURE_COLOR, TEXTURE_BLOOM_UP, TEXTURE_EXPOSURE_2 }); + PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { final_stage_source, TEXTURE_BLOOM_UP, TEXTURE_EXPOSURE_2 }); pipeline->addStep(effect); - effect->setBilinearFilter(1, true); // apply filter to the bloom + if (enable_ssaa) + effect->setBilinearFilter(0, true); + effect->setBilinearFilter(1, true); effect->setRenderSource(buffer); if (enable_auto_exposure) { diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 6a3b1226f..9d03a752f 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -122,7 +122,8 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); - u16 fsaa = g_settings->getU16("fsaa"); + bool enable_fsaa = g_settings->get("antialiasing") == "fsaa"; + u16 fsaa = enable_fsaa ? g_settings->getU16("fsaa") : 0; // Determine driver auto driverType = chooseVideoDriver(); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index ce662b41d..1ef5056c6 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -780,6 +780,12 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, if (g_settings->getBool("enable_auto_exposure")) shaders_header << "#define ENABLE_AUTO_EXPOSURE 1\n"; + if (g_settings->get("antialiasing") == "ssaa") { + shaders_header << "#define ENABLE_SSAA 1\n"; + u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); + shaders_header << "#define SSAA_SCALE " << ssaa_scale << ".\n"; + } + shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics std::string common_header = shaders_header.str(); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 2b32b92bb..a6bc987f2 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -264,6 +264,7 @@ void set_default_settings() settings->setDefault("enable_waving_plants", "false"); settings->setDefault("exposure_compensation", "0.0"); settings->setDefault("enable_auto_exposure", "false"); + settings->setDefault("antialiasing", "none"); settings->setDefault("enable_bloom", "false"); settings->setDefault("enable_bloom_debug", "false"); settings->setDefault("bloom_strength_factor", "1.0");