]> git.lizzy.rs Git - minetest.git/commitdiff
Add dynamic exposure correction (#12959)
authorx2048 <codeforsmile@gmail.com>
Fri, 6 Jan 2023 21:33:25 +0000 (22:33 +0100)
committerGitHub <noreply@github.com>
Fri, 6 Jan 2023 21:33:25 +0000 (22:33 +0100)
* Add uniform for frame delta time
* Adjust exposure in logarithmic (EV) space
* Add network support and LUA API
* Add testing mod

29 files changed:
android/native/jni/Android.mk
builtin/settingtypes.txt
client/shaders/extract_bloom/opengl_fragment.glsl
client/shaders/extract_bloom/opengl_vertex.glsl
client/shaders/second_stage/opengl_fragment.glsl
client/shaders/second_stage/opengl_vertex.glsl
client/shaders/update_exposure/opengl_fragment.glsl [new file with mode: 0644]
client/shaders/update_exposure/opengl_vertex.glsl [new file with mode: 0644]
doc/lua_api.txt
games/devtest/mods/lighting/init.lua [new file with mode: 0644]
games/devtest/mods/lighting/mod.conf [new file with mode: 0644]
games/devtest/mods/util_commands/init.lua
src/CMakeLists.txt
src/client/clientenvironment.cpp
src/client/clientenvironment.h
src/client/game.cpp
src/client/render/pipeline.cpp
src/client/render/pipeline.h
src/client/render/secondstage.cpp
src/client/renderingengine.cpp
src/client/shader.cpp
src/client/shader.h
src/defaultsettings.cpp
src/lighting.cpp [new file with mode: 0644]
src/lighting.h
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/script/lua_api/l_object.cpp
src/server.cpp

index cd9326d4ad038fdfb805a986ad44dad6ac0fb4e2..b2a8b3d6a00935292b4521f0593fdea3826f693d 100644 (file)
@@ -194,6 +194,7 @@ LOCAL_SRC_FILES := \
        ../../src/itemdef.cpp                        \
        ../../src/itemstackmetadata.cpp              \
        ../../src/light.cpp                          \
+       ../../src/lighting.cpp                       \
        ../../src/log.cpp                            \
        ../../src/main.cpp                           \
        ../../src/map.cpp                            \
index d3a8171264b694fe694581c81ae2ec62e574062a..25f75fbbea79f7fd7dd830de218735d11aff35b2 100644 (file)
@@ -450,12 +450,17 @@ shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 -60.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
+#    Set the exposure compensation in EV units.
+#    Value of 0.0 (default) means no exposure compensation.
+#    Range: from -1 to 1.0
+exposure_compensation  (Exposure compensation) float 0.0 -1.0 1.0
+
+#    Enable automatic exposure correction
+#    When enabled, the post-processing engine will
+#    automatically adjust to the brightness of the scene,
+#    simulating the behavior of human eye.
+enable_auto_exposure (Enable Automatic Exposure) bool false
+
 
 [**Bloom]
 
index af320f9abf4685ee392328088b86cf57291837aa..45f5e9c6fdaf21d10bf265f4b24e7c8ef71cd952 100644 (file)
@@ -1,8 +1,12 @@
 #define rendered texture0
 
+struct ExposureParams {
+       float compensationFactor;
+};
+
 uniform sampler2D rendered;
-uniform mediump float exposureFactor;
 uniform mediump float bloomStrength;
+uniform ExposureParams exposureParams;
 
 #ifdef GL_ES
 varying mediump vec2 varTexCoord;
@@ -10,6 +14,7 @@ varying mediump vec2 varTexCoord;
 centroid varying vec2 varTexCoord;
 #endif
 
+varying float exposure;
 
 void main(void)
 {
@@ -18,10 +23,6 @@ void main(void)
        // translate to linear colorspace (approximate)
        color = pow(color, vec3(2.2));
 
-       // Scale colors by luminance to amplify bright colors
-       // in SDR textures.
-       float luminance = dot(color, vec3(0.213, 0.515, 0.072));
-       luminance *= luminance;
-       color *= luminance * exposureFactor * bloomStrength;
+       color *= pow(2., exposure) * exposureParams.compensationFactor * bloomStrength;
        gl_FragColor = vec4(color, 1.0); // force full alpha to avoid holes in the image.
 }
index 12692c29643fc0ad85791e7de6947ec5bc02b309..479ae10790d40e7337ddb4d8b7c8e3d1837d6039 100644 (file)
@@ -1,11 +1,19 @@
+#define exposureMap texture1
+
+uniform sampler2D exposureMap;
+
 #ifdef GL_ES
 varying mediump vec2 varTexCoord;
 #else
 centroid varying vec2 varTexCoord;
 #endif
 
+varying float exposure;
+
 void main(void)
 {
+       exposure = texture2D(exposureMap, vec2(0.5)).r;
+
        varTexCoord.st = inTexCoord0.st;
        gl_Position = inVertexPosition;
 }
index 09b49ef0edd53c249a9b3cac825d1ae6a231aed2..2ff58aa42ad36db54f402da229a16a95f75aede3 100644 (file)
@@ -1,9 +1,14 @@
 #define rendered texture0
 #define bloom texture1
 
+struct ExposureParams {
+       float compensationFactor;
+};
+
 uniform sampler2D rendered;
 uniform sampler2D bloom;
-uniform mediump float exposureFactor;
+
+uniform ExposureParams exposureParams;
 uniform lowp float bloomIntensity;
 uniform lowp float saturation;
 
@@ -13,6 +18,8 @@ varying mediump vec2 varTexCoord;
 centroid varying vec2 varTexCoord;
 #endif
 
+varying float exposure;
+
 #ifdef ENABLE_BLOOM
 
 vec4 applyBloom(vec4 color, vec2 uv)
@@ -80,7 +87,7 @@ void main(void)
        if (uv.x > 0.5 || uv.y > 0.5)
 #endif
        {
-               color.rgb *= exposureFactor;
+               color.rgb *= exposure * exposureParams.compensationFactor;
        }
 
 
index 12692c29643fc0ad85791e7de6947ec5bc02b309..7c121f6d123b034a43e4b7d96b3a2fccc828bcdd 100644 (file)
@@ -1,11 +1,24 @@
+#define exposureMap texture2
+
+uniform sampler2D exposureMap;
+
 #ifdef GL_ES
 varying mediump vec2 varTexCoord;
 #else
 centroid varying vec2 varTexCoord;
 #endif
 
+varying float exposure;
+
 void main(void)
 {
+#ifdef ENABLE_AUTO_EXPOSURE
+       exposure = texture2D(exposureMap, vec2(0.5)).r;
+       exposure = pow(2., exposure);
+#else
+       exposure = 1.0;
+#endif
+
        varTexCoord.st = inTexCoord0.st;
        gl_Position = inVertexPosition;
 }
diff --git a/client/shaders/update_exposure/opengl_fragment.glsl b/client/shaders/update_exposure/opengl_fragment.glsl
new file mode 100644 (file)
index 0000000..dfed8f0
--- /dev/null
@@ -0,0 +1,75 @@
+#define exposure texture0
+#define screen texture1
+
+struct ExposureParams {
+       float luminanceMin;
+       float luminanceMax;
+       float exposureCorrection;
+       float luminanceKey;
+       float speedDarkBright;
+       float speedBrightDark;
+       float centerWeightPower;
+       float compensationFactor;
+};
+
+uniform sampler2D exposure;
+uniform sampler2D screen;
+
+#ifdef ENABLE_BLOOM
+uniform float bloomStrength;
+#else
+const float bloomStrength = 1.0;
+#endif
+uniform ExposureParams exposureParams;
+uniform float animationTimerDelta;
+
+
+const vec3 luminanceFactors = vec3(0.213, 0.715, 0.072);
+
+float getLuminance(vec3 color)
+{
+       return dot(color, luminanceFactors);
+}
+
+void main(void)
+{
+       float previousExposure = texture2D(exposure, vec2(0.5, 0.5)).r;
+
+       vec3 averageColor = vec3(0.);
+       float n = 0.;
+
+       // Scan the screen with center-weighting and sample average color
+       for (float _x = 0.1; _x < 0.9; _x += 0.17) {
+               float x = pow(_x, exposureParams.centerWeightPower);
+               for (float _y = 0.1; _y < 0.9; _y += 0.17) {
+                       float y = pow(_y, exposureParams.centerWeightPower);
+                       averageColor += texture2D(screen, vec2(0.5 + 0.5 * x, 0.5 + 0.5 * y)).rgb;
+                       averageColor += texture2D(screen, vec2(0.5 + 0.5 * x, 0.5 - 0.5 * y)).rgb;
+                       averageColor += texture2D(screen, vec2(0.5 - 0.5 * x, 0.5 + 0.5 * y)).rgb;
+                       averageColor += texture2D(screen, vec2(0.5 - 0.5 * x, 0.5 - 0.5 * y)).rgb;
+                       n += 4.;
+               }
+       }
+
+       float luminance = getLuminance(averageColor);
+       luminance /= n;
+
+       luminance /= pow(2., previousExposure) * bloomStrength * exposureParams.compensationFactor; // compensate for the configurable factors
+
+       luminance = clamp(luminance, exposureParams.luminanceMin, exposureParams.luminanceMax);
+
+       // From https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/course-notes-moving-frostbite-to-pbr-v2.pdf
+       // 1. EV100 = log2(luminance * S / K) where S = 100, K = 0.125 = log2(luminance) + 3
+       // 2. Lmax = 1.2 * 2 ^ (EV100 - EC)
+       //    => Lmax = 1.2 * 2^3 * luminance / 2^EC = 9.6 * luminance / 2^EC
+       // 3. exposure = 1 / Lmax
+       //    => exposure = 2^EC / (9.6 * luminance)
+       float wantedExposure = exposureParams.exposureCorrection - log(luminance)/0.693147180559945 - 3.263034405833794;
+
+       if (wantedExposure < previousExposure)
+               wantedExposure = mix(wantedExposure, previousExposure, exp(-animationTimerDelta * exposureParams.speedDarkBright)); // dark -> bright
+       else
+               wantedExposure = mix(wantedExposure, previousExposure, exp(-animationTimerDelta * exposureParams.speedBrightDark)); // bright -> dark
+
+       gl_FragColor = vec4(vec3(wantedExposure), 1.);
+}
diff --git a/client/shaders/update_exposure/opengl_vertex.glsl b/client/shaders/update_exposure/opengl_vertex.glsl
new file mode 100644 (file)
index 0000000..12692c2
--- /dev/null
@@ -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;
+}
index a35adca6097007ac5e56190534837331c270a671..fb6bc9df8e54dda759a875f1eaaeb57e07f2fc1f 100644 (file)
@@ -7472,6 +7472,15 @@ child will follow movement and rotation of that bone.
       * `shadows` is a table that controls ambient shadows
         * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
             * This value has no effect on clients who have the "Dynamic Shadows" shader disabled.
