diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 91cc3ec32..e4d345e2d 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -449,6 +449,44 @@ shadow_soft_radius (Soft shadow radius) float 5.0 1.0 15.0 # Minimum value: 0.0; maximum value: 60.0 shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0 +[**Post processing] + +# Set the exposure compensation factor. +# This factor is applied to linear color value +# before all other post-processing effects. +# Value of 1.0 (default) means no exposure compensation. +# Range: from 0.1 to 10.0 +exposure_factor (Exposure Factor) float 1.0 0.1 10.0 + +[**Bloom] + +# Set to true to enable bloom effect. +# Bright colors will bleed over the neighboring objects. +enable_bloom (Enable Bloom) bool false + +# Set to true to render debugging breakdown of the bloom effect. +# In debug mode, the screen is split into 4 quadrants: +# top-left - processed base image, top-right - final image +# bottom-left - raw base image, bottom-right - bloom texture. +enable_bloom_debug (Enable Bloom Debug) bool false + +# Set to true to use dedicated texture at each step of bloom effect. +# This is a compatibility setting to avoid visual artifacts +# on certain GPUs and video drivers. +enable_bloom_dedicated_texture (Enable Bloom Dedicated Texture) bool false + +# Set the intensity of bloom +# Smaller values make bloom more subtle +# Range: from 0.01 to 1.0, default: 0.05 +bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0 + +# Set the radius of the bloom filter in pixels. +# Larger values render more glow around bright objects +# at the cost of higher resource consumption. +# Range: from 1 to 64, default: 16 +bloom_radius (Bloom Radius) int 16 1 64 + + [*Audio] # Volume of all sounds. diff --git a/client/shaders/blur_h/opengl_fragment.glsl b/client/shaders/blur_h/opengl_fragment.glsl new file mode 100644 index 000000000..c5818f9d8 --- /dev/null +++ b/client/shaders/blur_h/opengl_fragment.glsl @@ -0,0 +1,29 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform vec2 texelSize0; +uniform mediump float bloomRadius = 3.0; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + // kernel distance and linear size + mediump float n = 2. * bloomRadius + 1.; + + vec2 uv = varTexCoord.st - vec2(bloomRadius * texelSize0.x, 0.); + vec4 color = vec4(0.); + mediump float sum = 0.; + for (mediump float i = 0.; i < n; i++) { + mediump float weight = pow(1. - (abs(i / bloomRadius - 1.)), 1.3); + color += texture2D(rendered, uv).rgba * weight; + sum += weight; + uv += vec2(texelSize0.x, 0.); + } + color /= sum; + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/blur_h/opengl_vertex.glsl b/client/shaders/blur_h/opengl_vertex.glsl new file mode 100644 index 000000000..12692c296 --- /dev/null +++ b/client/shaders/blur_h/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/blur_v/opengl_fragment.glsl b/client/shaders/blur_v/opengl_fragment.glsl new file mode 100644 index 000000000..6820ca10d --- /dev/null +++ b/client/shaders/blur_v/opengl_fragment.glsl @@ -0,0 +1,29 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform vec2 texelSize0; +uniform mediump float bloomRadius = 3.0; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + // kernel distance and linear size + mediump float n = 2. * bloomRadius + 1.; + + vec2 uv = varTexCoord.st - vec2(0., bloomRadius * texelSize0.y); + vec4 color = vec4(0.); + mediump float sum = 0.; + for (mediump float i = 0.; i < n; i++) { + mediump float weight = pow(1. - (abs(i / bloomRadius - 1.)), 1.3); + color += texture2D(rendered, uv).rgba * weight; + sum += weight; + uv += vec2(0., texelSize0.y); + } + color /= sum; + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/blur_v/opengl_vertex.glsl b/client/shaders/blur_v/opengl_vertex.glsl new file mode 100644 index 000000000..12692c296 --- /dev/null +++ b/client/shaders/blur_v/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/extract_bloom/opengl_fragment.glsl b/client/shaders/extract_bloom/opengl_fragment.glsl new file mode 100644 index 000000000..8c25328ac --- /dev/null +++ b/client/shaders/extract_bloom/opengl_fragment.glsl @@ -0,0 +1,21 @@ +#define rendered texture0 + +uniform sampler2D rendered; +uniform mediump float exposureFactor = 2.5; +uniform float bloomLuminanceThreshold = 1.0; + +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + + +void main(void) +{ + vec2 uv = varTexCoord.st; + vec4 color = texture2D(rendered, uv).rgba; + // translate to linear colorspace (approximate) + color.rgb = pow(color.rgb, vec3(2.2)) * exposureFactor; + gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. +} diff --git a/client/shaders/extract_bloom/opengl_vertex.glsl b/client/shaders/extract_bloom/opengl_vertex.glsl new file mode 100644 index 000000000..12692c296 --- /dev/null +++ b/client/shaders/extract_bloom/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} diff --git a/client/shaders/second_stage/opengl_fragment.glsl b/client/shaders/second_stage/opengl_fragment.glsl index 965450fcb..b929b33b4 100644 --- a/client/shaders/second_stage/opengl_fragment.glsl +++ b/client/shaders/second_stage/opengl_fragment.glsl @@ -1,6 +1,10 @@ -uniform sampler2D baseTexture; +#define rendered texture0 +#define bloom texture1 -#define rendered baseTexture +uniform sampler2D rendered; +uniform sampler2D bloom; +uniform mediump float exposureFactor = 2.5; +uniform lowp float bloomIntensity = 1.0; #ifdef GL_ES varying mediump vec2 varTexCoord; @@ -8,6 +12,24 @@ varying mediump vec2 varTexCoord; centroid varying vec2 varTexCoord; #endif +#if ENABLE_BLOOM + +vec4 applyBloom(vec4 color, vec2 uv) +{ + float bias = bloomIntensity; + vec4 bloom = texture2D(bloom, uv); +#if ENABLE_BLOOM_DEBUG + if (uv.x > 0.5 && uv.y < 0.5) + return vec4(bloom.rgb, color.a); + if (uv.x < 0.5) + return color; +#endif + color.rgb = mix(color.rgb, bloom.rgb, bias); + return color; +} + +#endif + #if ENABLE_TONE_MAPPING /* Hable's UC2 Tone mapping parameters @@ -28,15 +50,13 @@ vec3 uncharted2Tonemap(vec3 x) vec4 applyToneMapping(vec4 color) { - color = vec4(pow(color.rgb, vec3(2.2)), color.a); - const float gamma = 1.6; - const float exposureBias = 5.5; + const float exposureBias = 2.0; color.rgb = uncharted2Tonemap(exposureBias * color.rgb); // Precalculated white_scale from //vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W)); vec3 whiteScale = vec3(1.036015346); color.rgb *= whiteScale; - return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a); + return color; } #endif @@ -45,9 +65,36 @@ void main(void) vec2 uv = varTexCoord.st; vec4 color = texture2D(rendered, uv).rgba; -#if ENABLE_TONE_MAPPING - color = applyToneMapping(color); + // translate to linear colorspace (approximate) + color.rgb = pow(color.rgb, vec3(2.2)); + +#if ENABLE_BLOOM_DEBUG + if (uv.x > 0.5 || uv.y > 0.5) #endif + { + color.rgb *= exposureFactor; + } + + +#if ENABLE_BLOOM + color = applyBloom(color, uv); +#endif + +#if ENABLE_BLOOM_DEBUG + if (uv.x > 0.5 || uv.y > 0.5) +#endif + { +#if ENABLE_TONE_MAPPING + color = applyToneMapping(color); +#else + color.rgb /= 2.5; // default exposure factor, see also RenderingEngine::DEFAULT_EXPOSURE_FACTOR; +#endif + } + + color.rgb = clamp(color.rgb, vec3(0.), vec3(1.)); + + // return to sRGB colorspace (approximate) + color.rgb = pow(color.rgb, vec3(1.0 / 2.2)); gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. } diff --git a/src/client/game.cpp b/src/client/game.cpp index d484e8193..e62d0f4a3 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -405,6 +405,7 @@ typedef s32 SamplerLayer_t; class GameGlobalShaderConstantSetter : public IShaderConstantSetter { Sky *m_sky; + Client *m_client; bool *m_force_fog_off; f32 *m_fog_range; bool m_fog_enabled; @@ -419,16 +420,31 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_minimap_yaw; CachedPixelShaderSetting m_camera_offset_pixel; CachedPixelShaderSetting m_camera_offset_vertex; - CachedPixelShaderSetting m_base_texture; - CachedPixelShaderSetting m_normal_texture; - CachedPixelShaderSetting m_texture_flags; - Client *m_client; + CachedPixelShaderSetting m_texture0; + CachedPixelShaderSetting m_texture1; + CachedPixelShaderSetting m_texture2; + CachedPixelShaderSetting m_texture3; + CachedPixelShaderSetting m_texel_size0; + std::array m_texel_size0_values; + CachedPixelShaderSetting m_exposure_factor_pixel; + float m_user_exposure_factor; + bool m_bloom_enabled; + CachedPixelShaderSetting m_bloom_intensity_pixel; + float m_bloom_intensity; + CachedPixelShaderSetting m_bloom_radius_pixel; + float m_bloom_radius; public: void onSettingsChange(const std::string &name) { if (name == "enable_fog") m_fog_enabled = g_settings->getBool("enable_fog"); + if (name == "exposure_factor") + m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f); + if (name == "bloom_intensity") + m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); + if (name == "bloom_radius") + m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f); } static void settingsCallback(const std::string &name, void *userdata) @@ -441,6 +457,7 @@ public: GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, f32 *fog_range, Client *client) : m_sky(sky), + m_client(client), m_force_fog_off(force_fog_off), m_fog_range(fog_range), m_sky_bg_color("skyBgColor"), @@ -454,13 +471,24 @@ public: m_minimap_yaw("yawVec"), m_camera_offset_pixel("cameraOffset"), m_camera_offset_vertex("cameraOffset"), - m_base_texture("baseTexture"), - m_normal_texture("normalTexture"), - m_texture_flags("textureFlags"), - m_client(client) + m_texture0("texture0"), + m_texture1("texture1"), + m_texture2("texture2"), + m_texture3("texture3"), + m_texel_size0("texelSize0"), + m_exposure_factor_pixel("exposureFactor"), + m_bloom_intensity_pixel("bloomIntensity"), + m_bloom_radius_pixel("bloomRadius") { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); + g_settings->registerChangedCallback("exposure_factor", settingsCallback, this); + g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this); + g_settings->registerChangedCallback("bloom_radius", settingsCallback, this); m_fog_enabled = g_settings->getBool("enable_fog"); + m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f); + m_bloom_enabled = g_settings->getBool("enable_bloom"); + m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); + m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f); } ~GameGlobalShaderConstantSetter() @@ -526,12 +554,41 @@ public: m_camera_offset_pixel.set(camera_offset_array, services); m_camera_offset_vertex.set(camera_offset_array, services); - SamplerLayer_t base_tex = 0, - normal_tex = 1, - flags_tex = 2; - m_base_texture.set(&base_tex, services); - m_normal_texture.set(&normal_tex, services); - m_texture_flags.set(&flags_tex, services); + SamplerLayer_t tex_id; + tex_id = 0; + m_texture0.set(&tex_id, services); + tex_id = 1; + m_texture1.set(&tex_id, services); + tex_id = 2; + m_texture2.set(&tex_id, services); + tex_id = 3; + m_texture3.set(&tex_id, services); + + m_texel_size0.set(m_texel_size0_values.data(), services); + + float exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR * m_user_exposure_factor; + if (std::isnan(exposure_factor)) + exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR; + m_exposure_factor_pixel.set(&exposure_factor, services); + + if (m_bloom_enabled) { + m_bloom_intensity_pixel.set(&m_bloom_intensity, services); + m_bloom_radius_pixel.set(&m_bloom_radius, services); + } + } + + void onSetMaterial(const video::SMaterial &material) + { + video::ITexture *texture = material.getTexture(0); + if (texture) { + core::dimension2du size = texture->getSize(); + m_texel_size0_values[0] = 1.f / size.Width; + m_texel_size0_values[1] = 1.f / size.Height; + } + else { + m_texel_size0_values[0] = 0.f; + m_texel_size0_values[1] = 0.f; + } } }; diff --git a/src/client/render/pipeline.cpp b/src/client/render/pipeline.cpp index baf215d8e..eb38adbad 100644 --- a/src/client/render/pipeline.cpp +++ b/src/client/render/pipeline.cpp @@ -27,9 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., TextureBuffer::~TextureBuffer() { - if (m_render_target) - m_driver->removeRenderTarget(m_render_target); - m_render_target = nullptr; for (u32 index = 0; index < m_textures.size(); index++) m_driver->removeTexture(m_textures[index]); m_textures.clear(); @@ -37,8 +34,6 @@ TextureBuffer::~TextureBuffer() video::ITexture *TextureBuffer::getTexture(u8 index) { - if (index == m_depth_texture_index) - return m_depth_texture; if (index >= m_textures.size()) return nullptr; return m_textures[index]; @@ -52,9 +47,6 @@ void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::str if (m_definitions.size() <= index) m_definitions.resize(index + 1); - if (m_depth_texture_index == index) - m_depth_texture_index = NO_DEPTH_TEXTURE; - auto &definition = m_definitions[index]; definition.valid = true; definition.dirty = true; @@ -71,9 +63,6 @@ void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &na if (m_definitions.size() <= index) m_definitions.resize(index + 1); - if (m_depth_texture_index == index) - m_depth_texture_index = NO_DEPTH_TEXTURE; - auto &definition = m_definitions[index]; definition.valid = true; definition.dirty = true; @@ -83,20 +72,6 @@ void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &na definition.format = format; } -void TextureBuffer::setDepthTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format) -{ - assert(index != NO_DEPTH_TEXTURE); - setTexture(index, size, name, format); - m_depth_texture_index = index; -} - -void TextureBuffer::setDepthTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format) -{ - assert(index != NO_DEPTH_TEXTURE); - setTexture(index, scale_factor, name, format); - m_depth_texture_index = index; -} - void TextureBuffer::reset(PipelineContext &context) { if (!m_driver) @@ -116,41 +91,14 @@ void TextureBuffer::reset(PipelineContext &context) m_textures.push_back(nullptr); // change textures to match definitions - bool modified = false; for (u32 i = 0; i < m_definitions.size(); i++) { video::ITexture **ptr = &m_textures[i]; - if (i == m_depth_texture_index) { - if (*ptr) { - m_driver->removeTexture(*ptr); - *ptr = nullptr; - } - ptr = &m_depth_texture; - } - if (ensureTexture(ptr, m_definitions[i], context)) - modified = true; + ensureTexture(ptr, m_definitions[i], context); m_definitions[i].dirty = false; } - // make sude depth texture is removed and reset - if (m_depth_texture_index == NO_DEPTH_TEXTURE && m_depth_texture) { - m_driver->removeTexture(m_depth_texture); - m_depth_texture = nullptr; - } - - if (!m_render_target) - m_render_target = m_driver->addRenderTarget(); - - if (modified) - m_render_target->setTexture(m_textures, m_depth_texture); - - RenderTarget::reset(context); -} - -void TextureBuffer::activate(PipelineContext &context) -{ - m_driver->setRenderTargetEx(m_render_target, m_clear ? video::ECBF_DEPTH | video::ECBF_COLOR : 0, context.clear_color); - RenderTarget::activate(context); + RenderSource::reset(context); } bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefinition& definition, PipelineContext &context) @@ -186,15 +134,48 @@ bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefini } TextureBufferOutput::TextureBufferOutput(TextureBuffer *_buffer, u8 _texture_index) - : buffer(_buffer), texture_index(_texture_index) + : buffer(_buffer), texture_map({_texture_index}) {} +TextureBufferOutput::TextureBufferOutput(TextureBuffer *_buffer, const std::vector &_texture_map) + : buffer(_buffer), texture_map(_texture_map) +{} + +TextureBufferOutput::TextureBufferOutput(TextureBuffer *_buffer, const std::vector &_texture_map, u8 _depth_stencil) + : buffer(_buffer), texture_map(_texture_map), depth_stencil(_depth_stencil) +{} + +TextureBufferOutput::~TextureBufferOutput() +{ + if (render_target && driver) + driver->removeRenderTarget(render_target); +} + void TextureBufferOutput::activate(PipelineContext &context) { - auto texture = buffer->getTexture(texture_index); - auto driver = context.device->getVideoDriver(); - driver->setRenderTarget(texture, m_clear, m_clear, context.clear_color); - driver->OnResize(texture->getSize()); + if (!driver) + driver = context.device->getVideoDriver(); + + if (!render_target) + render_target = driver->addRenderTarget(); + + core::array textures; + core::dimension2du size(0, 0); + for (size_t i = 0; i < texture_map.size(); i++) { + video::ITexture *texture = buffer->getTexture(texture_map[i]); + textures.push_back(texture); + if (texture && size.Width == 0) + size = texture->getSize(); + } + + video::ITexture *depth_texture = nullptr; + if (depth_stencil != NO_DEPTH_TEXTURE) + depth_texture = buffer->getTexture(depth_stencil); + + render_target->setTexture(textures, depth_texture); + + driver->setRenderTargetEx(render_target, m_clear ? video::ECBF_ALL : video::ECBF_NONE, context.clear_color); + driver->OnResize(size); RenderTarget::activate(context); } diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index 4a6df7de2..771c6f1d5 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -112,7 +112,7 @@ protected: * * @note Use of TextureBuffer requires use of gl_FragData[] in the shader */ -class TextureBuffer : public RenderSource, public RenderTarget +class TextureBuffer : public RenderSource { public: virtual ~TextureBuffer() override; @@ -138,29 +138,8 @@ public: */ void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format); - /** - * @Configure depth texture and assign index - * - * @param index index to use for the depth texture - * @param size width and height of the texture in pixels - * @param name unique name for the texture - * @param format color format - */ - void setDepthTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format); - - /** - * @Configure depth texture and assign index - * - * @param index index to use for the depth texture - * @param scale_factor relation of the texture dimensions to the screen dimensions - * @param name unique name for the texture - * @param format color format - */ - void setDepthTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format); - virtual u8 getTextureCount() override { return m_textures.size(); } virtual video::ITexture *getTexture(u8 index) override; - virtual void activate(PipelineContext &context) override; virtual void reset(PipelineContext &context) override; private: static const u8 NO_DEPTH_TEXTURE = 255; @@ -189,9 +168,6 @@ private: video::IVideoDriver *m_driver { nullptr }; std::vector m_definitions; core::array m_textures; - video::ITexture *m_depth_texture { nullptr }; - u8 m_depth_texture_index { NO_DEPTH_TEXTURE }; - video::IRenderTarget *m_render_target { nullptr }; }; /** @@ -201,10 +177,18 @@ class TextureBufferOutput : public RenderTarget { public: TextureBufferOutput(TextureBuffer *buffer, u8 texture_index); + TextureBufferOutput(TextureBuffer *buffer, const std::vector &texture_map); + TextureBufferOutput(TextureBuffer *buffer, const std::vector &texture_map, u8 depth_stencil); + virtual ~TextureBufferOutput() override; void activate(PipelineContext &context) override; private: + static const u8 NO_DEPTH_TEXTURE = 255; + TextureBuffer *buffer; - u8 texture_index; + std::vector texture_map; + u8 depth_stencil { NO_DEPTH_TEXTURE }; + video::IRenderTarget* render_target { nullptr }; + video::IVideoDriver* driver { nullptr }; }; /** diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index 8f6f59537..860b277bd 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -88,31 +88,77 @@ void PostProcessingStep::run(PipelineContext &context) driver->drawVertexPrimitiveList(&vertices, 4, &indices, 2); } +void PostProcessingStep::setBilinearFilter(u8 index, bool value) +{ + assert(index < video::MATERIAL_MAX_TEXTURES); + material.TextureLayer[index].BilinearFilter = value; +} + RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep, v2f scale, Client *client) { auto buffer = pipeline->createOwned(); - static const u8 TEXTURE_COLOR = 0; - static const u8 TEXTURE_DEPTH = 3; + auto driver = client->getSceneManager()->getVideoDriver(); - // init post-processing buffer - buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", video::ECF_A8R8G8B8); + // configure texture formats + video::ECOLOR_FORMAT color_format = video::ECF_A8R8G8B8; + if (driver->queryTextureFormat(video::ECF_A16B16G16R16F)) + color_format = video::ECF_A16B16G16R16F; video::ECOLOR_FORMAT depth_format = video::ECF_D16; // fallback depth format - auto driver = client->getSceneManager()->getVideoDriver(); if (driver->queryTextureFormat(video::ECF_D32)) depth_format = video::ECF_D32; else if (driver->queryTextureFormat(video::ECF_D24S8)) depth_format = video::ECF_D24S8; - buffer->setDepthTexture(TEXTURE_DEPTH, scale, "3d_depthmap", depth_format); + + + // init post-processing buffer + static const u8 TEXTURE_COLOR = 0; + static const u8 TEXTURE_DEPTH = 1; + static const u8 TEXTURE_BLOOM = 2; + static const u8 TEXTURE_BLUR = 3; + static const u8 TEXTURE_BLUR_SECONDARY = 4; + + buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", color_format); + buffer->setTexture(TEXTURE_DEPTH, scale, "3d_depthmap", depth_format); // attach buffer to the previous step - previousStep->setRenderTarget(buffer); + previousStep->setRenderTarget(pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH)); // post-processing stage - // set up shader - u32 shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); + // set up bloom + if (g_settings->getBool("enable_bloom")) { - RenderStep *effect = pipeline->addStep(shader_id, std::vector { TEXTURE_COLOR }); + buffer->setTexture(TEXTURE_BLUR, scale * 0.5, "blur", color_format); + buffer->setTexture(TEXTURE_BLOOM, scale * 0.5, "bloom", color_format); + u8 bloom_input_texture = TEXTURE_BLOOM; + + if (g_settings->getBool("enable_bloom_dedicated_texture")) { + buffer->setTexture(TEXTURE_BLUR_SECONDARY, scale * 0.5, "blur2", color_format); + bloom_input_texture = TEXTURE_BLUR_SECONDARY; + } + + // get bright spots + u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH); + RenderStep *extract_bloom = pipeline->addStep(shader_id, std::vector { TEXTURE_COLOR }); + extract_bloom->setRenderSource(buffer); + extract_bloom->setRenderTarget(pipeline->createOwned(buffer, bloom_input_texture)); + // horizontal blur + shader_id = client->getShaderSource()->getShader("blur_h", TILE_MATERIAL_PLAIN, NDT_MESH); + RenderStep *blur_h = pipeline->addStep(shader_id, std::vector { bloom_input_texture }); + blur_h->setRenderSource(buffer); + blur_h->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLUR)); + // vertical blur + shader_id = client->getShaderSource()->getShader("blur_v", TILE_MATERIAL_PLAIN, NDT_MESH); + RenderStep *blur_v = pipeline->addStep(shader_id, std::vector { TEXTURE_BLUR }); + blur_v->setRenderSource(buffer); + blur_v->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLOOM)); + } + + // final post-processing + u32 shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); + PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { TEXTURE_COLOR, TEXTURE_BLOOM }); + pipeline->addStep(effect); + effect->setBilinearFilter(1, true); // apply filter to the bloom effect->setRenderSource(buffer); return effect; } diff --git a/src/client/render/secondstage.h b/src/client/render/secondstage.h index 0c3c0a447..37d5b5071 100644 --- a/src/client/render/secondstage.h +++ b/src/client/render/secondstage.h @@ -22,17 +22,33 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "stereo.h" #include "pipeline.h" +/** + * Step to apply post-processing filter to the rendered image + */ class PostProcessingStep : public RenderStep { public: + /** + * Construct a new PostProcessingStep object + * + * @param shader_id ID of the shader in IShaderSource + * @param texture_map Map of textures to be chosen from the render source + */ PostProcessingStep(u32 shader_id, const std::vector &texture_map); - + void setRenderSource(RenderSource *source) override; void setRenderTarget(RenderTarget *target) override; void reset(PipelineContext &context) override; void run(PipelineContext &context) override; + /** + * Configure bilinear filtering for a specific texture layer + * + * @param index Index of the texture layer + * @param value true to enable the bilinear filter, false to disable + */ + void setBilinearFilter(u8 index, bool value); private: u32 shader_id; std::vector texture_map; diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 38420010f..e26171a44 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -46,6 +46,10 @@ class RenderingCore; class RenderingEngine { public: + /// Default color factor before applying effects like bloom or tomemapping + /// this is derived from tonemapping code and tuned empirically + static constexpr float DEFAULT_EXPOSURE_FACTOR = 2.5f; + RenderingEngine(IEventReceiver *eventReceiver); ~RenderingEngine(); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index f46807864..ab9b23117 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -682,6 +682,13 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, )"; } + // map legacy semantic texture names to texture identifiers + fragment_header += R"( + #define baseTexture texture0 + #define normalTexture texture1 + #define textureFlags texture2 + )"; + // Since this is the first time we're using the GL bindings be extra careful. // This should be removed before 5.6.0 or similar. if (!GL.GetString) { @@ -771,6 +778,12 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; } + if (g_settings->getBool("enable_bloom")) { + shaders_header << "#define ENABLE_BLOOM 1\n"; + if (g_settings->getBool("enable_bloom_debug")) + shaders_header << "#define ENABLE_BLOOM_DEBUG 1\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 bc1c98988..804bef2ca 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -263,6 +263,12 @@ void set_default_settings() settings->setDefault("water_wave_speed", "5.0"); settings->setDefault("enable_waving_leaves", "false"); settings->setDefault("enable_waving_plants", "false"); + settings->setDefault("exposure_factor", "1.0"); + settings->setDefault("enable_bloom", "false"); + settings->setDefault("enable_bloom_debug", "false"); + settings->setDefault("enable_bloom_dedicated_texture", "false"); + settings->setDefault("bloom_intensity", "0.05"); + settings->setDefault("bloom_radius", "16"); // Effects Shadows settings->setDefault("enable_dynamic_shadows", "false");