From 671cbd39962348fb347c5ca7a9a8abc03655f14a Mon Sep 17 00:00:00 2001 From: AR Date: Tue, 24 Feb 2026 20:50:58 +0500 Subject: [PATCH 1/2] Add motion blur support Graphics part by @HydrogenC, editor part mainly by @AR-DEV-1 & docs mainly by @sphynx-owner Co-authored-by: AR Co-Authored-By: sphynx-owner --- COPYRIGHT.txt | 10 + doc/classes/CameraAttributesPractical.xml | 27 ++ doc/classes/ProjectSettings.xml | 24 ++ doc/classes/RenderingServer.xml | 53 +++ editor/editor_node.cpp | 10 + scene/resources/camera_attributes.cpp | 85 +++++ scene/resources/camera_attributes.h | 29 ++ .../renderer_rd/effects/motion_blur.cpp | 315 ++++++++++++++++++ .../renderer_rd/effects/motion_blur.h | 143 ++++++++ .../render_forward_clustered.cpp | 5 +- .../renderer_rd/renderer_scene_render_rd.cpp | 27 ++ .../renderer_rd/renderer_scene_render_rd.h | 3 + .../shaders/effects/motion_blur_blur.glsl | 262 +++++++++++++++ ...motion_blur_neighbor_max.glsl\342\200\216" | 82 +++++ .../effects/motion_blur_preprocess.glsl | 190 +++++++++++ .../motion_blur_tile_max_x.glsl\342\200\216" | 78 +++++ .../motion_blur_tile_max_y.glsl\342\200\216" | 67 ++++ servers/rendering/rendering_server_default.h | 7 + .../storage/camera_attributes_storage.cpp | 45 +++ .../storage/camera_attributes_storage.h | 58 ++++ servers/rendering_server.cpp | 22 ++ servers/rendering_server.h | 29 ++ 22 files changed, 1570 insertions(+), 1 deletion(-) create mode 100644 servers/rendering/renderer_rd/effects/motion_blur.cpp create mode 100644 servers/rendering/renderer_rd/effects/motion_blur.h create mode 100644 servers/rendering/renderer_rd/shaders/effects/motion_blur_blur.glsl create mode 100644 "servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl\342\200\216" create mode 100644 servers/rendering/renderer_rd/shaders/effects/motion_blur_preprocess.glsl create mode 100644 "servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl\342\200\216" create mode 100644 "servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl\342\200\216" diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index c3bef8f7fef..75608fe316a 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -198,6 +198,16 @@ Copyright: 2013, Jorge Jimenez 2013, Diego Gutierrez License: Expat +Files: servers/rendering/renderer_rd/shaders/effects/motion_blur_blur.glsl + servers/rendering/renderer_rd/shaders/effects/motion_blur_preprocess.glsl + servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl + servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl + servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl +Comment: sphynx-owner's motion blur effect +Copyright: 2025, sphynx-owner +License: MIT + + Files: thirdparty/accesskit/* Comment: AccessKit Copyright: 2023, The AccessKit Authors. diff --git a/doc/classes/CameraAttributesPractical.xml b/doc/classes/CameraAttributesPractical.xml index 59ad77a1461..2e14658b36b 100644 --- a/doc/classes/CameraAttributesPractical.xml +++ b/doc/classes/CameraAttributesPractical.xml @@ -42,6 +42,33 @@ When positive, distance over which blur effect will scale from 0 to [member dof_blur_amount], ending at [member dof_blur_near_distance]. When negative, uses physically-based scaling so depth of field effect will scale from 0 at [member dof_blur_near_distance] and will increase in a physically accurate way as objects get closer to the [Camera3D]. + + + When true, object blur will be clamped to the motion blur tile size (configurable in project settings) as to not reveal the edges of motion blur tiles. + + + + + Defines the amount of motion blur to apply given the object's velocity. At 1.0, the object's velocity will be blurred verbatim. An intensity of 0.5 can be interpreted as a 180 degrees shutter. + + + Defines the intensity of blur similar to [member motion_blur_intensity], but only for the part of the motion caused by camera movement. + + + Defines the intensity of blur similar to [member motion_blur_intensity], but only for the part of the motion caused by object movement. + + + Defines the intensity of blur similar to [member motion_blur_intensity], but only for the part of the motion caused by camera rotation. + + + Defines a magnitude of motion that beyond which motion would start getting blurred. Works in tandem with [member motion_blur_velocity_upper_threshold] to define a seamless transition between non-blurred motion, and a fully blurred motion given the motion's magnitude. + The value is treated in terms of screen portion. + You can use these thresholds to create a less intrusive motion blur effect, where below certain movement speeds details stay crisp. + + + Defines a magnitude of motion that beyond which motion will be blurred in full. Works in tandem with [member motion_blur_velocity_lower_threshold] to define a seamless transition between non-blurred motion, and a fully blurred motion given the motion's magnitude. + The value is treated in terms of screen portion. + You can use these thresholds to create a less intrusive motion blur effect, where below certain movement speeds details stay crisp. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b6862b7808b..b31bf21fc38 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2802,6 +2802,30 @@ If [code]true[/code], jitters DOF samples to make effect slightly blurrier and hide lines created from low sample rates. This can result in a slightly grainy appearance when used with a low number of samples. + + + Define framerate-based behavior of the motion blur. Uses [member motion_blur_reference_framerate] as the reference framerate for the different modes. + MOTION_BLUR_FRAMERATE_MODE_NATIVE applies the blur based on the game's framerate as is, and does not use the reference framerate. + MOTION_BLUR_FRAMERATE_MODE_CAPPED applies the blur based on the game's framerate, but below a certain framerate (which means larger time gap and thus larger blur) it will cap the blur to emulate the blur that will be generated at the reference framerate. This is the default value, and it means that if the game lags, the blur will be kept under control. + MOTION_BLUR_FRAMERATE_MODE_FIXED enforces the reference framerate blur regardless of the game's framerate. This can lead to overblurring when the game's framerate is higher than the reference framerate. + + + Controls the quality of the motion blur. The motion blur uses noise to smoothen the transition between samples to prevent banding, but it can only do so much. Larger sample counts will mitigate the perceivable noise, at the cost of performance. The sample counts are as follows: + MOTION_BLUR_QUALITY_LOW = 4 samples + MOTION_BLUR_QUALITY_MEDIUM = 8 samples + MOTION_BLUR_QUALITY_HIGH = 16 samples + + + Defines a framerate to be used as reference by [member motion_blur_framerate_mode]. + + + + + Defines, in pixels, the size of velocity dilation tiles to be used by the motion blur. These tiles collect dominant velocities from neighboring tiles so that fast moving objects can be blurred beyond their original silhouettes. Changing the tile size has little performance cost, rather the cost and effect come in the form of detail vs range. Large tile sizes can blur objects further, but will do so in less detail. The sizes are: + MOTION_BLUR_TILE_SIZE_SMALL = 20 pixels + MOTION_BLUR_TILE_SIZE_MEDIUM = 40 pixels + MOTION_BLUR_TILE_SIZE_LARGE = 60 pixels + MOTION_BLUR_TILE_SIZE_EXTRA_LARGE = 80 pixels Disables [member rendering/driver/depth_prepass/enable] conditionally for certain vendors. By default, disables the depth prepass for mobile devices as mobile devices do not benefit from the depth prepass due to their unique architecture. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index ca96c9890bb..02ebf47888f 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -101,6 +101,45 @@ return log((aperture * aperture) / shutter_speed * (100.0 / sensitivity)) / log(2) [/codeblock] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5609,6 +5648,20 @@ Highest quality DOF blur. Results in the smoothest looking blur by taking the most samples, but is also significantly slower. + + + + + + + + + + + + + + The instance does not have a type. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 050212ad47f..a0eef080f2e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -464,6 +464,16 @@ void EditorNode::_update_from_settings() { scene_root->propagate_notification(Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED); } + RS::MotionBlurFramerateMode motion_blur_framerate_mode = RS::MotionBlurFramerateMode(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_framerate_mode"))); + int motion_blur_reference_framerate = GLOBAL_GET("rendering/camera/motion_blur/motion_blur_reference_framerate"); + RS::get_singleton()->camera_attributes_set_motion_blur_framerate_mode(motion_blur_framerate_mode, motion_blur_reference_framerate); + bool editor_mb_enabled = bool(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_show_in_editor")); + RS::get_singleton()->camera_attributes_set_motion_blur_show_in_editor(editor_mb_enabled); + RS::MotionBlurQuality motion_blur_quality = RS::MotionBlurQuality(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_quality"))); + RS::get_singleton()->camera_attributes_set_motion_blur_quality(motion_blur_quality); + RS::MotionBlurTileSize motion_blur_tile_size = RS::MotionBlurTileSize(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_tile_size"))); + RS::get_singleton()->camera_attributes_set_motion_blur_tile_size(motion_blur_tile_size); + RS::DOFBokehShape dof_shape = RS::DOFBokehShape(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_shape"))); RS::get_singleton()->camera_attributes_set_dof_blur_bokeh_shape(dof_shape); RS::DOFBlurQuality dof_quality = RS::DOFBlurQuality(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_quality"))); diff --git a/scene/resources/camera_attributes.cpp b/scene/resources/camera_attributes.cpp index 1c29e73224b..e0a8075fe8e 100644 --- a/scene/resources/camera_attributes.cpp +++ b/scene/resources/camera_attributes.cpp @@ -147,6 +147,91 @@ CameraAttributes::~CameraAttributes() { ////////////////////////////////////////////////////// /* CameraAttributesPractical */ +void CameraAttributesPractical::set_motion_blur_enabled(bool p_enabled) { + if (motion_blur_enabled == p_enabled) { + return; + } + motion_blur_enabled = p_enabled; + _update_motion_blur(); + notify_property_list_changed(); +} + +bool CameraAttributesPractical::is_motion_blur_enabled() const { + return motion_blur_enabled; +} + +void CameraAttributesPractical::set_motion_blur_intensity(float p_intensity) { + p_intensity = MAX(0.0f, p_intensity); + motion_blur_intensity = p_intensity; + _update_motion_blur(); +} + +float CameraAttributesPractical::get_motion_blur_intensity() const { + return motion_blur_intensity; +} + +void CameraAttributesPractical::set_motion_blur_clamp_velocities_to_tile(bool p_clamp_velocities_to_tile) { + if (motion_blur_clamp_velocities_to_tile == p_clamp_velocities_to_tile) { + return; + } + motion_blur_clamp_velocities_to_tile = p_clamp_velocities_to_tile; + _update_motion_blur(); +} + +bool CameraAttributesPractical::is_motion_blur_clamp_velocities_to_tile() const { + return motion_blur_clamp_velocities_to_tile; +} + +void CameraAttributesPractical::set_motion_blur_object_velocity_multiplier(float p_multiplier) { + p_multiplier = MAX(0.0f, p_multiplier); + motion_blur_object_velocity_multiplier = p_multiplier; + _update_motion_blur(); +} + +float CameraAttributesPractical::get_motion_blur_object_velocity_multiplier() const { + return motion_blur_object_velocity_multiplier; +} + +void CameraAttributesPractical::set_motion_blur_movement_velocity_multiplier(float p_multiplier) { + p_multiplier = MAX(0.0f, p_multiplier); + motion_blur_movement_velocity_multiplier = p_multiplier; + _update_motion_blur(); +} + +float CameraAttributesPractical::get_motion_blur_movement_velocity_multiplier() const { + return motion_blur_movement_velocity_multiplier; +} + +void CameraAttributesPractical::set_motion_blur_rotation_velocity_multiplier(float p_multiplier) { + p_multiplier = MAX(0.0f, p_multiplier); + motion_blur_rotation_velocity_multiplier = p_multiplier; + _update_motion_blur(); +} + +float CameraAttributesPractical::get_motion_blur_rotation_velocity_multiplier() const { + return motion_blur_rotation_velocity_multiplier; +} + +void CameraAttributesPractical::set_motion_blur_velocity_lower_threshold(float p_threshold) { + p_threshold = MAX(0.0f, p_threshold); + motion_blur_velocity_lower_threshold = p_threshold; + _update_motion_blur(); +} + +float CameraAttributesPractical::get_motion_blur_velocity_lower_threshold() const { + return motion_blur_velocity_lower_threshold; +} + +void CameraAttributesPractical::set_motion_blur_velocity_upper_threshold(float p_threshold) { + p_threshold = MAX(0.0f, p_threshold); + motion_blur_velocity_upper_threshold = p_threshold; + _update_motion_blur(); +} + +float CameraAttributesPractical::get_motion_blur_velocity_upper_threshold() const { + return motion_blur_velocity_upper_threshold; +} + void CameraAttributesPractical::set_dof_blur_far_enabled(bool p_enabled) { dof_blur_far_enabled = p_enabled; _update_dof_blur(); diff --git a/scene/resources/camera_attributes.h b/scene/resources/camera_attributes.h index 33acb089001..02e44073ed3 100644 --- a/scene/resources/camera_attributes.h +++ b/scene/resources/camera_attributes.h @@ -80,6 +80,17 @@ class CameraAttributesPractical : public CameraAttributes { GDCLASS(CameraAttributesPractical, CameraAttributes); private: + // Motion blur + bool motion_blur_enabled = false; + float motion_blur_intensity = 1.0; + bool motion_blur_clamp_velocities_to_tile = true; + float motion_blur_object_velocity_multiplier = 1.0; + float motion_blur_movement_velocity_multiplier = 1.0; + float motion_blur_rotation_velocity_multiplier = 1.0; + float motion_blur_velocity_lower_threshold = 0.0; + float motion_blur_velocity_upper_threshold = 0.0; + void _update_motion_blur(); + // DOF blur bool dof_blur_far_enabled = false; float dof_blur_far_distance = 10.0; @@ -99,6 +110,24 @@ class CameraAttributesPractical : public CameraAttributes { void _validate_property(PropertyInfo &p_property) const; public: + // Motion blur + void set_motion_blur_enabled(bool p_enabled); + bool is_motion_blur_enabled() const; + void set_motion_blur_intensity(float p_intensity); + float get_motion_blur_intensity() const; + void set_motion_blur_clamp_velocities_to_tile(bool p_clamp_velocities_to_tile); + bool is_motion_blur_clamp_velocities_to_tile() const; + void set_motion_blur_object_velocity_multiplier(float p_multiplier); + float get_motion_blur_object_velocity_multiplier() const; + void set_motion_blur_movement_velocity_multiplier(float p_multiplier); + float get_motion_blur_movement_velocity_multiplier() const; + void set_motion_blur_rotation_velocity_multiplier(float p_multiplier); + float get_motion_blur_rotation_velocity_multiplier() const; + void set_motion_blur_velocity_lower_threshold(float p_threshold); + float get_motion_blur_velocity_lower_threshold() const; + void set_motion_blur_velocity_upper_threshold(float p_threshold); + float get_motion_blur_velocity_upper_threshold() const; + // DOF blur void set_dof_blur_far_enabled(bool p_enabled); bool is_dof_blur_far_enabled() const; diff --git a/servers/rendering/renderer_rd/effects/motion_blur.cpp b/servers/rendering/renderer_rd/effects/motion_blur.cpp new file mode 100644 index 00000000000..31c0a9811f7 --- /dev/null +++ b/servers/rendering/renderer_rd/effects/motion_blur.cpp @@ -0,0 +1,315 @@ +/**************************************************************************/ +/* motion_blur.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "motion_blur.h" +#include "servers/rendering/renderer_rd/uniform_set_cache_rd.h" + +RendererRD::MotionBlur::MotionBlur(RS::MotionBlurTileSize p_tile_size_level) { + // Init tile size (changes require restart) + switch (p_tile_size_level) { + case RS::MOTION_BLUR_TILE_SIZE_SMALL: + tile_size = 20; + break; + case RS::MOTION_BLUR_TILE_SIZE_MEDIUM: + tile_size = 40; + break; + case RS::MOTION_BLUR_TILE_SIZE_LARGE: + tile_size = 60; + break; + case RS::MOTION_BLUR_TILE_SIZE_EXTRA_LARGE: + tile_size = 80; + break; + default: + WARN_PRINT_ONCE("Unknown motion blur tile size."); + tile_size = 40; + break; + } + + RD::SamplerState sampler; + sampler.mag_filter = RD::SAMPLER_FILTER_NEAREST; + sampler.min_filter = RD::SAMPLER_FILTER_NEAREST; + sampler.mip_filter = RD::SAMPLER_FILTER_NEAREST; + sampler.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + sampler.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + sampler.repeat_w = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE; + motion_blur.nearest_sampler = RD::get_singleton()->sampler_create(sampler); + + sampler.mag_filter = RD::SAMPLER_FILTER_LINEAR; + sampler.min_filter = RD::SAMPLER_FILTER_LINEAR; + sampler.mip_filter = RD::SAMPLER_FILTER_LINEAR; + motion_blur.linear_sampler = RD::get_singleton()->sampler_create(sampler); + + // Use macros to define TILE_SIZE to enable loop unrolling. + // This improves runtime performance significantly. + Vector tile_size_defs; + tile_size_defs.push_back(vformat("\n#define TILE_SIZE %d\n", tile_size)); + + motion_blur.preprocess_shader.initialize({ "\n" }); + motion_blur.preprocess_shader_version = motion_blur.preprocess_shader.version_create(); + motion_blur.pipelines[MOTION_BLUR_PREPROCESS].create_compute_pipeline(motion_blur.preprocess_shader.version_get_shader(motion_blur.preprocess_shader_version, 0)); + + motion_blur.tile_max_x_shader.initialize(tile_size_defs); + motion_blur.tile_max_x_shader_version = motion_blur.tile_max_x_shader.version_create(); + motion_blur.pipelines[MOTION_BLUR_TILE_MAX_X].create_compute_pipeline(motion_blur.tile_max_x_shader.version_get_shader(motion_blur.tile_max_x_shader_version, 0)); + + motion_blur.tile_max_y_shader.initialize(tile_size_defs); + motion_blur.tile_max_y_shader_version = motion_blur.tile_max_y_shader.version_create(); + motion_blur.pipelines[MOTION_BLUR_TILE_MAX_Y].create_compute_pipeline(motion_blur.tile_max_y_shader.version_get_shader(motion_blur.tile_max_y_shader_version, 0)); + + motion_blur.neighbor_max_shader.initialize({ "\n" }); + motion_blur.neighbor_max_shader_version = motion_blur.neighbor_max_shader.version_create(); + motion_blur.pipelines[MOTION_BLUR_NEIGHBOR_MAX].create_compute_pipeline(motion_blur.neighbor_max_shader.version_get_shader(motion_blur.neighbor_max_shader_version, 0)); + + motion_blur.blur_shader.initialize(tile_size_defs); + motion_blur.blur_shader_version = motion_blur.blur_shader.version_create(); + motion_blur.pipelines[MOTION_BLUR_BLUR].create_compute_pipeline(motion_blur.blur_shader.version_get_shader(motion_blur.blur_shader_version, 0)); +} + +RendererRD::MotionBlur::~MotionBlur() { + for (int i = 0; i < MOTION_BLUR_MAX; i++) { + motion_blur.pipelines[i].free(); + } + + motion_blur.preprocess_shader.version_free(motion_blur.preprocess_shader_version); + motion_blur.tile_max_x_shader.version_free(motion_blur.tile_max_x_shader_version); + motion_blur.tile_max_y_shader.version_free(motion_blur.tile_max_y_shader_version); + motion_blur.neighbor_max_shader.version_free(motion_blur.neighbor_max_shader_version); + motion_blur.blur_shader.version_free(motion_blur.blur_shader_version); + + RD::get_singleton()->free_rid(motion_blur.nearest_sampler); + RD::get_singleton()->free_rid(motion_blur.linear_sampler); +} + +void RendererRD::MotionBlur::motion_blur_process(const MotionBlurBuffers &p_buffers) { + UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton(); + RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); + + RD::get_singleton()->draw_command_begin_label("Preprocess motion vectors"); + + RID shader = motion_blur.preprocess_shader.version_get_shader(motion_blur.preprocess_shader_version, 0); + ERR_FAIL_COND(shader.is_null()); + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, motion_blur.pipelines[MOTION_BLUR_PREPROCESS].get_rid()); + + { + RD::Uniform depth_texture_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, { motion_blur.nearest_sampler, p_buffers.depth_texture }); + RD::Uniform velocity_texture_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 1, { motion_blur.nearest_sampler, p_buffers.velocity_texture }); + RD::Uniform custom_velocity_image = RD::Uniform(RD::UNIFORM_TYPE_IMAGE, 2, p_buffers.custom_velocity_texture); + RD::Uniform scene_data_uniform = RD::Uniform(RD::UNIFORM_TYPE_UNIFORM_BUFFER, 5, p_buffers.scene_data_uniform); + + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, + uniform_set_cache->get_cache(shader, 0, depth_texture_uniform, velocity_texture_uniform, custom_velocity_image, scene_data_uniform), 0); + + RD::get_singleton()->compute_list_set_push_constant(compute_list, &motion_blur.preprocess_push_constant, sizeof(MotionBlurPreprocessPushConstant)); + } + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_buffers.base_size.x, p_buffers.base_size.y, 1); + RD::get_singleton()->compute_list_add_barrier(compute_list); + + RD::get_singleton()->draw_command_end_label(); + RD::get_singleton()->draw_command_begin_label("Motion blur"); + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, motion_blur.pipelines[MOTION_BLUR_TILE_MAX_X].get_rid()); + + shader = motion_blur.tile_max_x_shader.version_get_shader(motion_blur.tile_max_x_shader_version, 0); + ERR_FAIL_COND(shader.is_null()); + + { + RD::Uniform custom_velocity_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, { motion_blur.nearest_sampler, p_buffers.custom_velocity_texture }); + RD::Uniform depth_texture_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 1, { motion_blur.nearest_sampler, p_buffers.depth_texture }); + RD::Uniform tile_max_x_image = RD::Uniform(RD::UNIFORM_TYPE_IMAGE, 2, p_buffers.tile_max_x_texture); + + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, + uniform_set_cache->get_cache(shader, 0, custom_velocity_uniform, depth_texture_uniform, tile_max_x_image), 0); + + // Clear push constant + RD::get_singleton()->compute_list_set_push_constant(compute_list, nullptr, 0); + } + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_buffers.tiled_size.x, p_buffers.base_size.y, 1); + RD::get_singleton()->compute_list_add_barrier(compute_list); + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, motion_blur.pipelines[MOTION_BLUR_TILE_MAX_Y].get_rid()); + + shader = motion_blur.tile_max_y_shader.version_get_shader(motion_blur.tile_max_y_shader_version, 0); + ERR_FAIL_COND(shader.is_null()); + + { + RD::Uniform tile_max_x_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, { motion_blur.nearest_sampler, p_buffers.tile_max_x_texture }); + RD::Uniform tile_max_y_image = RD::Uniform(RD::UNIFORM_TYPE_IMAGE, 1, p_buffers.tile_max_y_texture); + + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, + uniform_set_cache->get_cache(shader, 0, tile_max_x_uniform, tile_max_y_image), 0); + } + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_buffers.tiled_size.x, p_buffers.tiled_size.y, 1); + RD::get_singleton()->compute_list_add_barrier(compute_list); + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, motion_blur.pipelines[MOTION_BLUR_NEIGHBOR_MAX].get_rid()); + + shader = motion_blur.neighbor_max_shader.version_get_shader(motion_blur.neighbor_max_shader_version, 0); + ERR_FAIL_COND(shader.is_null()); + + { + RD::Uniform tile_max_y_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, { motion_blur.nearest_sampler, p_buffers.tile_max_y_texture }); + RD::Uniform neighbor_max_image = RD::Uniform(RD::UNIFORM_TYPE_IMAGE, 1, p_buffers.neighbor_max_texture); + + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, + uniform_set_cache->get_cache(shader, 0, tile_max_y_uniform, neighbor_max_image), 0); + } + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_buffers.tiled_size.x, p_buffers.tiled_size.y, 1); + RD::get_singleton()->compute_list_add_barrier(compute_list); + + RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, motion_blur.pipelines[MOTION_BLUR_BLUR].get_rid()); + + shader = motion_blur.blur_shader.version_get_shader(motion_blur.blur_shader_version, 0); + ERR_FAIL_COND(shader.is_null()); + + { + RD::Uniform color_texture_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, { motion_blur.nearest_sampler, p_buffers.base_texture }); + RD::Uniform custom_velocity_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 1, { motion_blur.nearest_sampler, p_buffers.custom_velocity_texture }); + RD::Uniform neighbor_max_uniform = RD::Uniform(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 2, { motion_blur.nearest_sampler, p_buffers.neighbor_max_texture }); + RD::Uniform output_image = RD::Uniform(RD::UNIFORM_TYPE_IMAGE, 3, p_buffers.blur_output_texture); + + RD::get_singleton()->compute_list_bind_uniform_set(compute_list, + uniform_set_cache->get_cache(shader, 0, color_texture_uniform, custom_velocity_uniform, neighbor_max_uniform, output_image), 0); + + RD::get_singleton()->compute_list_set_push_constant(compute_list, &motion_blur.blur_push_constant, sizeof(MotionBlurBlurPushConstant)); + } + + RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_buffers.base_size.x, p_buffers.base_size.y, 1); + RD::get_singleton()->compute_list_add_barrier(compute_list); + + RD::get_singleton()->compute_list_end(); +} + +void RendererRD::MotionBlur::motion_blur_compute(Ref p_render_buffers, RID p_camera_attributes, RenderSceneDataRD *p_scene_data, bool transparent_bg, float time_step, CopyEffects *p_copy_effects) { + Size2i base_size = p_render_buffers->get_internal_size(); + Size2i tiled_size = Size2i(Math::division_round_up(base_size.width, tile_size), Math::division_round_up(base_size.height, tile_size)); + uint32_t view_count = p_render_buffers->get_view_count(); + + if (!p_render_buffers->has_texture(RB_SCOPE_MOTION_BLUR, RB_TEX_BLUR_OUTPUT)) { + int usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT; + + p_render_buffers->create_texture(RB_SCOPE_MOTION_BLUR, RB_TEX_CUSTOM_VELOCITY, RD::DATA_FORMAT_R16G16B16A16_SFLOAT, usage_bits, RD::TEXTURE_SAMPLES_1, base_size); + p_render_buffers->create_texture(RB_SCOPE_MOTION_BLUR, RB_TEX_TILE_MAX_X, RD::DATA_FORMAT_R16G16_SFLOAT, usage_bits, RD::TEXTURE_SAMPLES_1, Size2i(tiled_size.x, base_size.y)); + p_render_buffers->create_texture(RB_SCOPE_MOTION_BLUR, RB_TEX_TILE_MAX_Y, RD::DATA_FORMAT_R16G16_SFLOAT, usage_bits, RD::TEXTURE_SAMPLES_1, tiled_size); + p_render_buffers->create_texture(RB_SCOPE_MOTION_BLUR, RB_TEX_NEIGHBOR_MAX, RD::DATA_FORMAT_R16G16_SFLOAT, usage_bits, RD::TEXTURE_SAMPLES_1, tiled_size); + p_render_buffers->create_texture(RB_SCOPE_MOTION_BLUR, RB_TEX_BLUR_OUTPUT, RD::DATA_FORMAT_R16G16B16A16_SFLOAT, usage_bits, RD::TEXTURE_SAMPLES_1, base_size); + + // Skip first frame, without a previous frame we cannot compute the camera velocity. + return; + } + + MotionBlurBuffers buffers; + buffers.base_size = base_size; + buffers.tiled_size = tiled_size; + + { + float intensity = RSG::camera_attributes->camera_attributes_get_motion_blur_intensity(p_camera_attributes); + int reference_framerate = RSG::camera_attributes->camera_attributes_get_motion_blur_reference_framerate(); + switch (RSG::camera_attributes->camera_attributes_get_motion_blur_framerate_mode()) { + case RenderingServer::MOTION_BLUR_FRAMERATE_MODE_NATIVE: + // Use raw intensity, ignore frame time + break; + case RenderingServer::MOTION_BLUR_FRAMERATE_MODE_CAPPED: + intensity *= MIN(1.f / reference_framerate, time_step) / time_step; + break; + case RenderingServer::MOTION_BLUR_FRAMERATE_MODE_FIXED: + // Scale intensity by frame time + intensity /= reference_framerate * time_step; + break; + } + + int sample_count; + switch (RSG::camera_attributes->camera_attributes_get_motion_blur_quality()) { + case RenderingServer::MOTION_BLUR_QUALITY_LOW: + sample_count = 4; + break; + case RenderingServer::MOTION_BLUR_QUALITY_MEDIUM: + sample_count = 8; + break; + case RenderingServer::MOTION_BLUR_QUALITY_HIGH: + sample_count = 16; + break; + default: + WARN_PRINT_ONCE("Unknown motion blur quality setting, defaulting to medium."); + sample_count = 8; + break; + } + + bool clamp_velocities_to_tile = RSG::camera_attributes->camera_attributes_get_motion_blur_clamp_velocities_to_tile(p_camera_attributes); + float velocity_lower_threshold = RSG::camera_attributes->camera_attributes_get_motion_blur_velocity_lower_threshold(p_camera_attributes); + float velocity_upper_threshold = RSG::camera_attributes->camera_attributes_get_motion_blur_velocity_upper_threshold(p_camera_attributes); + + // TODO: add these multipliers to settings + motion_blur.preprocess_push_constant.movement_velocity_multiplier = RSG::camera_attributes->camera_attributes_get_motion_blur_movement_velocity_multiplier(p_camera_attributes); + motion_blur.preprocess_push_constant.rotation_velocity_multiplier = RSG::camera_attributes->camera_attributes_get_motion_blur_rotation_velocity_multiplier(p_camera_attributes); + motion_blur.preprocess_push_constant.object_velocity_multiplier = RSG::camera_attributes->camera_attributes_get_motion_blur_object_velocity_multiplier(p_camera_attributes); + motion_blur.preprocess_push_constant.rotation_velocity_lower_threshold = velocity_lower_threshold; + motion_blur.preprocess_push_constant.rotation_velocity_upper_threshold = velocity_upper_threshold; + motion_blur.preprocess_push_constant.movement_velocity_lower_threshold = velocity_lower_threshold; + motion_blur.preprocess_push_constant.movement_velocity_upper_threshold = velocity_upper_threshold; + motion_blur.preprocess_push_constant.object_velocity_lower_threshold = velocity_lower_threshold; + motion_blur.preprocess_push_constant.object_velocity_upper_threshold = velocity_upper_threshold; + motion_blur.preprocess_push_constant.motion_blur_intensity = intensity; + motion_blur.preprocess_push_constant.support_fsr2 = 1.0f; + + motion_blur.blur_push_constant.motion_blur_intensity = intensity; + motion_blur.blur_push_constant.sample_count = sample_count; + motion_blur.blur_push_constant.frame = Engine::get_singleton()->get_frames_drawn() % 8; + motion_blur.blur_push_constant.clamp_velocities_to_tile = clamp_velocities_to_tile ? 1 : 0; + motion_blur.blur_push_constant.transparent_bg = transparent_bg ? 1 : 0; + } + + buffers.scene_data_uniform = p_scene_data->get_uniform_buffer(); + + RD::get_singleton()->draw_command_begin_label("Motion blur"); + for (uint32_t v = 0; v < view_count; v++) { + buffers.base_texture = p_render_buffers->get_internal_texture(v); + buffers.depth_texture = p_render_buffers->get_depth_texture(v); + buffers.velocity_texture = p_render_buffers->get_velocity_buffer(false, v); + + buffers.custom_velocity_texture = p_render_buffers->get_texture_slice(RB_SCOPE_MOTION_BLUR, RB_TEX_CUSTOM_VELOCITY, v, 0); + buffers.tile_max_x_texture = p_render_buffers->get_texture_slice(RB_SCOPE_MOTION_BLUR, RB_TEX_TILE_MAX_X, v, 0); + buffers.tile_max_y_texture = p_render_buffers->get_texture_slice(RB_SCOPE_MOTION_BLUR, RB_TEX_TILE_MAX_Y, v, 0); + buffers.neighbor_max_texture = p_render_buffers->get_texture_slice(RB_SCOPE_MOTION_BLUR, RB_TEX_NEIGHBOR_MAX, v, 0); + buffers.blur_output_texture = p_render_buffers->get_texture_slice(RB_SCOPE_MOTION_BLUR, RB_TEX_BLUR_OUTPUT, v, 0); + + motion_blur_process(buffers); + // Pong the blurred texture back to the internal texture + p_copy_effects->copy_to_rect(buffers.blur_output_texture, buffers.base_texture, Rect2i(Point2i(), base_size)); + } + + RD::get_singleton()->draw_command_end_label(); +} \ No newline at end of file diff --git a/servers/rendering/renderer_rd/effects/motion_blur.h b/servers/rendering/renderer_rd/effects/motion_blur.h new file mode 100644 index 00000000000..ab474274041 --- /dev/null +++ b/servers/rendering/renderer_rd/effects/motion_blur.h @@ -0,0 +1,143 @@ +/**************************************************************************/ +/* motion_blur.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "copy_effects.h" +#include "servers/rendering/renderer_rd/pipeline_cache_rd.h" +#include "servers/rendering/renderer_rd/pipeline_deferred_rd.h" +#include "servers/rendering/renderer_rd/shaders/effects/motion_blur_blur.glsl.gen.h" +#include "servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl.gen.h" +#include "servers/rendering/renderer_rd/shaders/effects/motion_blur_preprocess.glsl.gen.h" +#include "servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl.gen.h" +#include "servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl.gen.h" +#include "servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h" +#include "servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h" + +#define RB_SCOPE_MOTION_BLUR SNAME("motion_blur") + +#define RB_TEX_CUSTOM_VELOCITY SNAME("custom_velocity") +#define RB_TEX_TILE_MAX_X SNAME("tile_max_x") +#define RB_TEX_TILE_MAX_Y SNAME("tile_max_y") +#define RB_TEX_NEIGHBOR_MAX SNAME("neighbor_max") +#define RB_TEX_BLUR_OUTPUT SNAME("blur_output") + +namespace RendererRD { + +class MotionBlur { +private: + enum MotionBlurMode { + MOTION_BLUR_PREPROCESS, + MOTION_BLUR_TILE_MAX_X, + MOTION_BLUR_TILE_MAX_Y, + MOTION_BLUR_NEIGHBOR_MAX, + MOTION_BLUR_BLUR, + MOTION_BLUR_MAX, + }; + + struct MotionBlurPreprocessPushConstant { + float rotation_velocity_multiplier; + float movement_velocity_multiplier; + float object_velocity_multiplier; + float rotation_velocity_lower_threshold; + + float movement_velocity_lower_threshold; + float object_velocity_lower_threshold; + float rotation_velocity_upper_threshold; + float movement_velocity_upper_threshold; + + float object_velocity_upper_threshold; + float support_fsr2; + float motion_blur_intensity; + float pad; + }; + + struct MotionBlurBlurPushConstant { + float motion_blur_intensity; + int32_t sample_count; + int32_t frame; + int32_t clamp_velocities_to_tile; + int32_t transparent_bg; + int32_t pad[3]; + }; + + struct { + MotionBlurPreprocessPushConstant preprocess_push_constant; + MotionBlurBlurPushConstant blur_push_constant; + + MotionBlurPreprocessShaderRD preprocess_shader; + RID preprocess_shader_version; + + MotionBlurTileMaxXShaderRD tile_max_x_shader; + RID tile_max_x_shader_version; + + MotionBlurTileMaxYShaderRD tile_max_y_shader; + RID tile_max_y_shader_version; + + MotionBlurNeighborMaxShaderRD neighbor_max_shader; + RID neighbor_max_shader_version; + + MotionBlurBlurShaderRD blur_shader; + RID blur_shader_version; + + PipelineDeferredRD pipelines[MOTION_BLUR_MAX]; + RID linear_sampler; + RID nearest_sampler; + } motion_blur; + + struct MotionBlurBuffers { + Size2i base_size; + Size2i tiled_size; + + RID scene_data_uniform; + + // Textures and images + RID base_texture; + RID depth_texture; + RID velocity_texture; + RID custom_velocity_texture; + RID tile_max_x_texture; + RID tile_max_y_texture; + RID neighbor_max_texture; + RID blur_output_texture; + }; + + int tile_size; + void motion_blur_process(const MotionBlurBuffers &p_buffers); + +public: + MotionBlur(RS::MotionBlurTileSize p_tile_size_level); + ~MotionBlur(); + + void motion_blur_compute(Ref p_render_buffers, RID p_camera_attributes, RenderSceneDataRD *p_scene_data, bool transparent_bg, float time_step, CopyEffects *p_copy_effects); +}; +} //namespace RendererRD \ No newline at end of file diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 48fe352d57f..c05d13180ec 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1752,11 +1752,14 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } bool using_upscaling = scale_type != SCALE_NONE; + bool using_motion_blur = RSG::camera_attributes->camera_attributes_uses_motion_blur(p_render_data->camera_attributes); // check if we need motion vectors bool motion_vectors_required; if (using_debug_mvs) { motion_vectors_required = true; + } else if (using_motion_blur) { + motion_vectors_required = true; } else if (ce_needs_motion_vectors) { motion_vectors_required = true; } else if (!is_reflection_probe && using_taa) { @@ -2390,7 +2393,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co RD::get_singleton()->draw_command_begin_label("Resolve"); if (rb_data.is_valid() && use_msaa) { - bool resolve_velocity_buffer = (using_taa || using_upscaling || ce_needs_motion_vectors) && rb->has_velocity_buffer(true); + bool resolve_velocity_buffer = (using_taa || using_motion_blur || using_upscaling || ce_needs_motion_vectors) && rb->has_velocity_buffer(true); for (uint32_t v = 0; v < rb->get_view_count(); v++) { RD::get_singleton()->texture_resolve_multisample(rb->get_color_msaa(v), rb->get_internal_texture(v)); resolve_effects->resolve_depth(rb->get_depth_msaa(v), rb->get_depth_texture(v), rb->get_internal_size(), texture_multisamples[msaa]); diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index f91e716649d..f4364b986d3 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -34,6 +34,7 @@ #include "core/config/project_settings.h" #include "core/io/image.h" +#include "effects/motion_blur.h" #include "renderer_compositor_rd.h" #include "servers/rendering/renderer_rd/environment/fog.h" #include "servers/rendering/renderer_rd/shaders/decal_data_inc.glsl.gen.h" @@ -542,6 +543,23 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const Rende RD::get_singleton()->draw_command_end_label(); } + bool using_motion_blur = RSG::camera_attributes->camera_attributes_uses_motion_blur(p_render_data->camera_attributes); + + if (using_motion_blur && !can_use_storage) { + WARN_PRINT_ONCE("Motion blur requires storage support in shader. Disabling motion blur."); + using_motion_blur = false; + } + + if (using_motion_blur && !RSG::camera_attributes->camera_attributes_get_motion_blur_show_in_editor() && Engine::get_singleton()->is_editor_hint()) { + using_motion_blur = false; + } + + if (can_use_effects && using_motion_blur) { + RENDER_TIMESTAMP("Motion Blur"); + motion_blur->motion_blur_compute(rb, p_render_data->camera_attributes, p_render_data->scene_data, p_render_data->transparent_bg, time_step, copy_effects); + } + + float auto_exposure_scale = 1.0; if (can_use_effects && RSG::camera_attributes->camera_attributes_uses_auto_exposure(p_render_data->camera_attributes)) { @@ -1680,6 +1698,11 @@ void RendererSceneRenderRD::init() { RendererRD::Fog::get_singleton()->init_fog_shader(RendererRD::LightStorage::get_singleton()->get_max_directional_lights(), get_roughness_layers(), is_using_radiance_cubemap_array()); } + RSG::camera_attributes->camera_attributes_set_motion_blur_framerate_mode(RS::MotionBlurFramerateMode(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_framerate_mode"))), int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_reference_framerate"))); + RSG::camera_attributes->camera_attributes_set_motion_blur_show_in_editor(bool(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_show_in_editor"))); + RSG::camera_attributes->camera_attributes_set_motion_blur_quality(RS::MotionBlurQuality(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_quality")))); + RSG::camera_attributes->camera_attributes_set_motion_blur_tile_size(RS::MotionBlurTileSize(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_tile_size")))); + RSG::camera_attributes->camera_attributes_set_dof_blur_bokeh_shape(RS::DOFBokehShape(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_shape")))); RSG::camera_attributes->camera_attributes_set_dof_blur_quality(RS::DOFBlurQuality(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_quality"))), GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_use_jitter")); use_physical_light_units = GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units"); @@ -1708,6 +1731,7 @@ void RendererSceneRenderRD::init() { bool can_use_storage = _render_buffers_can_be_storage(); bool can_use_vrs = is_vrs_supported(); bokeh_dof = memnew(RendererRD::BokehDOF(!can_use_storage)); + motion_blur = memnew(RendererRD::MotionBlur(RSG::camera_attributes->camera_attributes_get_motion_blur_tile_size())); copy_effects = memnew(RendererRD::CopyEffects(!can_use_storage)); debug_effects = memnew(RendererRD::DebugEffects); luminance = memnew(RendererRD::Luminance(!can_use_storage)); @@ -1732,6 +1756,9 @@ RendererSceneRenderRD::~RendererSceneRenderRD() { if (bokeh_dof) { memdelete(bokeh_dof); } + if (motion_blur) { + memdelete(motion_blur); + } if (copy_effects) { memdelete(copy_effects); } diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index 8449fe89e7a..40cbb96df52 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -54,6 +54,8 @@ #include "servers/rendering/rendering_method.h" #include "servers/rendering/rendering_shader_library.h" +#include "effects/motion_blur.h" + class RendererSceneRenderRD : public RendererSceneRender, public RenderingShaderLibrary { friend RendererRD::SkyRD; friend RendererRD::GI; @@ -61,6 +63,7 @@ class RendererSceneRenderRD : public RendererSceneRender, public RenderingShader protected: RendererRD::ForwardIDStorage *forward_id_storage = nullptr; RendererRD::BokehDOF *bokeh_dof = nullptr; + RendererRD::MotionBlur *motion_blur = nullptr; RendererRD::CopyEffects *copy_effects = nullptr; RendererRD::DebugEffects *debug_effects = nullptr; RendererRD::Luminance *luminance = nullptr; diff --git a/servers/rendering/renderer_rd/shaders/effects/motion_blur_blur.glsl b/servers/rendering/renderer_rd/shaders/effects/motion_blur_blur.glsl new file mode 100644 index 00000000000..904506e80d7 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/effects/motion_blur_blur.glsl @@ -0,0 +1,262 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2025 sphynx-owner + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/////////////////////////////////////////////////////////////////////////////////// +// File changes (yyyy-mm-dd) +// 2025-01-11: sphynx: first commit +// 2026-01-16: HydrogenC: make tile size specification constant and simplify push constant +// 2026-01-18: AR-DEV-1: add missing t in overlapn +/////////////////////////////////////////////////////////////////////////////////// +// Original file link: https://github.com/sphynx-owner/godot-motion-blur-addon-simplified/blob/master/addons/sphynx_motion_blur_toolkit/guertin/shader_stages/shader_files/guertin_sphynx_blur.glsl + +#[compute] +#version 450 + +#VERSION_DEFINES + +#define FLT_MAX 3.402823466e+38 +#define FLT_MIN 1.175494351e-38 +#define M_PI 3.1415926535897932384626433832795 + +layout(set = 0, binding = 0) uniform sampler2D color_sampler; +layout(set = 0, binding = 1) uniform sampler2D velocity_sampler; +layout(set = 0, binding = 2) uniform sampler2D neighbor_max; +layout(rgba16f, set = 0, binding = 3) uniform writeonly image2D output_color; + +layout(push_constant, std430) uniform Params { + float motion_blur_intensity; + int sample_count; + int frame; + int clamp_velocities_to_tile; + int transparent_bg; + int pad1; + int pad2; + int pad3; +} +params; + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +// Guertin's functions https://research.nvidia.com/sites/default/files/pubs/2013-11_A-Fast-and/Guertin2013MotionBlur-small.pdf +// ---------------------------------------------------------- +float soft_compare(float a, float b, float sze) { + return clamp(sze * (a - b), 0, 1); +} +// ---------------------------------------------------------- + +// from https://www.shadertoy.com/view/ftKfzc +// ---------------------------------------------------------- +float interleaved_gradient_noise(vec2 uv) { + uv += float(params.frame) * (vec2(47, 17) * 0.695); + + vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189); + + return fract(magic.z * fract(dot(uv, magic.xy))); +} +// ---------------------------------------------------------- + +// from https://github.com/bradparks/KinoMotion__unity_motion_blur/tree/master +// ---------------------------------------------------------- +vec2 safenorm(vec2 v) { + float l = max(length(v), 1e-6); + return v / l * int(l >= 0.5); +} + +vec2 jitter_tile(vec2 uvi) { + float rx, ry; + float angle = interleaved_gradient_noise(uvi + vec2(2, 0)) * M_PI * 2; + rx = cos(angle); + ry = sin(angle); + return vec2(rx, ry) / textureSize(neighbor_max, 0) / 4; +} +// ---------------------------------------------------------- + +vec4 sample_velocity(sampler2D velocity_texture, vec2 uv) { + return textureLod(velocity_texture, uv, 0.0) * vec4(vec2(params.motion_blur_intensity), 1, 1); +} + +vec4 sample_x_velocity(vec2 x, float t, vec2 vx, float z, float zx, ivec2 render_size, out float x_weight) { + vec2 yx = x + t * vx / vec2(render_size); + + vec4 vyzwx = sample_velocity(velocity_sampler, yx); + + float zyx = vyzwx.w; + + x_weight = 1 - soft_compare(z + zx * t, zyx, -10); + + return textureLod(color_sampler, yx, 0.0); +} + +vec4 sample_y_velocity(vec2 x, float t, vec2 vn, vec2 wn, float z, ivec2 render_size, out float y_weight) { + vec2 yn = x + t * vn / vec2(render_size); + + vec4 vyzwn = sample_velocity(velocity_sampler, yn); + + vec2 vyn = vyzwn.xy; + + float zyn = vyzwn.w; + + float overlapn = 1 - soft_compare(zyn - vyzwn.z * t, z, -10); + + vec2 wyn = safenorm(vyn); + + float Tn = abs(t * length(vn)); + + float vyn_length = max(0.5, length(vyn)); + + if (params.clamp_velocities_to_tile == 1) { + float clamp_ratio = max(vyn_length / TILE_SIZE, 1.0); + vyn /= clamp_ratio; + vyn_length /= clamp_ratio; + } + + float projected = abs(dot(wyn, wn)); + + y_weight = step(Tn, vyn_length * projected) * overlapn; + + return textureLod(color_sampler, yn, 0.0); +} + +void blend_blur( + vec4 base_color, + vec4 x_sample, + float x_weight, + vec4 neg_x_sample, + float neg_x_weight, + vec4 y_sample, + float y_weight, + float weight_modifier, + inout vec4 color_sum, + inout float color_weight, + inout float alpha_weight) { + float current_weight_x = max(x_weight, neg_x_weight); + + vec4 x_color_sample = mix(neg_x_sample, x_sample, clamp(x_weight / neg_x_weight, 0, 1)); + + vec4 current_color = mix(mix(base_color, x_color_sample, current_weight_x), y_sample, y_weight); + + float current_color_weight = weight_modifier * max(current_color.a, 1 - params.transparent_bg); + + float current_alpha_weight = weight_modifier; + + color_sum += vec4(current_color.xyz * current_color_weight, current_color.a * current_alpha_weight); + + color_weight += current_color_weight; + + alpha_weight += current_alpha_weight; +} + +void main() { + // The size of the output texture + ivec2 render_size = ivec2(textureSize(color_sampler, 0)); + + // The pixel we are running the shader for. + ivec2 uvi = ivec2(gl_GlobalInvocationID.xy); + + // If the pixel we are in is outside the target render's size, we + // exit early + if ((uvi.x >= render_size.x) || (uvi.y >= render_size.y)) { + return; + } + + // We convert the pixel position into a texturing sampling position + // we add 0.5 to offset the sampling to be in the "middle" of the pixel + // and avoid artifacts caused by bilinear interpolation. + vec2 x = (vec2(uvi) + vec2(0.5)) / vec2(render_size); + + // We get the neighbor-max velocity for the tile we are in, with some jitter + // between tiles to hide seams between them. + vec4 vnzw = sample_velocity(neighbor_max, x + jitter_tile(uvi)); + + vec2 vn = vnzw.xy; + + float vn_length = length(vn); + + vec4 base_color = textureLod(color_sampler, x, 0.0); + + // We get the true velocity at the current pixel + vec4 vxzw = sample_velocity(velocity_sampler, x); + + vec2 vx = vxzw.xy; + + float vx_length = length(vx); + + if (params.clamp_velocities_to_tile == 1) { + float clamp_ratio = max(vn_length / TILE_SIZE, 1.0); + vn /= clamp_ratio; + vn_length /= clamp_ratio; + + clamp_ratio = max(vx_length / TILE_SIZE, 1.0); + vx /= clamp_ratio; + vx_length /= clamp_ratio; + } + + // We must account for cases where the dominant velocity is 0 even though + // The current velocity is not. This is only the case for the skybox, which + // Will never overlap geometry so it can safely be ignored when calculating neighbor_max + if (vn_length < 0.5) { + imageStore(output_color, uvi, base_color); + return; + } + + // We normalize neighbor-max velocity + vec2 wn = safenorm(vn); + + // Get the depth at current pixel + float zx = vxzw.w; + + // We get some random value for the current pixel between 0 and 1. This will be used to + // jitter the blur sampling, and achieve smoother looking blur gradient + // with a fraction of the sample count. + float j = interleaved_gradient_noise(uvi); + + float color_weight = 1e-6; + + float alpha_weight = 1e-6; + + // Create an initial color sum + vec4 sum = vec4(base_color.xyx * base_color.a * color_weight, base_color.a * alpha_weight); + + for (int i = 0; i < params.sample_count; i++) { + float ti = (i + j) / params.sample_count; + + // A point in time along the blur interval, used to scale velocity vectors to sample for color. + float t = mix(-0.5, 0, ti); + float neg_t = -t; + float current_total_weight = 1; + + float x_weight; + vec4 x_sample = sample_x_velocity(x, t, vx, zx, vxzw.z, render_size, x_weight); + float neg_x_weight; + vec4 neg_x_sample = sample_x_velocity(x, neg_t, vx, zx, vxzw.z, render_size, neg_x_weight); + + float y_weight; + vec4 y_sample = sample_y_velocity(x, t, vn, wn, zx, render_size, y_weight); + float neg_y_weight; + vec4 neg_y_sample = sample_y_velocity(x, -t, vn, wn, zx, render_size, neg_y_weight); + blend_blur(base_color, x_sample, x_weight, neg_x_sample, neg_x_weight, y_sample, y_weight, current_total_weight, sum, color_weight, alpha_weight); + blend_blur(base_color, neg_x_sample, neg_x_weight, x_sample, x_weight, neg_y_sample, neg_y_weight, current_total_weight, sum, color_weight, alpha_weight); + } + + sum.xyz /= color_weight; + sum.a /= alpha_weight; + + imageStore(output_color, uvi, sum); +} \ No newline at end of file diff --git "a/servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl\342\200\216" "b/servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl\342\200\216" new file mode 100644 index 00000000000..3c263f0879c --- /dev/null +++ "b/servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl\342\200\216" @@ -0,0 +1,82 @@ + +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2025 sphynx-owner + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/////////////////////////////////////////////////////////////////////////////////// +// File changes (yyyy-mm-dd) +// 2025-01-11: sphynx: first commit +// 2026-01-16: HydrogenC: make tile size specification constant and simplify push constant +/////////////////////////////////////////////////////////////////////////////////// +// Original file link: https://github.com/sphynx-owner/godot-motion-blur-addon-simplified/blob/master/addons/sphynx_motion_blur_toolkit/guertin/shader_stages/shader_files/guertin_neighbor_max.glsl + +#[compute] +#version 450 + +#VERSION_DEFINES + +#define FLT_MAX 3.402823466e+38 +#define FLT_MIN 1.175494351e-38 + +layout(set = 0, binding = 0) uniform sampler2D tile_max; +layout(rgba16f, set = 0, binding = 1) uniform writeonly image2D neighbor_max; + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +void main() { + ivec2 render_size = ivec2(textureSize(tile_max, 0)); + ivec2 uvi = ivec2(gl_GlobalInvocationID.xy); + if ((uvi.x >= render_size.x) || (uvi.y >= render_size.y)) { + return; + } + + vec2 uvn = (vec2(uvi) + vec2(0.5)) / render_size; + + vec2 max_neighbor_velocity = vec2(0); + + float max_neighbor_velocity_length = 0; + + for (int i = -1; i < 2; i++) { + for (int j = -1; j < 2; j++) { + vec2 current_offset = vec2(1) / vec2(render_size) * vec2(i, j); + vec2 current_uv = uvn + current_offset; + if (current_uv.x < 0 || current_uv.x > 1 || current_uv.y < 0 || current_uv.y > 1) { + continue; + } + + bool is_diagonal = (abs(i) + abs(j) == 2); + + vec2 current_neighbor_velocity = textureLod(tile_max, current_uv, 0.0).xy; + + bool facing_center = dot(current_neighbor_velocity, current_offset) > 0; + + if (is_diagonal && !facing_center) { + continue; + } + + float current_neighbor_velocity_length = dot(current_neighbor_velocity, current_neighbor_velocity); + if (current_neighbor_velocity_length > max_neighbor_velocity_length) { + max_neighbor_velocity_length = current_neighbor_velocity_length; + max_neighbor_velocity = current_neighbor_velocity; + } + } + } + + imageStore(neighbor_max, uvi, vec4(max_neighbor_velocity, 0, 0)); +} \ No newline at end of file diff --git a/servers/rendering/renderer_rd/shaders/effects/motion_blur_preprocess.glsl b/servers/rendering/renderer_rd/shaders/effects/motion_blur_preprocess.glsl new file mode 100644 index 00000000000..6641a1fc1be --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/effects/motion_blur_preprocess.glsl @@ -0,0 +1,190 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2025 sphynx-owner + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/////////////////////////////////////////////////////////////////////////////////// +// File changes (yyyy-mm-dd) +// 2025-01-11: sphynx: first commit +// 2026-01-16: HydrogenC: make tile size specification constant and simplify push constant +/////////////////////////////////////////////////////////////////////////////////// +// Original file link: https://github.com/sphynx-owner/godot-motion-blur-addon-simplified/blob/master/addons/sphynx_motion_blur_toolkit/pre_blur_processing/shader_stages/shaders/pre_blur_processor.glsl + +#[compute] +#version 450 + +#VERSION_DEFINES + +#define FLT_MAX 3.402823466e+38 +#define FLT_MIN 1.175494351e-38 + +#define MAX_VIEWS 2 + +#include "../scene_data_inc.glsl" + +layout(set = 0, binding = 0) uniform sampler2D depth_sampler; +layout(set = 0, binding = 1) uniform sampler2D vector_sampler; +layout(rgba32f, set = 0, binding = 2) uniform writeonly image2D vector_output; +// layout(set = 0, binding = 4) uniform sampler2D stencil_texture; + +layout(set = 0, binding = 5, std140) uniform SceneDataBlock { + SceneData data; + SceneData prev_data; +} +scene; + +layout(push_constant, std430) uniform Params { + float rotation_velocity_multiplier; + float movement_velocity_multiplier; + float object_velocity_multiplier; + float rotation_velocity_lower_threshold; + + float movement_velocity_lower_threshold; + float object_velocity_lower_threshold; + float rotation_velocity_upper_threshold; + float movement_velocity_upper_threshold; + + float object_velocity_upper_threshold; + float support_fsr2; + float motion_blur_intensity; + float pad; +} +params; + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +float sharp_step(float lower, float upper, float x) { + return clamp((x - lower) / (upper - lower), 0, 1); +} + +float get_view_depth(float depth) { + return 0.; +} + +void main() { + ivec2 render_size = ivec2(textureSize(vector_sampler, 0)); + ivec2 uvi = ivec2(gl_GlobalInvocationID.xy); + if ((uvi.x >= render_size.x) || (uvi.y >= render_size.y)) { + return; + } + + // must be on pixel center for whole values (tested) + vec2 uvn = vec2(uvi + vec2(0.5)) / render_size; + + SceneData scene_data = scene.data; + + SceneData previous_scene_data = scene.prev_data; + + float depth = textureLod(depth_sampler, uvn, 0.0).x; + + vec4 view_position = inverse(scene_data.projection_matrix) * vec4(uvn * 2.0 - 1.0, depth, 1.0); + + view_position.xyz /= view_position.w; + + mat4 read_view_matrix = transpose(mat4(scene_data.view_matrix[0], + scene_data.view_matrix[1], + scene_data.view_matrix[2], + vec4(0.0, 0.0, 0.0, 1.0))); + + // get full change + vec4 world_local_position = inverse(read_view_matrix) * vec4(view_position.xyz, 1.0); + + mat4 read_prev_view_matrix = transpose(mat4(previous_scene_data.view_matrix[0], + previous_scene_data.view_matrix[1], + previous_scene_data.view_matrix[2], + vec4(0.0, 0.0, 0.0, 1.0))); + + vec4 view_past_position = read_prev_view_matrix * vec4(world_local_position.xyz, 1.0); + + vec4 view_past_ndc = previous_scene_data.projection_matrix * view_past_position; + + view_past_ndc.xyz /= view_past_ndc.w; + + vec3 past_uv = vec3(view_past_ndc.xy * 0.5 + 0.5, view_past_position.z); + + vec4 view_past_ndc_cache = view_past_ndc; + + vec3 camera_uv_change = past_uv - vec3(uvn, view_position.z); + + // get just rotation change + world_local_position = mat4(mat3(inverse(read_view_matrix))) * vec4(view_position.xyz, 1.0); + + view_past_position = mat4(mat3(read_prev_view_matrix)) * vec4(world_local_position.xyz, 1.0); + + view_past_ndc = previous_scene_data.projection_matrix * view_past_position; + + view_past_ndc.xyz /= view_past_ndc.w; + + past_uv = vec3(view_past_ndc.xy * 0.5 + 0.5, view_past_position.z); + + vec3 camera_rotation_uv_change = past_uv - vec3(uvn, view_position.z); + + // get just movement change + vec3 camera_movement_uv_change = camera_uv_change - camera_rotation_uv_change; + + // fill in gaps in base velocity (skybox, z velocity) + vec3 base_velocity = vec3( + textureLod(vector_sampler, uvn, 0.0).xy + + mix(vec2(0), camera_uv_change.xy, step(depth, 0.)), + depth == 0 ? 0 : camera_uv_change.z); + + // fsr just makes it so values are larger than 1, I assume its the only case when it happens + if (params.support_fsr2 > 0.5 && dot(base_velocity.xy, base_velocity.xy) >= 1) { + base_velocity = camera_uv_change; + } + + // get object velocity + vec3 object_uv_change = base_velocity - camera_uv_change.xyz; + + // construct final velocity with user defined weights + vec3 total_velocity = + + camera_rotation_uv_change * params.rotation_velocity_multiplier * + sharp_step(params.rotation_velocity_lower_threshold, params.rotation_velocity_upper_threshold, + length(camera_rotation_uv_change.xy) * params.rotation_velocity_multiplier * params.motion_blur_intensity) + + + camera_movement_uv_change * params.movement_velocity_multiplier * + sharp_step(params.movement_velocity_lower_threshold, params.movement_velocity_upper_threshold, + length(camera_movement_uv_change.xy) * params.movement_velocity_multiplier * params.motion_blur_intensity) + + + object_uv_change * params.object_velocity_multiplier * + sharp_step(params.object_velocity_lower_threshold, params.object_velocity_upper_threshold, + length(object_uv_change.xy) * params.object_velocity_multiplier * params.motion_blur_intensity); + + // if objects move, clear z direction, (velocity z can only be assumed for static environment) + if (dot(object_uv_change.xy, object_uv_change.xy) > 0.000001) { + total_velocity.z = 0; + base_velocity.z = 0; + } + + // choose the smaller option out of the two based on magnitude, seems to work well + if (dot(total_velocity.xy * 99, total_velocity.xy * 100) >= dot(base_velocity.xy * 100, base_velocity.xy * 100)) { + total_velocity = base_velocity; + } + + float total_velocity_length = max(FLT_MIN, length(total_velocity.xy)); + total_velocity.xy /= max(total_velocity_length, 1); + + float enable_velocity = 1; //step(textureLod(stencil_texture, uvn, 0.0).x, 0.5); + + // If the previous position is happening behind the camera, the w component of the projected vector would be negative, + // and the velocity vector would be flipped. (I am not 100% sure this is the whole story but this handles velocities + // that are extracted from the environment when the camera moves backwards rapidly, avoiding crazy artifacts) + // If degth == 0 (skybox), we use an arithmetic operation to generate a negative infinity float. + imageStore(vector_output, uvi, vec4(enable_velocity * total_velocity.xy / scene_data.screen_pixel_size * (view_past_ndc_cache.w < 0 ? -1 : 1), enable_velocity * total_velocity.z, depth == 0 ? (-1.0 / 0.0) : view_position.z)); +} \ No newline at end of file diff --git "a/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl\342\200\216" "b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl\342\200\216" new file mode 100644 index 00000000000..16f5fd448cb --- /dev/null +++ "b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl\342\200\216" @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2025 sphynx-owner + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/////////////////////////////////////////////////////////////////////////////////// +// File changes (yyyy-mm-dd) +// 2025-01-11: sphynx: first commit +// 2026-01-16: HydrogenC: make tile size specification constant and simplify push constant +/////////////////////////////////////////////////////////////////////////////////// +// Original file link: https://github.com/sphynx-owner/godot-motion-blur-addon-simplified/blob/master/addons/sphynx_motion_blur_toolkit/guertin/shader_stages/shader_files/guertin_tile_max_x.glsl + +#[compute] +#version 450 + +#VERSION_DEFINES + +#define FLT_MAX 3.402823466e+38 +#define FLT_MIN 1.175494351e-38 + +layout(set = 0, binding = 0) uniform sampler2D velocity_sampler; +layout(set = 0, binding = 1) uniform sampler2D depth_sampler; +layout(rgba16f, set = 0, binding = 2) uniform writeonly image2D tile_max_x; + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +void main() { + ivec2 render_size = ivec2(textureSize(velocity_sampler, 0)); + ivec2 output_size = imageSize(tile_max_x); + ivec2 uvi = ivec2(gl_GlobalInvocationID.xy); + + ivec2 global_uvi = uvi * ivec2(TILE_SIZE, 1); + if ((uvi.x >= output_size.x) || (uvi.y >= output_size.y) || (global_uvi.x >= render_size.x) || (global_uvi.y >= render_size.y)) { + return; + } + + vec2 uvn = (vec2(global_uvi) + vec2(0.5)) / render_size; + + vec4 max_velocity = vec4(0); + + float max_velocity_length = -1; + + for (int i = 0; i < TILE_SIZE; i++) { + vec2 current_uv = uvn + vec2(float(i) / render_size.x, 0); + vec4 velocity_sample = textureLod(velocity_sampler, current_uv, 0.0); + + // If the depth at the potential dominant velocity is infinity (background or skybox) + // then it will never go in front of other geometry, and can be skipped. + // TODO @sphynx-owner: enable when considering ignoring skybox for dominant velocity + // if(velocity_sample.w == (-1.0 / 0.0)) + // { + // continue; + // } + + float current_velocity_length = dot(velocity_sample.xy, velocity_sample.xy); + if (current_velocity_length > max_velocity_length) { + max_velocity_length = current_velocity_length; + max_velocity = vec4(velocity_sample.xy, 0, 0); + } + } + + imageStore(tile_max_x, uvi, max_velocity); +} \ No newline at end of file diff --git "a/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl\342\200\216" "b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl\342\200\216" new file mode 100644 index 00000000000..755b414a7fc --- /dev/null +++ "b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl\342\200\216" @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2025 sphynx-owner + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/////////////////////////////////////////////////////////////////////////////////// +// File changes (yyyy-mm-dd) +// 2025-01-11: sphynx: first commit +// 2026-01-16: HydrogenC: make tile size specification constant and simplify push constant +/////////////////////////////////////////////////////////////////////////////////// +// Original file link: https://github.com/sphynx-owner/godot-motion-blur-addon-simplified/blob/master/addons/sphynx_motion_blur_toolkit/guertin/shader_stages/shader_files/guertin_tile_max_y.glsl + +#[compute] +#version 450 + +#VERSION_DEFINES + +#define FLT_MAX 3.402823466e+38 +#define FLT_MIN 1.175494351e-38 + +layout(set = 0, binding = 0) uniform sampler2D tile_max_x; +layout(rgba16f, set = 0, binding = 1) uniform writeonly image2D tile_max; + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +void main() { + ivec2 render_size = ivec2(textureSize(tile_max_x, 0)); + ivec2 output_size = imageSize(tile_max); + ivec2 uvi = ivec2(gl_GlobalInvocationID.xy); + + ivec2 global_uvi = uvi * ivec2(1, TILE_SIZE); + if ((uvi.x >= output_size.x) || (uvi.y >= output_size.y) || (global_uvi.x >= render_size.x) || (global_uvi.y >= render_size.y)) { + return; + } + + vec2 uvn = (vec2(global_uvi) + vec2(0.5)) / render_size; + + vec4 max_velocity = vec4(0); + + float max_velocity_length = -1; + + for (int i = 0; i < TILE_SIZE; i++) { + vec2 current_uv = uvn + vec2(0, float(i) / render_size.y); + vec2 velocity_sample = textureLod(tile_max_x, current_uv, 0.0).xy; + float current_velocity_length = dot(velocity_sample, velocity_sample); + if (current_velocity_length > max_velocity_length) { + max_velocity_length = current_velocity_length; + max_velocity = vec4(velocity_sample, 0, 0); + } + } + imageStore(tile_max, uvi, max_velocity); +} \ No newline at end of file diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index a1b6a687a2b..e307a175296 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -866,6 +866,13 @@ class RenderingServerDefault : public RenderingServer { FUNCRIDSPLIT(camera_attributes) + FUNC2(camera_attributes_set_motion_blur_framerate_mode, MotionBlurFramerateMode, int) + FUNC1(camera_attributes_set_motion_blur_show_in_editor, bool) + FUNC1(camera_attributes_set_motion_blur_quality, MotionBlurQuality) + FUNC1(camera_attributes_set_motion_blur_tile_size, MotionBlurTileSize) + + FUNC9(camera_attributes_set_motion_blur, RID, bool, float, bool, float, float, float, float, float) + FUNC2(camera_attributes_set_dof_blur_quality, DOFBlurQuality, bool) FUNC1(camera_attributes_set_dof_blur_bokeh_shape, DOFBokehShape) diff --git a/servers/rendering/storage/camera_attributes_storage.cpp b/servers/rendering/storage/camera_attributes_storage.cpp index 4c86b317477..cb8a93dbfcd 100644 --- a/servers/rendering/storage/camera_attributes_storage.cpp +++ b/servers/rendering/storage/camera_attributes_storage.cpp @@ -55,6 +55,51 @@ void RendererCameraAttributes::camera_attributes_free(RID p_rid) { camera_attributes_owner.free(p_rid); } +void RendererCameraAttributes::camera_attributes_set_motion_blur_framerate_mode(RenderingServer::MotionBlurFramerateMode p_mode, int p_reference_framerate) { + motion_blur_framerate_mode = p_mode; + motion_blur_reference_framerate = p_reference_framerate; +} + +void RendererCameraAttributes::camera_attributes_set_motion_blur_quality(RenderingServer::MotionBlurQuality p_quality) { + motion_blur_quality = p_quality; +} + +void RendererCameraAttributes::camera_attributes_set_motion_blur_tile_size(RenderingServer::MotionBlurTileSize p_tile_size) { + motion_blur_tile_size = p_tile_size; +} + +void RendererCameraAttributes::camera_attributes_set_motion_blur_show_in_editor(bool p_enabled) { + if (motion_blur_show_in_editor == p_enabled) { + return; + } + motion_blur_show_in_editor = p_enabled; +} + +void RendererCameraAttributes::camera_attributes_set_motion_blur(RID p_camera_attributes, bool p_enable, float p_intensity, bool p_clamp_velocities_to_tile, float p_object_velocity_multiplier, float p_movement_velocity_multiplier, float p_rotation_velocity_multiplier, float p_velocity_lower_threshold, float p_velocity_upper_threshold) { + CameraAttributes *cam_attributes = camera_attributes_owner.get_or_null(p_camera_attributes); + ERR_FAIL_NULL(cam_attributes); +#ifdef DEBUG_ENABLED + if ((OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "mobile") && p_enable) { + WARN_PRINT_ONCE_ED("Motion blur is only available when using the Forward+ renderer."); + } +#endif + cam_attributes->motion_blur_enabled = p_enable; + cam_attributes->motion_blur_intensity = p_intensity; + cam_attributes->motion_blur_clamp_velocities_to_tile = p_clamp_velocities_to_tile; + cam_attributes->motion_blur_object_velocity_multiplier = p_object_velocity_multiplier; + cam_attributes->motion_blur_movement_velocity_multiplier = p_movement_velocity_multiplier; + cam_attributes->motion_blur_rotation_velocity_multiplier = p_rotation_velocity_multiplier; + cam_attributes->motion_blur_velocity_lower_threshold = p_velocity_lower_threshold; + cam_attributes->motion_blur_velocity_upper_threshold = p_velocity_upper_threshold; +} + +float RendererCameraAttributes::camera_attributes_get_motion_blur_intensity(RID p_camera_attributes) { + CameraAttributes *cam_attributes = camera_attributes_owner.get_or_null(p_camera_attributes); + ERR_FAIL_NULL_V(cam_attributes, 0.0); + return cam_attributes->motion_blur_intensity; +} + + void RendererCameraAttributes::camera_attributes_set_dof_blur_quality(RS::DOFBlurQuality p_quality, bool p_use_jitter) { dof_blur_quality = p_quality; dof_blur_use_jitter = p_use_jitter; diff --git a/servers/rendering/storage/camera_attributes_storage.h b/servers/rendering/storage/camera_attributes_storage.h index 52cbe1d124b..fda24118688 100644 --- a/servers/rendering/storage/camera_attributes_storage.h +++ b/servers/rendering/storage/camera_attributes_storage.h @@ -51,6 +51,16 @@ class RendererCameraAttributes { float auto_exposure_scale = 1.0; uint64_t auto_exposure_version = 0; + bool motion_blur_enabled = false; + float motion_blur_intensity = 1.0; + bool motion_blur_clamp_velocities_to_tile = true; + float motion_blur_object_velocity_multiplier = 1.0; + float motion_blur_movement_velocity_multiplier = 1.0; + float motion_blur_rotation_velocity_multiplier = 1.0; + float motion_blur_velocity_lower_threshold = 0.0; + float motion_blur_velocity_upper_threshold = 0.0; + + bool dof_blur_far_enabled = false; float dof_blur_far_distance = 10; float dof_blur_far_transition = 5; @@ -60,6 +70,12 @@ class RendererCameraAttributes { float dof_blur_amount = 0.1; }; + RS::MotionBlurFramerateMode motion_blur_framerate_mode = RS::MOTION_BLUR_FRAMERATE_MODE_CAPPED; + int motion_blur_reference_framerate = 30; + bool motion_blur_show_in_editor = true; + RS::MotionBlurQuality motion_blur_quality = RS::MOTION_BLUR_QUALITY_MEDIUM; + RS::MotionBlurTileSize motion_blur_tile_size = RS::MOTION_BLUR_TILE_SIZE_MEDIUM; + RS::DOFBlurQuality dof_blur_quality = RS::DOF_BLUR_QUALITY_MEDIUM; RS::DOFBokehShape dof_blur_bokeh_shape = RS::DOF_BOKEH_HEXAGON; bool dof_blur_use_jitter = false; @@ -80,6 +96,27 @@ class RendererCameraAttributes { void camera_attributes_initialize(RID p_rid); void camera_attributes_free(RID p_rid); + void camera_attributes_set_motion_blur_framerate_mode(RS::MotionBlurFramerateMode p_mode, int p_reference_framerate); + void camera_attributes_set_motion_blur_show_in_editor(bool p_enabled); + void camera_attributes_set_motion_blur_quality(RS::MotionBlurQuality p_quality); + void camera_attributes_set_motion_blur_tile_size(RS::MotionBlurTileSize p_tile_size); + + void camera_attributes_set_motion_blur(RID p_camera_attributes, bool p_enable, float p_intensity, bool p_clamp_velocities_to_tile, float p_object_velocity_multiplier, float p_movement_velocity_multiplier, float p_rotation_velocity_multiplier, float p_velocity_lower_threshold, float p_velocity_upper_threshold); + float camera_attributes_get_motion_blur_intensity(RID p_camera_attributes); + bool camera_attributes_get_motion_blur_clamp_velocities_to_tile(RID p_camera_attributes); + float camera_attributes_get_motion_blur_object_velocity_multiplier(RID p_camera_attributes); + float camera_attributes_get_motion_blur_movement_velocity_multiplier(RID p_camera_attributes); + float camera_attributes_get_motion_blur_rotation_velocity_multiplier(RID p_camera_attributes); + float camera_attributes_get_motion_blur_velocity_lower_threshold(RID p_camera_attributes); + float camera_attributes_get_motion_blur_velocity_upper_threshold(RID p_camera_attributes); + + _FORCE_INLINE_ bool camera_attributes_uses_motion_blur(RID p_camera_attributes) { + CameraAttributes *cam_attributes = camera_attributes_owner.get_or_null(p_camera_attributes); + + return cam_attributes && cam_attributes->motion_blur_enabled && cam_attributes->motion_blur_intensity > 0.0; + } + + void camera_attributes_set_dof_blur_quality(RS::DOFBlurQuality p_quality, bool p_use_jitter); void camera_attributes_set_dof_blur_bokeh_shape(RS::DOFBokehShape p_shape); @@ -114,6 +151,27 @@ class RendererCameraAttributes { return cam_attributes && cam_attributes->use_auto_exposure; } + + _FORCE_INLINE_ RS::MotionBlurFramerateMode camera_attributes_get_motion_blur_framerate_mode() { + return motion_blur_framerate_mode; + } + + _FORCE_INLINE_ int camera_attributes_get_motion_blur_reference_framerate() { + return motion_blur_reference_framerate; + } + + _FORCE_INLINE_ bool camera_attributes_get_motion_blur_show_in_editor() { + return motion_blur_show_in_editor; + } + + _FORCE_INLINE_ RS::MotionBlurQuality camera_attributes_get_motion_blur_quality() { + return motion_blur_quality; + } + + _FORCE_INLINE_ RS::MotionBlurTileSize camera_attributes_get_motion_blur_tile_size() { + return motion_blur_tile_size; + } + _FORCE_INLINE_ RS::DOFBlurQuality camera_attributes_get_dof_blur_quality() { return dof_blur_quality; } diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 96817aadb75..d84044dd477 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3187,9 +3187,15 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("camera_attributes_set_dof_blur_quality", "quality", "use_jitter"), &RenderingServer::camera_attributes_set_dof_blur_quality); ClassDB::bind_method(D_METHOD("camera_attributes_set_dof_blur_bokeh_shape", "shape"), &RenderingServer::camera_attributes_set_dof_blur_bokeh_shape); + ClassDB::bind_method(D_METHOD("camera_attributes_set_motion_blur_framerate_mode", "mode", "reference_framerate"), &RenderingServer::camera_attributes_set_motion_blur_framerate_mode); + ClassDB::bind_method(D_METHOD("camera_attributes_set_motion_blur_show_in_editor", "enabled"), &RenderingServer::camera_attributes_set_motion_blur_show_in_editor); + ClassDB::bind_method(D_METHOD("camera_attributes_set_motion_blur_quality", "quality"), &RenderingServer::camera_attributes_set_motion_blur_quality); + ClassDB::bind_method(D_METHOD("camera_attributes_set_motion_blur_tile_size", "tile_size"), &RenderingServer::camera_attributes_set_motion_blur_tile_size); + ClassDB::bind_method(D_METHOD("camera_attributes_set_dof_blur", "camera_attributes", "far_enable", "far_distance", "far_transition", "near_enable", "near_distance", "near_transition", "amount"), &RenderingServer::camera_attributes_set_dof_blur); ClassDB::bind_method(D_METHOD("camera_attributes_set_exposure", "camera_attributes", "multiplier", "normalization"), &RenderingServer::camera_attributes_set_exposure); ClassDB::bind_method(D_METHOD("camera_attributes_set_auto_exposure", "camera_attributes", "enable", "min_sensitivity", "max_sensitivity", "speed", "scale"), &RenderingServer::camera_attributes_set_auto_exposure); + ClassDB::bind_method(D_METHOD("camera_attributes_set_motion_blur", "camera_attributes", "enabled", "intensity", "clamp_velocities_to_tile", "object_velocity_multiplier", "movement_velocity_multiplier", "rotation_velocity_multiplier", "velocity_lower_threshold", "velocity_upper_threshold"), &RenderingServer::camera_attributes_set_motion_blur); BIND_ENUM_CONSTANT(DOF_BOKEH_BOX); BIND_ENUM_CONSTANT(DOF_BOKEH_HEXAGON); @@ -3200,6 +3206,15 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(DOF_BLUR_QUALITY_MEDIUM); BIND_ENUM_CONSTANT(DOF_BLUR_QUALITY_HIGH); + BIND_ENUM_CONSTANT(MOTION_BLUR_QUALITY_LOW); + BIND_ENUM_CONSTANT(MOTION_BLUR_QUALITY_MEDIUM); + BIND_ENUM_CONSTANT(MOTION_BLUR_QUALITY_HIGH); + + BIND_ENUM_CONSTANT(MOTION_BLUR_TILE_SIZE_SMALL); + BIND_ENUM_CONSTANT(MOTION_BLUR_TILE_SIZE_MEDIUM); + BIND_ENUM_CONSTANT(MOTION_BLUR_TILE_SIZE_LARGE); + BIND_ENUM_CONSTANT(MOTION_BLUR_TILE_SIZE_EXTRA_LARGE); + /* SCENARIO */ ClassDB::bind_method(D_METHOD("scenario_create"), &RenderingServer::scenario_create); @@ -3701,6 +3716,13 @@ void RenderingServer::init() { GLOBAL_DEF_RST("rendering/textures/default_filters/use_nearest_mipmap_filter", false); GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/textures/default_filters/anisotropic_filtering_level", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Faster),4× (Fast),8× (Average),16× (Slow)")), 2); + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/camera/motion_blur/motion_blur_quality", PROPERTY_HINT_ENUM, "Low (Fast),Medium (Average),High (Slow)"), 1); + GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/camera/motion_blur/motion_blur_tile_size", PROPERTY_HINT_ENUM, "Small,Medium,Large,Extra Large"), 1); + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/camera/motion_blur/motion_blur_framerate_mode", PROPERTY_HINT_ENUM, "Native,Capped,Fixed"), 1); + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/camera/motion_blur/motion_blur_reference_framerate", PROPERTY_HINT_RANGE, "1,120,1,or_greater"), 30); + GLOBAL_DEF("rendering/camera/motion_blur/motion_blur_show_in_editor", true); + + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/camera/depth_of_field/depth_of_field_bokeh_shape", PROPERTY_HINT_ENUM, "Box (Fast),Hexagon (Average),Circle (Slowest)"), 1); GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/camera/depth_of_field/depth_of_field_bokeh_quality", PROPERTY_HINT_ENUM, "Very Low (Fastest),Low (Fast),Medium (Average),High (Slow)"), 1); GLOBAL_DEF("rendering/camera/depth_of_field/depth_of_field_use_jitter", false); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index abf97c00508..7d511493e6e 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -1392,6 +1392,32 @@ class RenderingServer : public Object { virtual RID camera_attributes_create() = 0; + enum MotionBlurQuality { + MOTION_BLUR_QUALITY_LOW, + MOTION_BLUR_QUALITY_MEDIUM, + MOTION_BLUR_QUALITY_HIGH, + }; + + enum MotionBlurTileSize { + MOTION_BLUR_TILE_SIZE_SMALL, + MOTION_BLUR_TILE_SIZE_MEDIUM, + MOTION_BLUR_TILE_SIZE_LARGE, + MOTION_BLUR_TILE_SIZE_EXTRA_LARGE, + }; + + enum MotionBlurFramerateMode { + MOTION_BLUR_FRAMERATE_MODE_NATIVE, + MOTION_BLUR_FRAMERATE_MODE_CAPPED, + MOTION_BLUR_FRAMERATE_MODE_FIXED, + }; + + virtual void camera_attributes_set_motion_blur(RID p_camera_attributes, bool p_enable, float p_intensity, bool p_clamp_velocities_to_tile, float p_object_velocity_multiplier, float p_movement_velocity_multiplier, float p_rotation_velocity_multiplier, float p_velocity_lower_threshold, float p_velocity_upper_threshold) = 0; + + virtual void camera_attributes_set_motion_blur_framerate_mode(MotionBlurFramerateMode p_mode, int p_reference_framerate) = 0; + virtual void camera_attributes_set_motion_blur_show_in_editor(bool p_enabled) = 0; + virtual void camera_attributes_set_motion_blur_quality(MotionBlurQuality p_quality) = 0; + virtual void camera_attributes_set_motion_blur_tile_size(MotionBlurTileSize p_tile_size) = 0; + enum DOFBlurQuality { DOF_BLUR_QUALITY_VERY_LOW, DOF_BLUR_QUALITY_LOW, @@ -1976,6 +2002,9 @@ VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGIRayCount); VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGIFramesToUpdateLight); VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGIYScale); VARIANT_ENUM_CAST(RenderingServer::SubSurfaceScatteringQuality); +VARIANT_ENUM_CAST(RenderingServer::MotionBlurQuality); +VARIANT_ENUM_CAST(RenderingServer::MotionBlurTileSize); +VARIANT_ENUM_CAST(RenderingServer::MotionBlurFramerateMode); VARIANT_ENUM_CAST(RenderingServer::DOFBlurQuality); VARIANT_ENUM_CAST(RenderingServer::DOFBokehShape); VARIANT_ENUM_CAST(RenderingServer::ShadowQuality); From f9dc76ddf169d2069e2cabac3333c0a569c0c99c Mon Sep 17 00:00:00 2001 From: AR Date: Wed, 25 Feb 2026 05:57:11 +0500 Subject: [PATCH 2/2] Small fix Co-authored-by: AR Co-authored-by: sphynx-owner --- doc/classes/ProjectSettings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b31bf21fc38..169347ea923 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2804,7 +2804,7 @@ If [code]true[/code], jitters DOF samples to make effect slightly blurrier and hide lines created from low sample rates. This can result in a slightly grainy appearance when used with a low number of samples. - Define framerate-based behavior of the motion blur. Uses [member motion_blur_reference_framerate] as the reference framerate for the different modes. + Defines framerate-based behavior of the motion blur. Uses [member motion_blur_reference_framerate] as the reference framerate for the different modes. MOTION_BLUR_FRAMERATE_MODE_NATIVE applies the blur based on the game's framerate as is, and does not use the reference framerate. MOTION_BLUR_FRAMERATE_MODE_CAPPED applies the blur based on the game's framerate, but below a certain framerate (which means larger time gap and thus larger blur) it will cap the blur to emulate the blur that will be generated at the reference framerate. This is the default value, and it means that if the game lags, the blur will be kept under control. MOTION_BLUR_FRAMERATE_MODE_FIXED enforces the reference framerate blur regardless of the game's framerate. This can lead to overblurring when the game's framerate is higher than the reference framerate.