+      * `exposure` is a table that controls automatic exposure.
+        The basic exposure factor equation is `e = 2^exposure_correction / clamp(luminance, 2^luminance_min, 2^luminance_max)`
+        * `luminance_min` set the lower luminance boundary to use in the calculation
+        * `luminance_max` set the upper luminance boundary to use in the calculation
+        * `exposure_correction` correct observed exposure by the given EV value
+        * `speed_dark_bright` set the speed of adapting to bright light
+        * `speed_bright_dark` set the speed of adapting to dark scene
+        * `center_weight_power` set the power factor for center-weighted luminance measurement
+
 * `get_lighting()`: returns the current state of lighting for the player.
     * Result is a table with the same fields as `light_definition` in `set_lighting`.
 * `respawn()`: Respawns the player using the same mechanism as the death screen,
diff --git a/games/devtest/mods/lighting/init.lua b/games/devtest/mods/lighting/init.lua
new file mode 100644 (file)
index 0000000..5fb0f68
--- /dev/null
@@ -0,0 +1,140 @@
+local lighting_sections = {
+       {n = "shadows", d = "Shadows",
+               entries = {
+                       { n = "intensity", d = "Shadow Intensity", min = 0, max = 1 }
+               }
+       },
+       {
+               n = "exposure", d = "Exposure",
+               entries = {
+                       {n = "luminance_min", d = "Minimum Luminance", min = -10, max = 10},
+                       {n = "luminance_max", d = "Maximum Luminance", min = -10, max = 10},
+                       {n = "exposure_correction", d = "Exposure Correction", min = -10, max = 10},
+                       {n = "speed_dark_bright", d = "Bright light adaptation speed", min = -10, max = 10, type="log2"},
+                       {n = "speed_bright_dark", d = "Dark scene adaptation speed", min = -10, max = 10, type="log2"},
+                       {n = "center_weight_power", d = "Power factor for center-weighting", min = 0.1, max = 10},
+               }
+       }
+}
+
+local function dump_lighting(lighting)
+       local result = "{\n"
+       local section_count = 0
+       for _,section in ipairs(lighting_sections) do
+               section_count = section_count + 1
+
+               local parameters = section.entries or {}
+               local state = lighting[section.n] or {}
+
+               result = result.."  "..section.n.." = {\n"
+
+               local count = 0
+               for _,v in ipairs(parameters) do
+                       count = count + 1
+                       result = result.."    "..v.n.." = "..(math.floor(state[v.n] * 1000)/1000)
+                       if count < #parameters then
+                               result = result..","
+                       end
+                       result = result.."\n"
+               end
+
+               result = result.."  }"
+
+               if section_count < #lighting_sections then
+                       result = result..","
+               end
+               result = result.."\n"
+       end
+       result = result .."}"
+       return result
+end
+
+minetest.register_chatcommand("set_lighting", {
+       params = "",
+       description = "Tune lighting parameters",
+       func = function(player_name, param)
+               local player = minetest.get_player_by_name(player_name);
+               if not player then return end
+
+               local lighting = player:get_lighting()
+               local exposure = lighting.exposure or {}
+
+               local form = {
+                       "formspec_version[2]",
+                       "size[15,30]",
+                       "position[0.99,0.15]",
+                       "anchor[1,0]",
+                       "padding[0.05,0.1]",
+                       "no_prepend[]"
+               };
+
+               local line = 1
+               for _,section in ipairs(lighting_sections) do
+                       local parameters = section.entries or {}
+                       local state = lighting[section.n] or {}
+
+                       table.insert(form, "label[1,"..line..";"..section.d.."]")
+                       line  = line + 1
+
+                       for _,v in ipairs(parameters) do
+                               table.insert(form, "label[2,"..line..";"..v.d.."]")
+                               table.insert(form, "scrollbaroptions[min=0;max=1000;smallstep=10;largestep=100;thumbsize=10]")
+                               local value = state[v.n]
+                               if v.type == "log2" then
+                                       value = math.log(value or 1) / math.log(2)
+                               end
+                               local sb_scale = math.floor(1000 * (math.max(v.min, value or 0) - v.min) / (v.max - v.min))
+                               table.insert(form, "scrollbar[2,"..(line+0.7)..";12,1;horizontal;"..section.n.."."..v.n..";"..sb_scale.."]")
+                               line = line + 2.7
+                       end
+
+                       line = line + 1
+               end
+
+               minetest.show_formspec(player_name, "lighting", table.concat(form))
+               local debug_value = dump_lighting(lighting)
+               local debug_ui = player:hud_add({type="text", position={x=0.1, y=0.3}, scale={x=1,y=1}, alignment = {x=1, y=1}, text=debug_value, number=0xFFFFFF})
+               player:get_meta():set_int("lighting_hud", debug_ui)
+       end
+})
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+       if formname ~= "lighting" then return end
+
+       if not player then return end
+
+       local hud_id = player:get_meta():get_int("lighting_hud")
+
+       if fields.quit then
+               player:hud_remove(hud_id)
+               player:get_meta():set_int("lighting_hud", -1)
+               return
+       end
+
+       local lighting = player:get_lighting()
+       for _,section in ipairs(lighting_sections) do
+               local parameters = section.entries or {}
+
+               local state = (lighting[section.n] or {})
+               lighting[section.n] = state
+
+               for _,v in ipairs(parameters) do
+                       
+                       if fields[section.n.."."..v.n] then
+                               local event = minetest.explode_scrollbar_event(fields[section.n.."."..v.n])
+                               if event.type == "CHG" then
+                                       local value = v.min + (v.max - v.min) * (event.value / 1000);
+                                       if v.type == "log2" then
+                                               value = math.pow(2, value);
+                                       end
+                                       state[v.n] = value;
+                               end
+                       end
+               end
+       end
+
+       local debug_value = dump_lighting(lighting)
+       player:hud_change(hud_id, "text", debug_value)
+
+       player:set_lighting(lighting)
+end)
\ No newline at end of file
diff --git a/games/devtest/mods/lighting/mod.conf b/games/devtest/mods/lighting/mod.conf
new file mode 100644 (file)
index 0000000..83bbbef
--- /dev/null
@@ -0,0 +1,2 @@
+name = lighting
+description = UI to control and debug lighting parameters
index 4702cc5562395f2f124ad063de4dfbeb6a151d3b..48cd47f10732d28f5c602ce40fe4b517c3ae363b 100644 (file)
@@ -210,20 +210,6 @@ minetest.register_chatcommand("dump_item", {
        end,
 })
 
