../../src/itemdef.cpp \
../../src/itemstackmetadata.cpp \
../../src/light.cpp \
+ ../../src/lighting.cpp \
../../src/log.cpp \
../../src/main.cpp \
../../src/map.cpp \
[**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]
#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;
centroid varying vec2 varTexCoord;
#endif
+varying float exposure;
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.
}
+#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;
}
#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;
centroid varying vec2 varTexCoord;
#endif
+varying float exposure;
+
#ifdef ENABLE_BLOOM
vec4 applyBloom(vec4 color, vec2 uv)
if (uv.x > 0.5 || uv.y > 0.5)
#endif
{
- color.rgb *= exposureFactor;
+ color.rgb *= exposure * exposureParams.compensationFactor;
}
+#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;
}
--- /dev/null
+#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.);
+}
--- /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;
+}
* `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,
--- /dev/null
+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
--- /dev/null
+name = lighting
+description = UI to control and debug lighting parameters
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.",
itemdef.cpp
itemstackmetadata.cpp
light.cpp
+ lighting.cpp
log.cpp
main.cpp
map.cpp
{
// 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;
+ }
}
void updateFrameTime(bool is_paused);
u64 getFrameTime() const { return m_frame_time; }
+ u64 getFrameTimeDelta() const { return m_frame_dtime; }
private:
ClientMap *m_map;
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;
};
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;
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;
{
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")
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"),
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);
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);
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);
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;
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;
/**
* Base object that can be owned by RenderPipeline
- *
+ *
*/
class 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;
/**
* 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
/**
* 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
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;
/**
* Allows remapping texture indicies in another RenderSource.
- *
+ *
* @note all unmapped indexes are passed through to the underlying render source.
*/
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.
*/
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;
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;
/**
* Dynamically changes render target of another step.
- *
+ *
* This allows re-running parts of the pipeline with different outputs
*/
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
public:
/**
* Add a step to the end of the pipeline
- *
+ *
* @param step reference to a @see RenderStep implementation.
*/
RenderStep *addStep(RenderStep *step)
/**
* 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.
*/
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
// 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);
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--) {
}
}
+ 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;
}
#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,
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();
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.
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");
--- /dev/null
+/*
+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)
+{}
#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};
};
*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;
+ }
}
/*
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,
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;
}
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;
}
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);
}