From 6d45c243f85942b20dab58753e735ec89a68f710 Mon Sep 17 00:00:00 2001 From: x2048 Date: Fri, 6 Jan 2023 22:33:25 +0100 Subject: Add dynamic exposure correction (#12959) * Add uniform for frame delta time * Adjust exposure in logarithmic (EV) space * Add network support and LUA API * Add testing mod --- src/client/clientenvironment.cpp | 11 +++++--- src/client/clientenvironment.h | 2 ++ src/client/game.cpp | 41 +++++++++++++++++++++-------- src/client/render/pipeline.cpp | 20 ++++++++++++++ src/client/render/pipeline.h | 44 +++++++++++++++++++++---------- src/client/render/secondstage.cpp | 55 ++++++++++++++++++++++++++++++--------- src/client/renderingengine.cpp | 2 +- src/client/shader.cpp | 3 +++ src/client/shader.h | 37 ++++++++++++++++++++++++++ 9 files changed, 173 insertions(+), 42 deletions(-) (limited to 'src/client') diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 0070fa82f..d9b88eb4a 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -531,8 +531,13 @@ void ClientEnvironment::updateFrameTime(bool is_paused) { // if paused, m_frame_time_pause_accumulator increases by dtime, // otherwise, m_frame_time increases by dtime - if (is_paused) + if (is_paused) { + m_frame_dtime = 0; m_frame_time_pause_accumulator = porting::getTimeMs() - m_frame_time; - else - m_frame_time = porting::getTimeMs() - m_frame_time_pause_accumulator; + } + else { + auto new_frame_time = porting::getTimeMs() - m_frame_time_pause_accumulator; + m_frame_dtime = new_frame_time - MYMAX(m_frame_time, m_frame_time_pause_accumulator); + m_frame_time = new_frame_time; + } } diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h index 87820fbe8..f5d46deb5 100644 --- a/src/client/clientenvironment.h +++ b/src/client/clientenvironment.h @@ -144,6 +144,7 @@ public: void updateFrameTime(bool is_paused); u64 getFrameTime() const { return m_frame_time; } + u64 getFrameTimeDelta() const { return m_frame_dtime; } private: ClientMap *m_map; @@ -158,5 +159,6 @@ private: std::list m_player_names; v3s16 m_camera_offset; u64 m_frame_time = 0; + u64 m_frame_dtime = 0; u64 m_frame_time_pause_accumulator = 0; }; diff --git a/src/client/game.cpp b/src/client/game.cpp index 3f76d2e05..cf0117046 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -414,6 +414,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_fog_distance; CachedVertexShaderSetting m_animation_timer_vertex; CachedPixelShaderSetting m_animation_timer_pixel; + CachedVertexShaderSetting m_animation_timer_delta_vertex; + CachedPixelShaderSetting m_animation_timer_delta_pixel; CachedPixelShaderSetting m_day_light; CachedPixelShaderSetting m_star_color; CachedPixelShaderSetting m_eye_position_pixel; @@ -427,8 +429,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_texture3; CachedPixelShaderSetting m_texel_size0; std::array m_texel_size0_values; - CachedPixelShaderSetting m_exposure_factor_pixel; - float m_user_exposure_factor; + CachedStructPixelShaderSetting m_exposure_params_pixel; + float m_user_exposure_compensation; bool m_bloom_enabled; CachedPixelShaderSetting m_bloom_intensity_pixel; float m_bloom_intensity; @@ -443,8 +445,8 @@ public: { 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 == "exposure_compensation") + m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); if (name == "bloom_intensity") m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); if (name == "bloom_strength_factor") @@ -470,6 +472,8 @@ public: m_fog_distance("fogDistance"), m_animation_timer_vertex("animationTimer"), m_animation_timer_pixel("animationTimer"), + m_animation_timer_delta_vertex("animationTimerDelta"), + m_animation_timer_delta_pixel("animationTimerDelta"), m_day_light("dayLight"), m_star_color("starColor"), m_eye_position_pixel("eyePosition"), @@ -482,20 +486,24 @@ public: m_texture2("texture2"), m_texture3("texture3"), m_texel_size0("texelSize0"), - m_exposure_factor_pixel("exposureFactor"), + m_exposure_params_pixel("exposureParams", + std::array { + "luminanceMin", "luminanceMax", "exposureCorrection", + "speedDarkBright", "speedBrightDark", "centerWeightPower", "compensationFactor" + }), m_bloom_intensity_pixel("bloomIntensity"), m_bloom_strength_pixel("bloomStrength"), m_bloom_radius_pixel("bloomRadius"), m_saturation_pixel("saturation") { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); - g_settings->registerChangedCallback("exposure_factor", settingsCallback, this); + g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this); g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this); g_settings->registerChangedCallback("bloom_strength_factor", settingsCallback, this); g_settings->registerChangedCallback("bloom_radius", settingsCallback, this); g_settings->registerChangedCallback("saturation", settingsCallback, this); m_fog_enabled = g_settings->getBool("enable_fog"); - m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f); + m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); m_bloom_enabled = g_settings->getBool("enable_bloom"); m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f); @@ -546,6 +554,10 @@ public: m_animation_timer_vertex.set(&animation_timer_f, services); m_animation_timer_pixel.set(&animation_timer_f, services); + float animation_timer_delta_f = (float)m_client->getEnv().getFrameTimeDelta() / 100000.f; + m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services); + m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services); + float eye_position_array[3]; v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); epos.getAs3Values(eye_position_array); @@ -577,10 +589,17 @@ public: m_texel_size0.set(m_texel_size0_values.data(), services); - float exposure_factor = m_user_exposure_factor; - if (std::isnan(exposure_factor)) - exposure_factor = 1.0f; - m_exposure_factor_pixel.set(&exposure_factor, services); + const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure; + std::array exposure_buffer = { + std::pow(2.0f, exposure_params.luminance_min), + std::pow(2.0f, exposure_params.luminance_max), + exposure_params.exposure_correction, + exposure_params.speed_dark_bright, + exposure_params.speed_bright_dark, + exposure_params.center_weight_power, + powf(2.f, m_user_exposure_compensation) + }; + m_exposure_params_pixel.set(exposure_buffer.data(), services); if (m_bloom_enabled) { m_bloom_intensity_pixel.set(&m_bloom_intensity, services); diff --git a/src/client/render/pipeline.cpp b/src/client/render/pipeline.cpp index 13898f8a4..cc275a7ef 100644 --- a/src/client/render/pipeline.cpp +++ b/src/client/render/pipeline.cpp @@ -101,6 +101,16 @@ void TextureBuffer::reset(PipelineContext &context) RenderSource::reset(context); } +void TextureBuffer::swapTextures(u8 texture_a, u8 texture_b) +{ + assert(m_definitions[texture_a].valid && m_definitions[texture_b].valid); + + video::ITexture *temp = m_textures[texture_a]; + m_textures[texture_a] = m_textures[texture_b]; + m_textures[texture_b] = temp; +} + + bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefinition& definition, PipelineContext &context) { bool modify; @@ -230,6 +240,16 @@ void SetRenderTargetStep::run(PipelineContext &context) step->setRenderTarget(target); } +SwapTexturesStep::SwapTexturesStep(TextureBuffer *_buffer, u8 _texture_a, u8 _texture_b) + : buffer(_buffer), texture_a(_texture_a), texture_b(_texture_b) +{ +} + +void SwapTexturesStep::run(PipelineContext &context) +{ + buffer->swapTextures(texture_a, texture_b); +} + RenderSource *RenderPipeline::getInput() { return &m_input; diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index 354624102..bfdef2931 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -53,7 +53,7 @@ struct PipelineContext /** * Base object that can be owned by RenderPipeline - * + * */ class RenderPipelineObject { @@ -74,7 +74,7 @@ public: virtual u8 getTextureCount() = 0; /** - * Get a texture by index. + * Get a texture by index. * Returns nullptr is the texture does not exist. */ virtual video::ITexture *getTexture(u8 index) = 0; @@ -119,7 +119,7 @@ public: /** * Configure fixed-size texture for the specific index - * + * * @param index index of the texture * @param size width and height of the texture in pixels * @param height height of the texture in pixels @@ -130,7 +130,7 @@ public: /** * Configure relative-size texture for the specific index - * + * * @param index index of the texture * @param scale_factor relation of the texture dimensions to the screen dimensions * @param name unique name of the texture @@ -141,6 +141,7 @@ public: virtual u8 getTextureCount() override { return m_textures.size(); } virtual video::ITexture *getTexture(u8 index) override; virtual void reset(PipelineContext &context) override; + void swapTextures(u8 texture_a, u8 texture_b); private: static const u8 NO_DEPTH_TEXTURE = 255; @@ -193,7 +194,7 @@ private: /** * Allows remapping texture indicies in another RenderSource. - * + * * @note all unmapped indexes are passed through to the underlying render source. */ class RemappingSource : RenderSource @@ -205,7 +206,7 @@ public: /** * Maps texture index to a different index in the dependent source. - * + * * @param index texture index as requested by the @see RenderStep. * @param target_index matching texture index in the underlying @see RenderSource. */ @@ -250,7 +251,7 @@ public: virtual u8 getTextureCount() override; /** - * Get a texture by index. + * Get a texture by index. * Returns nullptr is the texture does not exist. */ virtual video::ITexture *getTexture(u8 index) override; @@ -288,14 +289,14 @@ class RenderStep : virtual public RenderPipelineObject public: /** * Assigns render source to this step. - * + * * @param source source of rendering information */ virtual void setRenderSource(RenderSource *source) = 0; /** * Assigned render target to this step. - * + * * @param target render target to send output to. */ virtual void setRenderTarget(RenderTarget *target) = 0; @@ -319,7 +320,7 @@ public: /** * Dynamically changes render target of another step. - * + * * This allows re-running parts of the pipeline with different outputs */ class SetRenderTargetStep : public TrivialRenderStep @@ -332,9 +333,24 @@ private: RenderTarget *target; }; +/** + * Swaps two textures in the texture buffer. + * + */ +class SwapTexturesStep : public TrivialRenderStep +{ +public: + SwapTexturesStep(TextureBuffer *buffer, u8 texture_a, u8 texture_b); + virtual void run(PipelineContext &context) override; +private: + TextureBuffer *buffer; + u8 texture_a; + u8 texture_b; +}; + /** * Render Pipeline provides a flexible way to execute rendering steps in the engine. - * + * * RenderPipeline also implements @see RenderStep, allowing for nesting of the pipelines. */ class RenderPipeline : public RenderStep @@ -342,7 +358,7 @@ class RenderPipeline : public RenderStep public: /** * Add a step to the end of the pipeline - * + * * @param step reference to a @see RenderStep implementation. */ RenderStep *addStep(RenderStep *step) @@ -353,9 +369,9 @@ public: /** * Capture ownership of a dynamically created @see RenderStep instance. - * + * * RenderPipeline will delete the instance when the pipeline is destroyed. - * + * * @param step reference to the instance. * @return RenderStep* value of the 'step' parameter. */ diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index ebc7e7411..395a0fe6b 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -115,10 +115,14 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep static const u8 TEXTURE_COLOR = 0; static const u8 TEXTURE_DEPTH = 1; static const u8 TEXTURE_BLOOM = 2; + static const u8 TEXTURE_EXPOSURE_1 = 3; + static const u8 TEXTURE_EXPOSURE_2 = 4; static const u8 TEXTURE_BLOOM_DOWN = 10; static const u8 TEXTURE_BLOOM_UP = 20; buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", color_format); + buffer->setTexture(TEXTURE_EXPOSURE_1, core::dimension2du(1,1), "exposure_1", color_format); + buffer->setTexture(TEXTURE_EXPOSURE_2, core::dimension2du(1,1), "exposure_2", color_format); buffer->setTexture(TEXTURE_DEPTH, scale, "3d_depthmap", depth_format); // attach buffer to the previous step @@ -127,30 +131,40 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // shared variables u32 shader_id; + // 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 - // set up bloom - if (g_settings->getBool("enable_bloom")) { + u8 source = TEXTURE_COLOR; - buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", color_format); + // common downsampling step for bloom or autoexposure + if (enable_bloom || enable_auto_exposure) { - const u8 MIPMAP_LEVELS = 4; v2f downscale = scale * 0.5; for (u8 i = 0; i < MIPMAP_LEVELS; i++) { - buffer->setTexture(TEXTURE_BLOOM_DOWN + i, downscale, std::string("bloom_down") + std::to_string(i), color_format); - buffer->setTexture(TEXTURE_BLOOM_UP + i, downscale, std::string("bloom_up") + std::to_string(i), color_format); + buffer->setTexture(TEXTURE_BLOOM_DOWN + i, downscale, std::string("downsample") + std::to_string(i), color_format); + if (enable_bloom) + buffer->setTexture(TEXTURE_BLOOM_UP + i, downscale, std::string("upsample") + std::to_string(i), color_format); downscale *= 0.5; } - // 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, TEXTURE_BLOOM)); + if (enable_bloom) { + buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", color_format); + + // 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, TEXTURE_EXPOSURE_1 }); + extract_bloom->setRenderSource(buffer); + extract_bloom->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLOOM)); + source = TEXTURE_BLOOM; + } // downsample shader_id = client->getShaderSource()->getShader("bloom_downsample", TILE_MATERIAL_PLAIN, NDT_MESH); - u8 source = TEXTURE_BLOOM; for (u8 i = 0; i < MIPMAP_LEVELS; i++) { auto step = pipeline->addStep(shader_id, std::vector { source }); step->setRenderSource(buffer); @@ -158,7 +172,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep step->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLOOM_DOWN + i)); source = TEXTURE_BLOOM_DOWN + i; } + } + if (enable_bloom) { // upsample shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH); for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) { @@ -171,11 +187,24 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep } } + 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) }); + update_exposure->setBilinearFilter(1, true); + update_exposure->setRenderSource(buffer); + update_exposure->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_EXPOSURE_2)); + } + // final post-processing 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 }); + PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { TEXTURE_COLOR, TEXTURE_BLOOM_UP, TEXTURE_EXPOSURE_2 }); pipeline->addStep(effect); effect->setBilinearFilter(1, true); // apply filter to the bloom effect->setRenderSource(buffer); + + if (enable_auto_exposure) { + pipeline->addStep(buffer, TEXTURE_EXPOSURE_1, TEXTURE_EXPOSURE_2); + } + return effect; } diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index ec7a05338..a58b0efe6 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -56,7 +56,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif RenderingEngine *RenderingEngine::s_singleton = nullptr; -const float RenderingEngine::BASE_BLOOM_STRENGTH = 8.0f; +const float RenderingEngine::BASE_BLOOM_STRENGTH = 1.0f; static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment, diff --git a/src/client/shader.cpp b/src/client/shader.cpp index da3da8ab1..ccecb22c3 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -784,6 +784,9 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, shaders_header << "#define ENABLE_BLOOM_DEBUG 1\n"; } + if (g_settings->getBool("enable_auto_exposure")) + shaders_header << "#define ENABLE_AUTO_EXPOSURE 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/client/shader.h b/src/client/shader.h index 8f1ba1e41..33c3e45e3 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -121,6 +121,43 @@ public: CachedShaderSetting(name, false){} }; +template +class CachedStructShaderSetting { + const char *m_name; + T m_sent[count]; + bool has_been_set = false; + std::array m_fields; +public: + CachedStructShaderSetting(const char *name, std::array &&fields) : + m_name(name), m_fields(std::move(fields)) + {} + + void set(const T value[count], video::IMaterialRendererServices *services) + { + if (cache && has_been_set && std::equal(m_sent, m_sent + count, value)) + return; + + for (std::size_t i = 0; i < count; i++) { + std::string uniform_name = std::string(m_name) + "." + m_fields[i]; + + if (is_pixel) + services->setPixelShaderConstant(services->getPixelShaderConstantID(uniform_name.c_str()), value + i, 1); + else + services->setVertexShaderConstant(services->getVertexShaderConstantID(uniform_name.c_str()), value + i, 1); + } + + if (cache) { + std::copy(value, value + count, m_sent); + has_been_set = true; + } + } +}; + +template +using CachedStructVertexShaderSetting = CachedStructShaderSetting; + +template +using CachedStructPixelShaderSetting = CachedStructShaderSetting; /* ShaderSource creates and caches shaders. -- cgit v1.2.3