--- shadow control
-minetest.register_on_joinplayer(function (player)
-       player:set_lighting({shadows={intensity = 0.33}})
-end)
-
-core.register_chatcommand("set_shadow", {
-    params = "<shadow_intensity>",
-    description = "Set shadow parameters of current player.",
-    func = function(player_name, param)
-        local shadow_intensity = tonumber(param)
-        minetest.get_player_by_name(player_name):set_lighting({shadows = { intensity = shadow_intensity} })
-    end
-})
-
 core.register_chatcommand("set_saturation", {
     params = "<saturation>",
     description = "Set the saturation for current player.",
index b4f4573409b8c1833f4d2edaa964b6d94583eab4..afe0083951d2ac6836dab5ffffd5307c019db4f4 100644 (file)
@@ -375,6 +375,7 @@ set(common_SRCS
        itemdef.cpp
        itemstackmetadata.cpp
        light.cpp
+       lighting.cpp
        log.cpp
        main.cpp
        map.cpp
index 0070fa82f95ce2d9311489071214f03e866f9c09..d9b88eb4a54b5f05b47d38fb233b573891f0902b 100644 (file)
@@ -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;
+       }
 }
index 87820fbe88b5eafd7baac851bf7aaff71d7114db..f5d46deb5dabf7ae272923c7e3c9c1104ab40f12 100644 (file)
@@ -144,6 +144,7 @@ class ClientEnvironment : public Environment
 
        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 @@ class ClientEnvironment : public Environment
        std::list<std::string> m_player_names;
        v3s16 m_camera_offset;
        u64 m_frame_time = 0;
+       u64 m_frame_dtime = 0;
        u64 m_frame_time_pause_accumulator = 0;
 };
