diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index ccbbcc23e42..de82a8c1d4d 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -36,19 +36,19 @@ - The global brightness value of the rendered scene. Effective only if [member adjustment_enabled] is [code]true[/code]. + Applies a simple brightness adjustment to the rendered image after tonemaping. To adjust scene brightness use [member tonemap_exposure] instead, which is applied before tonemapping and thus less prone to issues with bright colors. Effective only if [member adjustment_enabled] is [code]true[/code]. The [Texture2D] or [Texture3D] lookup table (LUT) to use for the built-in post-process color grading. Can use a [GradientTexture1D] for a 1-dimensional LUT, or a [Texture3D] for a more complex LUT. Effective only if [member adjustment_enabled] is [code]true[/code]. - The global contrast value of the rendered scene (default value is 1). Effective only if [member adjustment_enabled] is [code]true[/code]. + Increasing [member adjustment_contrast] will make dark values darker and bright values brighter. This simple adjustment is applied to the rendered image after tonemaping. When set to a value greater than [code]1.0[/code], [member adjustment_contrast] is prone to clipping colors that become too bright or too dark. Effective only if [member adjustment_enabled] is [code]true[/code]. If [code]true[/code], enables the [code]adjustment_*[/code] properties provided by this resource. If [code]false[/code], modifications to the [code]adjustment_*[/code] properties will have no effect on the rendered scene. - The global color saturation value of the rendered scene (default value is 1). Effective only if [member adjustment_enabled] is [code]true[/code]. + Applies a simple saturation adjustment to the rendered image after tonemaping. When [member adjustment_saturation] is set to [code]0.0[/code], the rendered image will be fully converted to a grayscale image. Effective only if [member adjustment_enabled] is [code]true[/code]. The ambient light's [Color]. Only effective if [member ambient_light_sky_contribution] is lower than [code]1.0[/code] (exclusive). diff --git a/drivers/gles3/shaders/effects/post.glsl b/drivers/gles3/shaders/effects/post.glsl index d8912e7063b..fd79d1a6141 100644 --- a/drivers/gles3/shaders/effects/post.glsl +++ b/drivers/gles3/shaders/effects/post.glsl @@ -86,16 +86,6 @@ vec3 apply_color_correction(vec3 color) { #endif // USE_1D_LUT #endif // USE_COLOR_CORRECTION -#ifdef USE_BCS -vec3 apply_bcs(vec3 color) { - color = mix(vec3(0.0), color, brightness); - color = mix(vec3(0.5), color, contrast); - color = mix(vec3(dot(vec3(1.0), color) * 0.33333), color, saturation); - - return color; -} -#endif - in vec2 uv_interp; layout(location = 0) out vec4 frag_color; @@ -140,12 +130,33 @@ void main() { #endif // USE_GLOW color.rgb = srgb_to_linear(color.rgb); + color.rgb = apply_tonemapping(color.rgb, white); - color.rgb = linear_to_srgb(color.rgb); #ifdef USE_BCS - color.rgb = apply_bcs(color.rgb); -#endif + // Apply brightness: + // Apply to relative luminance. This ensures that the hue and saturation of + // colors is not affected by the adjustment, but requires the multiplication + // to be performed on linear-encoded values. + color.rgb = color.rgb * brightness; + + color.rgb = linear_to_srgb(color.rgb); + + // Apply contrast: + // By applying contrast to RGB values that are perceptually uniform (nonlinear), + // the darkest values are not hard-clipped as badly, which produces a + // higher quality contrast adjustment and maintains compatibility with + // existing projects. + color.rgb = mix(vec3(0.5), color.rgb, contrast); + + // Apply saturation: + // By applying saturation adjustment to nonlinear sRGB-encoded values with + // even weights the preceived brightness of blues are affected, but this + // maintains compatibility with existing projects. + color.rgb = mix(vec3(dot(vec3(1.0), color.rgb) * (1.0 / 3.0)), color.rgb, saturation); +#else + color.rgb = linear_to_srgb(color.rgb); +#endif // USE_BCS #ifdef USE_COLOR_CORRECTION color.rgb = apply_color_correction(color.rgb); diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index beb116c5fbd..0e3f9423b88 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -1246,7 +1246,7 @@ void Environment::_bind_methods() { ADD_GROUP("Tonemap", "tonemap_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tonemap_mode", PROPERTY_HINT_ENUM, "Linear,Reinhard,Filmic,ACES,AgX"), "set_tonemapper", "get_tonemapper"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_exposure", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_tonemap_exposure", "get_tonemap_exposure"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_exposure", PROPERTY_HINT_RANGE, "0,4,0.01,or_greater"), "set_tonemap_exposure", "get_tonemap_exposure"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_white", PROPERTY_HINT_RANGE, "1,16,0.01,or_greater"), "set_tonemap_white", "get_tonemap_white"); // SSR @@ -1522,9 +1522,9 @@ void Environment::_bind_methods() { ADD_GROUP("Adjustments", "adjustment_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "adjustment_enabled", PROPERTY_HINT_GROUP_ENABLE), "set_adjustment_enabled", "is_adjustment_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "adjustment_brightness", PROPERTY_HINT_RANGE, "0.01,8,0.01"), "set_adjustment_brightness", "get_adjustment_brightness"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "adjustment_contrast", PROPERTY_HINT_RANGE, "0.01,8,0.01"), "set_adjustment_contrast", "get_adjustment_contrast"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "adjustment_saturation", PROPERTY_HINT_RANGE, "0.01,8,0.01"), "set_adjustment_saturation", "get_adjustment_saturation"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "adjustment_brightness", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,or_greater"), "set_adjustment_brightness", "get_adjustment_brightness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "adjustment_contrast", PROPERTY_HINT_RANGE, "0.75,1.25,0.005,or_less,or_greater"), "set_adjustment_contrast", "get_adjustment_contrast"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "adjustment_saturation", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,or_less,or_greater"), "set_adjustment_saturation", "get_adjustment_saturation"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "adjustment_color_correction", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D,Texture3D"), "set_adjustment_color_correction", "get_adjustment_color_correction"); // Constants diff --git a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl index fbf2149e0cb..8e73c38866a 100644 --- a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl @@ -329,13 +329,15 @@ vec3 tonemap_agx(vec3 color) { } vec3 linear_to_srgb(vec3 color) { - // Clamping is not strictly necessary for floating point nonlinear sRGB encoding, - // but many cases that call this function need the result clamped. - color = clamp(color, vec3(0.0), vec3(1.0)); const vec3 a = vec3(0.055f); return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f))); } +vec3 srgb_to_linear(vec3 color) { + const vec3 a = vec3(0.055f); + return mix(pow((color.rgb + a) * (1.0f / (vec3(1.0f) + a)), vec3(2.4f)), color.rgb * (1.0f / 12.92f), lessThan(color.rgb, vec3(0.04045f))); +} + #define TONEMAPPER_LINEAR 0 #define TONEMAPPER_REINHARD 1 #define TONEMAPPER_FILMIC 2 @@ -446,13 +448,6 @@ vec3 apply_glow(vec3 color, vec3 glow, float white) { } } -vec3 apply_bcs(vec3 color, vec3 bcs) { - color = mix(vec3(0.0f), color, bcs.x); - color = mix(vec3(0.5f), color, bcs.y); - color = mix(vec3(dot(vec3(1.0f), color) * 0.33333f), color, bcs.z); - - return color; -} #ifdef USE_1D_LUT vec3 apply_color_correction(vec3 color) { color.r = texture(source_color_correction, vec2(color.r, 0.0f)).r; @@ -928,23 +923,39 @@ void main() { } #endif - bool convert_to_srgb = bool(params.flags & FLAG_CONVERT_TO_SRGB); - if (convert_to_srgb) { - color.rgb = linear_to_srgb(color.rgb); // Regular linear -> SRGB conversion. - } - if (bool(params.flags & FLAG_USE_BCS)) { - color.rgb = apply_bcs(color.rgb, params.bcs); - } + // Apply brightness: + // Apply to relative luminance. This ensures that the hue and saturation of + // colors is not affected by the adjustment, but requires the multiplication + // to be performed on linear-encoded values. + color.rgb = color.rgb * params.bcs.x; - if (bool(params.flags & FLAG_USE_COLOR_CORRECTION)) { - // apply_color_correction requires nonlinear sRGB encoding - if (!convert_to_srgb) { - color.rgb = linear_to_srgb(color.rgb); + color.rgb = linear_to_srgb(color.rgb); + + // Apply contrast: + // By applying contrast to RGB values that are perceptually uniform (nonlinear), + // the darkest values are not hard-clipped as badly, which produces a + // higher quality contrast adjustment and maintains compatibility with + // existing projects. + color.rgb = mix(vec3(0.5), color.rgb, params.bcs.y); + + // Apply saturation: + // By applying saturation adjustment to nonlinear sRGB-encoded values with + // even weights the preceived brightness of blues are affected, but this + // maintains compatibility with existing projects. + color.rgb = mix(vec3(dot(vec3(1.0), color.rgb) * (1.0 / 3.0)), color.rgb, params.bcs.z); + + if (bool(params.flags & FLAG_USE_COLOR_CORRECTION)) { + color.rgb = clamp(color.rgb, vec3(0.0), vec3(1.0)); + color.rgb = apply_color_correction(color.rgb); + // When using color correction and FLAG_CONVERT_TO_SRGB is false, there + // is no need to convert back to linear because the color correction + // texture sampling does this for us. + } else if (!bool(params.flags & FLAG_CONVERT_TO_SRGB)) { + color.rgb = srgb_to_linear(color.rgb); } - color.rgb = apply_color_correction(color.rgb); - // When convert_to_srgb is false, there is no need to convert back to - // linear because the color correction texture sampling does this for us. + } else if (bool(params.flags & FLAG_CONVERT_TO_SRGB)) { + color.rgb = linear_to_srgb(color.rgb); } // Debanding should be done at the end of tonemapping, but before writing to the LDR buffer. diff --git a/servers/rendering/storage/environment_storage.cpp b/servers/rendering/storage/environment_storage.cpp index da8bd7cb8a8..c606cd3823d 100644 --- a/servers/rendering/storage/environment_storage.cpp +++ b/servers/rendering/storage/environment_storage.cpp @@ -793,7 +793,9 @@ void RendererEnvironmentStorage::environment_set_adjustment(RID p_env, bool p_en ERR_FAIL_NULL(env); env->adjustments_enabled = p_enable; - env->adjustments_brightness = p_brightness; + // Scale brightness via the nonlinear sRGB transfer function to provide a + // somewhat perceptually uniform brightness adjustment. + env->adjustments_brightness = p_brightness < 0.04045f ? p_brightness * (1.0f / 12.92f) : Math::pow(float((p_brightness + 0.055f) * (1.0f / (1.055f))), 2.4f); env->adjustments_contrast = p_contrast; env->adjustments_saturation = p_saturation; env->use_1d_color_correction = p_use_1d_color_correction;