diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b3077c4536c..262bee91afd 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2744,7 +2744,7 @@ [b]Note:[/b] This property is only read when the project starts. There is currently no way to change this setting at run-time. - If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible in 3D. 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS]. + If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible. If [member rendering/viewport/hdr_2d] is [code]false[/code], 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS]. If [member rendering/viewport/hdr_2d] is [code]true[/code], debanding will affect all 2D and 3D rendering, including canvas items. In some cases, debanding may introduce a slightly noticeable dithering pattern. It's recommended to enable debanding only when actually needed since the dithering pattern will make lossless-compressed screenshots larger. [b]Note:[/b] This property is only read when the project starts. To set debanding at runtime, set [member Viewport.use_debanding] on the root [Viewport] instead, or use [method RenderingServer.viewport_set_use_debanding]. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 47c48f1487c..2d983b0522d 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -4232,7 +4232,7 @@ - If [code]true[/code], enables debanding on the specified viewport. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding] or [member Viewport.use_debanding]. + Equivalent to [member Viewport.use_debanding]. See also [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding]. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index f6f9895cb4b..9fb4de8e8ab 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -444,7 +444,7 @@ If [code]true[/code], the viewport should render its background as transparent. - If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible in 3D. 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS]. + If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible. If [member use_hdr_2d] is [code]false[/code], 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS]. If [member use_hdr_2d] is [code]true[/code], debanding will only be applied if this is the root [Viewport] and will affect all 2D and 3D rendering, including canvas items. In some cases, debanding may introduce a slightly noticeable dithering pattern. It's recommended to enable debanding only when actually needed since the dithering pattern will make lossless-compressed screenshots larger. See also [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding] and [method RenderingServer.viewport_set_use_debanding]. diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 405f07ca1b4..6119b8d2475 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -647,6 +647,8 @@ public: virtual void render_target_do_msaa_resolve(RID p_render_target) override {} virtual void render_target_set_use_hdr(RID p_render_target, bool p_use_hdr_2d) override; virtual bool render_target_is_using_hdr(RID p_render_target) const override; + virtual void render_target_set_use_debanding(RID p_render_target, bool p_use_debanding) override {} + virtual bool render_target_is_using_debanding(RID p_render_target) const override { return false; } // new void render_target_set_as_unused(RID p_render_target) override { diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h index 973bd7a7e13..afda5e054c7 100644 --- a/servers/rendering/dummy/storage/texture_storage.h +++ b/servers/rendering/dummy/storage/texture_storage.h @@ -180,6 +180,8 @@ public: virtual void render_target_do_msaa_resolve(RID p_render_target) override {} virtual void render_target_set_use_hdr(RID p_render_target, bool p_use_hdr_2d) override {} virtual bool render_target_is_using_hdr(RID p_render_target) const override { return false; } + virtual void render_target_set_use_debanding(RID p_render_target, bool p_use_debanding) override {} + virtual bool render_target_is_using_debanding(RID p_render_target) const override { return false; } virtual void render_target_request_clear(RID p_render_target, const Color &p_clear_color) override {} virtual bool render_target_is_clear_requested(RID p_render_target) override { return false; } diff --git a/servers/rendering/renderer_rd/effects/tone_mapper.cpp b/servers/rendering/renderer_rd/effects/tone_mapper.cpp index 83cf2ed7195..63feecd988d 100644 --- a/servers/rendering/renderer_rd/effects/tone_mapper.cpp +++ b/servers/rendering/renderer_rd/effects/tone_mapper.cpp @@ -123,7 +123,8 @@ void ToneMapper::tonemapper(RID p_source_color, RID p_dst_framebuffer, const Ton tonemap.push_constant.flags |= p_settings.use_color_correction ? TONEMAP_FLAG_USE_COLOR_CORRECTION : 0; tonemap.push_constant.flags |= p_settings.use_fxaa ? TONEMAP_FLAG_USE_FXAA : 0; - tonemap.push_constant.flags |= p_settings.use_debanding ? TONEMAP_FLAG_USE_DEBANDING : 0; + // When convert_to_srgb is false: postpone debanding until convert_to_srgb is true (usually during blit). + tonemap.push_constant.flags |= (p_settings.use_debanding && p_settings.convert_to_srgb) ? TONEMAP_FLAG_USE_DEBANDING : 0; tonemap.push_constant.pixel_size[0] = 1.0 / p_settings.texture_size.x; tonemap.push_constant.pixel_size[1] = 1.0 / p_settings.texture_size.y; @@ -207,8 +208,8 @@ void ToneMapper::tonemapper(RD::DrawListID p_subpass_draw_list, RID p_source_col tonemap.push_constant.auto_exposure_scale = p_settings.auto_exposure_scale; tonemap.push_constant.flags |= p_settings.use_color_correction ? TONEMAP_FLAG_USE_COLOR_CORRECTION : 0; - - tonemap.push_constant.flags |= p_settings.use_debanding ? TONEMAP_FLAG_USE_DEBANDING : 0; + // When convert_to_srgb is false: postpone debanding until convert_to_srgb is true (usually during blit). + tonemap.push_constant.flags |= (p_settings.use_debanding && p_settings.convert_to_srgb) ? TONEMAP_FLAG_USE_DEBANDING : 0; tonemap.push_constant.luminance_multiplier = p_settings.luminance_multiplier; tonemap.push_constant.flags |= p_settings.convert_to_srgb ? TONEMAP_FLAG_CONVERT_TO_SRGB : 0; diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp index 21ba32af63e..ef48280f52f 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp @@ -96,6 +96,8 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID blit.push_constant.upscale = p_render_targets[i].lens_distortion.upscale; blit.push_constant.aspect_ratio = p_render_targets[i].lens_distortion.aspect_ratio; blit.push_constant.convert_to_srgb = texture_storage->render_target_is_using_hdr(p_render_targets[i].render_target); + // If convert_to_srgb is false, debanding was applied earlier (usually in tonemapping). + blit.push_constant.use_debanding = uint32_t(blit.push_constant.convert_to_srgb && texture_storage->render_target_is_using_debanding(p_render_targets[i].render_target)); RD::get_singleton()->draw_list_set_push_constant(draw_list, &blit.push_constant, sizeof(BlitPushConstant)); RD::get_singleton()->draw_list_draw(draw_list, true); @@ -257,6 +259,7 @@ void RendererCompositorRD::set_boot_image(const Ref &p_image, const Color blit.push_constant.upscale = 1.0; blit.push_constant.aspect_ratio = 1.0; blit.push_constant.convert_to_srgb = false; + blit.push_constant.use_debanding = false; RD::get_singleton()->draw_list_set_push_constant(draw_list, &blit.push_constant, sizeof(BlitPushConstant)); RD::get_singleton()->draw_list_draw(draw_list, true); diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h index 42e3bb03be3..dd9e420db03 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.h +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h @@ -73,7 +73,6 @@ protected: float rotation_sin; float rotation_cos; - float pad[2]; float eye_center[2]; float k1; @@ -83,6 +82,8 @@ protected: float aspect_ratio; uint32_t layer; uint32_t convert_to_srgb; + uint32_t use_debanding; + float pad; }; struct Blit { diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 2a037d241e2..6cbc59b5981 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -665,6 +665,7 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const Rende tonemap.use_color_correction = false; tonemap.use_1d_color_correction = false; tonemap.color_correction_texture = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_3D_WHITE); + tonemap.convert_to_srgb = !texture_storage->render_target_is_using_hdr(render_target); if (can_use_effects && p_render_data->environment.is_valid()) { tonemap.use_bcs = environment_get_adjustments_enabled(p_render_data->environment); @@ -674,15 +675,13 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const Rende if (environment_get_adjustments_enabled(p_render_data->environment) && environment_get_color_correction(p_render_data->environment).is_valid()) { tonemap.use_color_correction = true; tonemap.use_1d_color_correction = environment_get_use_1d_color_correction(p_render_data->environment); - tonemap.color_correction_texture = texture_storage->texture_get_rd_texture(environment_get_color_correction(p_render_data->environment)); + tonemap.color_correction_texture = texture_storage->texture_get_rd_texture(environment_get_color_correction(p_render_data->environment), !tonemap.convert_to_srgb); } } tonemap.luminance_multiplier = _render_buffers_get_luminance_multiplier(); tonemap.view_count = rb->get_view_count(); - tonemap.convert_to_srgb = !texture_storage->render_target_is_using_hdr(render_target); - RID dest_fb; if (spatial_upscaler != nullptr || use_smaa) { // If we use a spatial upscaler to upscale or SMAA to antialias we need to write our result into an intermediate buffer. @@ -824,6 +823,7 @@ void RendererSceneRenderRD::_post_process_subpass(RID p_source_texture, RID p_fr tonemap.use_color_correction = false; tonemap.use_1d_color_correction = false; tonemap.color_correction_texture = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_3D_WHITE); + tonemap.convert_to_srgb = !texture_storage->render_target_is_using_hdr(rb->get_render_target()); if (can_use_effects && p_render_data->environment.is_valid()) { tonemap.use_bcs = environment_get_adjustments_enabled(p_render_data->environment); @@ -833,7 +833,7 @@ void RendererSceneRenderRD::_post_process_subpass(RID p_source_texture, RID p_fr if (environment_get_adjustments_enabled(p_render_data->environment) && environment_get_color_correction(p_render_data->environment).is_valid()) { tonemap.use_color_correction = true; tonemap.use_1d_color_correction = environment_get_use_1d_color_correction(p_render_data->environment); - tonemap.color_correction_texture = texture_storage->texture_get_rd_texture(environment_get_color_correction(p_render_data->environment)); + tonemap.color_correction_texture = texture_storage->texture_get_rd_texture(environment_get_color_correction(p_render_data->environment), !tonemap.convert_to_srgb); } } @@ -843,8 +843,6 @@ void RendererSceneRenderRD::_post_process_subpass(RID p_source_texture, RID p_fr tonemap.luminance_multiplier = _render_buffers_get_luminance_multiplier(); tonemap.view_count = rb->get_view_count(); - tonemap.convert_to_srgb = !texture_storage->render_target_is_using_hdr(rb->get_render_target()); - tone_mapper->tonemapper(draw_list, p_source_texture, RD::get_singleton()->framebuffer_get_format(p_framebuffer), tonemap); RD::get_singleton()->draw_command_end_label(); diff --git a/servers/rendering/renderer_rd/shaders/blit.glsl b/servers/rendering/renderer_rd/shaders/blit.glsl index fe6416f03c6..af41938b3c9 100644 --- a/servers/rendering/renderer_rd/shaders/blit.glsl +++ b/servers/rendering/renderer_rd/shaders/blit.glsl @@ -10,7 +10,6 @@ layout(push_constant, std140) uniform Pos { float rotation_sin; float rotation_cos; - vec2 pad; vec2 eye_center; float k1; @@ -20,6 +19,8 @@ layout(push_constant, std140) uniform Pos { float aspect_ratio; uint layer; bool convert_to_srgb; + bool use_debanding; + float pad; } data; @@ -50,7 +51,6 @@ layout(push_constant, std140) uniform Pos { float rotation_sin; float rotation_cos; - vec2 pad; vec2 eye_center; float k1; @@ -60,6 +60,8 @@ layout(push_constant, std140) uniform Pos { float aspect_ratio; uint layer; bool convert_to_srgb; + bool use_debanding; + float pad; } data; @@ -74,12 +76,27 @@ layout(binding = 0) uniform sampler2D src_rt; #endif vec3 linear_to_srgb(vec3 color) { - // If going to srgb, clamp from 0 to 1. - 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))); } +// From https://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf +// and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom) +// NOTE: `frag_coord` is in pixels (i.e. not normalized UV). +// This dithering must be applied after encoding changes (linear/nonlinear) have been applied +// as the final step before quantization from floating point to integer values. +vec3 screen_space_dither(vec2 frag_coord) { + // Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR. + // Removed the time component to avoid passing time into this shader. + vec3 dither = vec3(dot(vec2(171.0, 231.0), frag_coord)); + dither.rgb = fract(dither.rgb / vec3(103.0, 71.0, 97.0)); + + // Subtract 0.5 to avoid slightly brightening the whole viewport. + // Use a dither strength of 100% rather than the 37.5% suggested by the original source. + // Divide by 255 to align to 8-bit quantization. + return (dither.rgb - 0.5) / 255.0; +} + void main() { #ifdef APPLY_LENS_DISTORTION vec2 coords = uv * 2.0 - 1.0; @@ -118,5 +135,10 @@ void main() { if (data.convert_to_srgb) { color.rgb = linear_to_srgb(color.rgb); // Regular linear -> SRGB conversion. + // When convert_to_srgb is true, debanding was skipped in tonemap.glsl. + if (data.use_debanding) { + color.rgb += screen_space_dither(gl_FragCoord.xy); + } + color.rgb = clamp(color.rgb, vec3(0.0), vec3(1.0)); } } diff --git a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl index 3043dae8bcd..84e149bec43 100644 --- a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl @@ -328,7 +328,8 @@ vec3 tonemap_agx(vec3 color) { } vec3 linear_to_srgb(vec3 color) { - //if going to srgb, clamp from 0 to 1. + // 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))); @@ -816,12 +817,17 @@ vec3 do_fxaa(vec3 color, float exposure, vec2 uv_interp) { // From https://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf // and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom) // NOTE: `frag_coord` is in pixels (i.e. not normalized UV). +// This dithering must be applied after encoding changes (linear/nonlinear) have been applied +// as the final step before quantization from floating point to integer values. vec3 screen_space_dither(vec2 frag_coord) { // Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR. + // Removed the time component to avoid passing time into this shader. vec3 dither = vec3(dot(vec2(171.0, 231.0), frag_coord)); dither.rgb = fract(dither.rgb / vec3(103.0, 71.0, 97.0)); // Subtract 0.5 to avoid slightly brightening the whole viewport. + // Use a dither strength of 100% rather than the 37.5% suggested by the original source. + // Divide by 255 to align to 8-bit quantization. return (dither.rgb - 0.5) / 255.0; } @@ -866,7 +872,8 @@ void main() { color.rgb = apply_tonemapping(color.rgb, params.white); - if (bool(params.flags & FLAG_CONVERT_TO_SRGB)) { + 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. } #ifndef SUBPASS @@ -879,7 +886,7 @@ void main() { // high dynamic range -> SRGB glow = apply_tonemapping(glow, params.white); - if (bool(params.flags & FLAG_CONVERT_TO_SRGB)) { + if (convert_to_srgb) { glow = linear_to_srgb(glow); } @@ -894,7 +901,13 @@ void main() { } 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 = 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. } if (bool(params.flags & FLAG_USE_DEBANDING)) { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index a8e1f323c88..3706eb3779b 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -3709,6 +3709,20 @@ bool TextureStorage::render_target_is_using_hdr(RID p_render_target) const { return rt->use_hdr; } +void TextureStorage::render_target_set_use_debanding(RID p_render_target, bool p_use_debanding) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_NULL(rt); + + rt->use_debanding = p_use_debanding; +} + +bool TextureStorage::render_target_is_using_debanding(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_NULL_V(rt, false); + + return rt->use_debanding; +} + RID TextureStorage::render_target_get_rd_framebuffer(RID p_render_target) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_NULL_V(rt, RID()); diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index 8db9a565ab1..744f2f88eff 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -366,6 +366,7 @@ private: bool is_transparent = false; bool use_hdr = false; + bool use_debanding = false; bool sdf_enabled = false; @@ -759,6 +760,8 @@ public: virtual void render_target_do_msaa_resolve(RID p_render_target) override; virtual void render_target_set_use_hdr(RID p_render_target, bool p_use_hdr) override; virtual bool render_target_is_using_hdr(RID p_render_target) const override; + virtual void render_target_set_use_debanding(RID p_render_target, bool p_use_debanding) override; + virtual bool render_target_is_using_debanding(RID p_render_target) const override; void render_target_copy_to_back_buffer(RID p_render_target, const Rect2i &p_region, bool p_gen_mipmaps); void render_target_clear_back_buffer(RID p_render_target, const Rect2i &p_region, const Color &p_color); diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index ce2fe2f1e56..f87c90071fe 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -1401,6 +1401,7 @@ void RendererViewport::viewport_set_use_debanding(RID p_viewport, bool p_use_deb return; } viewport->use_debanding = p_use_debanding; + RSG::texture_storage->render_target_set_use_debanding(viewport->render_target, p_use_debanding); _configure_3d_render_buffers(viewport); } diff --git a/servers/rendering/storage/texture_storage.h b/servers/rendering/storage/texture_storage.h index c600adc352f..135bcc39fc5 100644 --- a/servers/rendering/storage/texture_storage.h +++ b/servers/rendering/storage/texture_storage.h @@ -158,6 +158,8 @@ public: virtual void render_target_do_msaa_resolve(RID p_render_target) = 0; virtual void render_target_set_use_hdr(RID p_render_target, bool p_use_hdr) = 0; virtual bool render_target_is_using_hdr(RID p_render_target) const = 0; + virtual void render_target_set_use_debanding(RID p_render_target, bool p_use_debanding) = 0; + virtual bool render_target_is_using_debanding(RID p_render_target) const = 0; virtual void render_target_request_clear(RID p_render_target, const Color &p_clear_color) = 0; virtual bool render_target_is_clear_requested(RID p_render_target) = 0;