index 3f76d2e05d48ddc281a34c0c5040dc638c38b2b3..cf0117046906acc1c4e9a10770a48d5e1635c590 100644 (file)
@@ -414,6 +414,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        CachedPixelShaderSetting<float> m_fog_distance;
        CachedVertexShaderSetting<float> m_animation_timer_vertex;
        CachedPixelShaderSetting<float> m_animation_timer_pixel;
+       CachedVertexShaderSetting<float> m_animation_timer_delta_vertex;
+       CachedPixelShaderSetting<float> m_animation_timer_delta_pixel;
        CachedPixelShaderSetting<float, 3> m_day_light;
        CachedPixelShaderSetting<float, 4> m_star_color;
        CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
@@ -427,8 +429,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        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;
+       CachedStructPixelShaderSetting<float, 7> m_exposure_params_pixel;
+       float m_user_exposure_compensation;
        bool m_bloom_enabled;
        CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
        float m_bloom_intensity;
@@ -443,8 +445,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        {
                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 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                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 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                m_texture2("texture2"),
                m_texture3("texture3"),
                m_texel_size0("texelSize0"),
-               m_exposure_factor_pixel("exposureFactor"),
+               m_exposure_params_pixel("exposureParams",
+                               std::array<const char*, 7> {
+                                               "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 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                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 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 
                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<float, 7> 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);
index 13898f8a41f078bbe7beb97db2404218fe7e6a36..cc275a7ef0f2c26dc3a503ac92505a40ce6724d1 100644 (file)
@@ -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;
index 35462410236cb81bf53a66dcbfb7a917429ad849..bfdef2931c7c07c1210d31e730274651cb56033b 100644 (file)
@@ -53,7 +53,7 @@ struct PipelineContext
 
 /**
  * Base object that can be owned by RenderPipeline
- * 
+ *
  */
 class RenderPipelineObject
 {
@@ -74,7 +74,7 @@ class RenderSource : virtual public RenderPipelineObject
        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 @@ class TextureBuffer : public RenderSource
 
        /**
         * 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 @@ class TextureBuffer : public RenderSource
 
        /**
         * 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 @@ class TextureBuffer : public RenderSource
        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 @@ class TextureBufferOutput : public RenderTarget
 
 /**
  * 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 @@ class RemappingSource : RenderSource
 
        /**
         * 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 @@ class DynamicSource : public RenderSource
        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 @@ class TrivialRenderStep : public RenderStep
 
 /**
  * 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 @@ class SetRenderTargetStep : public TrivialRenderStep
        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 @@ class RenderPipeline : public RenderStep
 
        /**
         * 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.
         */
index ebc7e7411ae9293b27a79e32bd65c857ac41bb24..395a0fe6bc14fadd6e6c473eb7d1262ea7d2edf6 100644 (file)
@@ -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<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR });
-               extract_bloom->setRenderSource(buffer);
-               extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(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<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR, TEXTURE_EXPOSURE_1 });
+                       extract_bloom->setRenderSource(buffer);
+                       extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(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<PostProcessingStep>(shader_id, std::vector<u8> { source });
                        step->setRenderSource(buffer);
@@ -158,7 +172,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
                        step->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(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<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_EXPOSURE_1, u8(TEXTURE_BLOOM_DOWN + MIPMAP_LEVELS - 1) });
