Adds configurable light exposure control and bloom effect (light bleeding) with client-side settings.
# 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.
--- /dev/null
+#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.
+}
--- /dev/null
+#ifdef GL_ES
+varying mediump vec2 varTexCoord;
+#else
+centroid varying vec2 varTexCoord;
+#endif
+
+void main(void)
+{
+ varTexCoord.st = inTexCoord0.st;
+ gl_Position = inVertexPosition;
+}
--- /dev/null
+#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.
+}
--- /dev/null
+#ifdef GL_ES
+varying mediump vec2 varTexCoord;
+#else
+centroid varying vec2 varTexCoord;
+#endif
+
+void main(void)
+{
+ varTexCoord.st = inTexCoord0.st;
+ gl_Position = inVertexPosition;
+}
--- /dev/null
+#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.
+}
--- /dev/null
+#ifdef GL_ES
+varying mediump vec2 varTexCoord;
+#else
+centroid varying vec2 varTexCoord;
+#endif
+
+void main(void)
+{
+ varTexCoord.st = inTexCoord0.st;
+ gl_Position = inVertexPosition;
+}
-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;
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
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
vec2 uv = varTexCoord.st;
vec4 color = texture2D(rendered, uv).rgba;
+ // 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);
+ 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.
}
class GameGlobalShaderConstantSetter : public IShaderConstantSetter
{
Sky *m_sky;
+ Client *m_client;
bool *m_force_fog_off;
f32 *m_fog_range;
bool m_fog_enabled;
CachedPixelShaderSetting<float, 3> m_minimap_yaw;
CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
- CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
- CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
- CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
- Client *m_client;
+ CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
+ CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
+ CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
+ CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
+ CachedPixelShaderSetting<float, 2> m_texel_size0;
+ std::array<float, 2> m_texel_size0_values;
+ CachedPixelShaderSetting<float> m_exposure_factor_pixel;
+ float m_user_exposure_factor;
+ bool m_bloom_enabled;
+ CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
+ float m_bloom_intensity;
+ CachedPixelShaderSetting<float> 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)
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"),
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()
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;
+ }
}
};
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();
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];
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;
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;
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)
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)
}
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<u8> &_texture_map)
+ : buffer(_buffer), texture_map(_texture_map)
+{}
+
+TextureBufferOutput::TextureBufferOutput(TextureBuffer *_buffer, const std::vector<u8> &_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<video::ITexture *> 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);
}
*
* @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;
*/
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;
video::IVideoDriver *m_driver { nullptr };
std::vector<TextureDefinition> m_definitions;
core::array<video::ITexture *> m_textures;
- video::ITexture *m_depth_texture { nullptr };
- u8 m_depth_texture_index { NO_DEPTH_TEXTURE };
- video::IRenderTarget *m_render_target { nullptr };
};
/**
{
public:
TextureBufferOutput(TextureBuffer *buffer, u8 texture_index);
+ TextureBufferOutput(TextureBuffer *buffer, const std::vector<u8> &texture_map);
+ TextureBufferOutput(TextureBuffer *buffer, const std::vector<u8> &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<u8> texture_map;
+ u8 depth_stencil { NO_DEPTH_TEXTURE };
+ video::IRenderTarget* render_target { nullptr };
+ video::IVideoDriver* driver { nullptr };
};
/**
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<TextureBuffer>();
- 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<TextureBufferOutput>(buffer, std::vector<u8> { 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")) {
+
+ 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<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR });
+ extract_bloom->setRenderSource(buffer);
+ extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, bloom_input_texture));
+ // horizontal blur
+ shader_id = client->getShaderSource()->getShader("blur_h", TILE_MATERIAL_PLAIN, NDT_MESH);
+ RenderStep *blur_h = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { bloom_input_texture });
+ blur_h->setRenderSource(buffer);
+ blur_h->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLUR));
+ // vertical blur
+ shader_id = client->getShaderSource()->getShader("blur_v", TILE_MATERIAL_PLAIN, NDT_MESH);
+ RenderStep *blur_v = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_BLUR });
+ blur_v->setRenderSource(buffer);
+ blur_v->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM));
+ }
- RenderStep *effect = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR });
+ // final post-processing
+ u32 shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH);
+ PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR, TEXTURE_BLOOM });
+ pipeline->addStep(effect);
+ effect->setBilinearFilter(1, true); // apply filter to the bloom
effect->setRenderSource(buffer);
return effect;
}
#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<u8> &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<u8> texture_map;
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();
)";
}
+ // 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) {
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();
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");