+               update_exposure->setBilinearFilter(1, true);
+               update_exposure->setRenderSource(buffer);
+               update_exposure->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_EXPOSURE_2));
+       }
+
        // final post-processing
        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_UP });
+       PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { 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<SwapTexturesStep>(buffer, TEXTURE_EXPOSURE_1, TEXTURE_EXPOSURE_2);
+       }
+
        return effect;
 }
index ec7a05338b0e29207bf6fceb0be58c673324d128..a58b0efe69ab43a1d5c45aee7facf5a14ee03016 100644 (file)
@@ -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,
index da3da8ab1d17a65969129be68c8ad7ec5b6d2a80..ccecb22c3a72432aa198d9795e87bb62db05f09a 100644 (file)
@@ -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();
index 8f1ba1e4153cc87e21062eab6637ab34616d54d9..33c3e45e3087628ad9466ec2def4efe3450851ce 100644 (file)
@@ -121,6 +121,43 @@ class CachedVertexShaderSetting : public CachedShaderSetting<T, count, cache> {
                CachedShaderSetting<T, count, cache>(name, false){}
 };
 
+template <typename T, std::size_t count, bool cache, bool is_pixel>
+class CachedStructShaderSetting {
+       const char *m_name;
+       T m_sent[count];
+       bool has_been_set = false;
+       std::array<const char*, count> m_fields;
+public:
+       CachedStructShaderSetting(const char *name, std::array<const char*, count> &&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<typename T, std::size_t count, bool cache = true>
+using CachedStructVertexShaderSetting = CachedStructShaderSetting<T, count, cache, false>;
+
+template<typename T, std::size_t count, bool cache = true>
+using CachedStructPixelShaderSetting = CachedStructShaderSetting<T, count, cache, true>;
 
 /*
        ShaderSource creates and caches shaders.
index 66a7411c494573f8bf63200b077819cc218287c3..e9ee8281d64fca04a8503994d38069b2a558e6b6 100644 (file)
@@ -278,7 +278,8 @@ 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("exposure_compensation", "0.0");
+       settings->setDefault("enable_auto_exposure", "false");
        settings->setDefault("enable_bloom", "false");
        settings->setDefault("enable_bloom_debug", "false");
        settings->setDefault("bloom_strength_factor", "1.0");
diff --git a/src/lighting.cpp b/src/lighting.cpp
new file mode 100644 (file)
index 0000000..b19d477
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+Minetest
+Copyright (C) 2021 x2048, Dmitry Kostenko <codeforsmile@gmail.com>
+
+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; either version 2.1 of the License, or
+(at your option) any later version.
+
+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, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "lighting.h"
+
+AutoExposure::AutoExposure()
+        : luminance_min(-3.f),
+        luminance_max(-3.f),
+        exposure_correction(0.0f),
+        speed_dark_bright(1000.f),
+        speed_bright_dark(1000.f),
+        center_weight_power(1.f)
+{}
index 6c837568b618a8ac868d2f5ee643d7b95f7f7477..9c42116050374c100f6ef63234b90c40532772d9 100644 (file)
@@ -19,10 +19,38 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #pragma once
 
+
+/**
+ * Parameters for automatic exposure compensation
+ *
+ * Automatic exposure compensation uses the following equation:
+ *
+ * wanted_exposure = 2^exposure_correction / clamp(observed_luminance, 2^luminance_min, 2^luminance_max)
+ *
+ */
+struct AutoExposure
+{
+    /// @brief Minimum boundary for computed luminance
+    float luminance_min;
+    /// @brief Maximum boundary for computed luminance
+    float luminance_max;
+    /// @brief Luminance bias. Higher values make the scene darker, can be negative.
+    float exposure_correction;
+    /// @brief Speed of transition from dark to bright scenes
+    float speed_dark_bright;
+    /// @brief Speed of transition from bright to dark scenes
+    float speed_bright_dark;
+    /// @brief Power value for center-weighted metering. Value of 1.0 measures entire screen uniformly
+    float center_weight_power;
+
+    AutoExposure();
+};
+
 /** Describes ambient light settings for a player
  */
 struct Lighting
 {
+    AutoExposure exposure;
     float shadow_intensity {0.0f};
     float saturation {1.0f};
 };
index 829b1c3e69969adeb648f2bf57c27a705e93a9ce..0e62563568d30c614ae14ff6f975eab96ac68c42 100644 (file)
@@ -1766,4 +1766,12 @@ void Client::handleCommand_SetLighting(NetworkPacket *pkt)
                *pkt >> lighting.shadow_intensity;
        if (pkt->getRemainingBytes() >= 4)
                *pkt >> lighting.saturation;
+       if (pkt->getRemainingBytes() >= 24) {
+               *pkt >> lighting.exposure.luminance_min
+                               >> lighting.exposure.luminance_max
+                               >> lighting.exposure.exposure_correction
+                               >> lighting.exposure.speed_dark_bright
+                               >> lighting.exposure.speed_bright_dark
+                               >> lighting.exposure.center_weight_power;
+       }
 }
index 01e65ef68b77dce6ac39503d4ae581677ca20ed1..4e50ef53307ab11f3870dd4fc9630c11e1e48f84 100644 (file)
@@ -831,6 +831,13 @@ enum ToClientCommand
        /*
                f32 shadow_intensity
                f32 saturation
+               exposure parameters
+                       f32 luminance_min
+                       f32 luminance_max
+                       f32 exposure_correction
+                       f32 speed_dark_bright
+                       f32 speed_bright_dark
+                       f32 center_weight_power
        */
 
        TOCLIENT_NUM_MSG_TYPES = 0x64,
index 0a3e05907bb60e7e46e978f3d33c3e137d81a2e3..fc2c1254b5f36eda44a99c1152417a9dcee33976 100644 (file)
@@ -2297,8 +2297,20 @@ int ObjectRef::l_set_lighting(lua_State *L)
                getfloatfield(L, -1, "intensity", lighting.shadow_intensity);
        }
        lua_pop(L, 1); // shadows
+
        getfloatfield(L, -1, "saturation", lighting.saturation);
 
+       lua_getfield(L, 2, "exposure");
+       if (lua_istable(L, -1)) {
+               lighting.exposure.luminance_min       = getfloatfield_default(L, -1, "luminance_min",       lighting.exposure.luminance_min);
+               lighting.exposure.luminance_max       = getfloatfield_default(L, -1, "luminance_max",       lighting.exposure.luminance_max);
+               lighting.exposure.exposure_correction = getfloatfield_default(L, -1, "exposure_correction",      lighting.exposure.exposure_correction);
+               lighting.exposure.speed_dark_bright   = getfloatfield_default(L, -1, "speed_dark_bright",   lighting.exposure.speed_dark_bright);
+               lighting.exposure.speed_bright_dark   = getfloatfield_default(L, -1, "speed_bright_dark",   lighting.exposure.speed_bright_dark);
+               lighting.exposure.center_weight_power = getfloatfield_default(L, -1, "center_weight_power", lighting.exposure.center_weight_power);
+       }
+       lua_pop(L, 1); // exposure
+
        getServer(L)->setLighting(player, lighting);
        return 0;
 }
@@ -2321,6 +2333,20 @@ int ObjectRef::l_get_lighting(lua_State *L)
        lua_setfield(L, -2, "shadows");
        lua_pushnumber(L, lighting.saturation);
        lua_setfield(L, -2, "saturation");
+       lua_newtable(L); // "exposure"
+       lua_pushnumber(L, lighting.exposure.luminance_min);
+       lua_setfield(L, -2, "luminance_min");
+       lua_pushnumber(L, lighting.exposure.luminance_max);
+       lua_setfield(L, -2, "luminance_max");
+       lua_pushnumber(L, lighting.exposure.exposure_correction);
+       lua_setfield(L, -2, "exposure_correction");
+       lua_pushnumber(L, lighting.exposure.speed_dark_bright);
+       lua_setfield(L, -2, "speed_dark_bright");
+       lua_pushnumber(L, lighting.exposure.speed_bright_dark);
+       lua_setfield(L, -2, "speed_bright_dark");
+       lua_pushnumber(L, lighting.exposure.center_weight_power);
+       lua_setfield(L, -2, "center_weight_power");
+       lua_setfield(L, -2, "exposure");
        return 1;
 }
 
index ee08b45fd792291270c5722397f863e56d3a8acd..a1171f40440f8a83d1c0a45d5056d1f3924ebe88 100644 (file)
@@ -1866,6 +1866,13 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting)
        pkt << lighting.shadow_intensity;
        pkt << lighting.saturation;
 
+       pkt << lighting.exposure.luminance_min
+                       << lighting.exposure.luminance_max
+                       << lighting.exposure.exposure_correction
+                       << lighting.exposure.speed_dark_bright
+                       << lighting.exposure.speed_bright_dark
+                       << lighting.exposure.center_weight_power;
+
        Send(&pkt);
 }