diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 98eeead289..128d999a29 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 59ad77a146..5c67bc4a8f 100644 --- a/doc/classes/CameraAttributesPractical.xml +++ b/doc/classes/CameraAttributesPractical.xml @@ -43,5 +43,31 @@ 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]. + + If [code]true[/code], enables a motion blur effect when the camera moves, rotates, or when an object on screen moves. This has a moderate performance impact. + [b]Note:[/b] Motion blur can result in motion sickness among some players. When implementing motion blur in a game, consider exposing an option to reduce its intensity or turn it off. + + + Defines the amount of motion blur to apply given the object's velocity. At 1.0, the object's velocity will be blurred in full (blur fills in the motion step fully). 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 camera rotation. + + + Defines the intensity of blur similar to [member motion_blur_intensity], but only for the part of the motion caused by object movement. + + + Defines a magnitude of motion beyond which motion would start getting blurred. Works in tandem with [member motion_blur_velocity_threshold_upper] 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 percentage. Setting it to 50 means that blurring starts at motion that spans more than half the screen in one frame. + 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 beyond which motion will be blurred fully. Works in tandem with [member motion_blur_velocity_threshold_lower] 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 percentage. Setting it to 50 means that motion that spans more than half the screen in one frame is blurred in full. + 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/EditorSettings.xml b/doc/classes/EditorSettings.xml index a2a9750f90..355de89de5 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -435,6 +435,9 @@ The color to use for the selection box that surrounds selected nodes in the 3D editor viewport. The color's alpha channel influences the selection box's opacity. + + If [code]true[/code], when a scene has a camera that has motion blur enabled, it will be be applied in the editor scene view as well. When [code]false[/code], the motion blur will only be applied at runtime. + The color to use for the AABB gizmo that displays the [GeometryInstance3D]'s custom [AABB]. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b6862b7808..fd78dfd2fd 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2803,6 +2803,28 @@ 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. + + Defines the framerate-based behavior of the motion blur. Uses [member rendering/camera/motion_blur/motion_blur_reference_framerate] as the reference framerate for the different modes. + - [code]MOTION_BLUR_FRAMERATE_MODE_NATIVE[/code] applies the blur based on the game's framerate as is, and does not use the reference framerate. + - [code]MOTION_BLUR_FRAMERATE_MODE_CAPPED[/code] applies the blur based on the game's framerate, but below a certain framerate (which means larger time gap and thus more intense blurring) 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. + - [code]MOTION_BLUR_FRAMERATE_MODE_FIXED[/code] 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: + - [b]Low:[/b] 4 samples + - [b]Medium:[/b] 8 samples + - [b]High:[/b] 16 samples + + + Defines a framerate to be used as a reference by [member rendering/camera/motion_blur/motion_blur_framerate_mode]. + + + Defines, in pixels, the size of the 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: + - [b]Small:[/b] 20 pixels + - [b]Medium:[/b] 40 pixels + - [b]Large:[/b] 60 pixels + - [b]Extra Large:[/b] 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 ca96c9890b..b2976bb002 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -101,6 +101,50 @@ return log((aperture * aperture) / shutter_speed * (100.0 / sensitivity)) / log(2) [/codeblock] + + + + + + + + + + + + + + Configures the motion blur effect on the specified [param camera_attributes] resource. See properties in [CameraAttributesPractical] starting from [member CameraAttributesPractical.motion_blur_enabled] for details on each parameter. + + + + + + + + Sets the framerate-based behavior of the motion blur effect. See also [member ProjectSettings.rendering/camera/motion_blur/motion_blur_framerate_mode]. + + + + + + + Sets the quality for the motion blur effect. See also [member ProjectSettings.rendering/camera/motion_blur/motion_blur_quality]. + + + + + + + If [code]true[/code], and motion blur is enabled,, it will be be applied in the editor scene view as well. When [code]false[/code], the motion blur will only be applied at runtime. See also [member EditorSettings.editors/3d/viewport_visuals/show_motion_blur_in_editor] + + + + + + + Sets the tile size for the motion blur effect. See also [member ProjectSettings.rendering/camera/motion_blur/motion_blur_tile_size]. + @@ -5610,6 +5654,36 @@ Highest quality DOF blur. Results in the smoothest looking blur by taking the most samples, but is also significantly slower. + + Low quality for the motion blur effect (cheaper on the GPU). + + + Medium quality for the motion blur effect. + + + High quality for the motion blur effect (more expensive on the GPU). + + + Tile size of 20×20 pixels for the motion blur effect. Lowest blurring potential for fast-moving objects, highest level of detail. + + + Tile size of 40×40 pixels for the motion blur effect. Low blurring potential for fast-moving objects, high level of detail. + + + Tile size of 60×60 pixels for the motion blur effect. High blurring potential for fast-moving objects, low level of detail. + + + Tile size of 80×80 pixels for the motion blur effect. Highest blurring potential for fast-moving objects, lowest level of detail. + + + Applies the blur based on the game's framerate as is, and does not use the reference framerate. + + + Applies the blur based on the game's framerate, but below a certain framerate (which means larger time gap and thus more intense blurring) 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. + + + 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. + The instance does not have a type. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 32ea76e79d..befb86f759 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -470,6 +470,14 @@ void EditorNode::_update_from_settings() { scene_root->propagate_notification(Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED); } + RSE::MotionBlurFramerateMode motion_blur_framerate_mode = RSE::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); + RSE::MotionBlurQuality motion_blur_quality = RSE::MotionBlurQuality(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_quality"))); + RS::get_singleton()->camera_attributes_set_motion_blur_quality(motion_blur_quality); + RSE::MotionBlurTileSize motion_blur_tile_size = RSE::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/editor/scene/3d/node_3d_editor_plugin.cpp b/editor/scene/3d/node_3d_editor_plugin.cpp index ed2e51bf1d..b78c941d3b 100644 --- a/editor/scene/3d/node_3d_editor_plugin.cpp +++ b/editor/scene/3d/node_3d_editor_plugin.cpp @@ -8569,6 +8569,10 @@ void Node3DEditor::_notification(int p_what) { active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15)); } + // Update the internal setting that controls whether motion blur effect is enabled in editor viewport. + const bool motion_blur_show_in_editor = EDITOR_GET("editors/3d/viewport_visuals/show_motion_blur_in_editor"); + RS::get_singleton()->camera_attributes_set_motion_blur_show_in_editor(motion_blur_show_in_editor); + // Update grid color by rebuilding grid. _finish_grid(); _init_grid(); @@ -9290,6 +9294,10 @@ Node3DEditor::Node3DEditor() { gizmo.visible = true; gizmo.scale = 1.0; + // Init motion blow show in editor setting + const bool motion_blur_show_in_editor = EDITOR_GET("editors/3d/viewport_visuals/show_motion_blur_in_editor"); + RS::get_singleton()->camera_attributes_set_motion_blur_show_in_editor(motion_blur_show_in_editor); + viewport_environment.instantiate(); VBoxContainer *vbc = this; diff --git a/editor/settings/editor_settings.cpp b/editor/settings/editor_settings.cpp index a649ea9000..8d05600ae9 100644 --- a/editor/settings/editor_settings.cpp +++ b/editor/settings/editor_settings.cpp @@ -926,6 +926,9 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING_BASIC(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/default_z_near", 0.05, "0.01,10,0.01,or_greater,suffix:m") EDITOR_SETTING_BASIC(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/3d/default_z_far", 4000.0, "0.1,4000,0.1,or_greater,suffix:m") + // 3D: Viewport visuals + _initial_set("editors/3d/viewport_visuals/show_motion_blur_in_editor", true); + // 3D: Navigation _initial_set("editors/3d/navigation/invert_x_axis", false, true); _initial_set("editors/3d/navigation/invert_y_axis", false, true); diff --git a/scene/resources/camera_attributes.cpp b/scene/resources/camera_attributes.cpp index e658b02685..e4c10e7b68 100644 --- a/scene/resources/camera_attributes.cpp +++ b/scene/resources/camera_attributes.cpp @@ -153,6 +153,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(); @@ -230,6 +315,19 @@ void CameraAttributesPractical::_update_dof_blur() { dof_blur_amount); } +void CameraAttributesPractical::_update_motion_blur() { + RS::get_singleton()->camera_attributes_set_motion_blur( + get_rid(), + motion_blur_enabled, + motion_blur_intensity, + motion_blur_clamp_velocities_to_tile, + motion_blur_object_velocity_multiplier, + motion_blur_movement_velocity_multiplier, + motion_blur_rotation_velocity_multiplier, + motion_blur_velocity_lower_threshold, + motion_blur_velocity_upper_threshold); +} + float CameraAttributesPractical::calculate_exposure_normalization() const { return exposure_sensitivity / 3072007.0; // Matches exposure normalization for default CameraAttributesPhysical at ISO 100. } @@ -267,13 +365,36 @@ void CameraAttributesPractical::_validate_property(PropertyInfo &p_property) con if (!Engine::get_singleton()->is_editor_hint()) { return; } + if ((!dof_blur_far_enabled && (p_property.name == "dof_blur_far_distance" || p_property.name == "dof_blur_far_transition")) || (!dof_blur_near_enabled && (p_property.name == "dof_blur_near_distance" || p_property.name == "dof_blur_near_transition"))) { p_property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL; } + + if (p_property.name.begins_with("motion_blur_") && p_property.name != "motion_blur_enabled" && !motion_blur_enabled) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + return; + } } void CameraAttributesPractical::_bind_methods() { + // Motion blur + + ClassDB::bind_method(D_METHOD("set_motion_blur_enabled", "enabled"), &CameraAttributesPractical::set_motion_blur_enabled); + ClassDB::bind_method(D_METHOD("is_motion_blur_enabled"), &CameraAttributesPractical::is_motion_blur_enabled); + ClassDB::bind_method(D_METHOD("set_motion_blur_intensity", "intensity"), &CameraAttributesPractical::set_motion_blur_intensity); + ClassDB::bind_method(D_METHOD("get_motion_blur_intensity"), &CameraAttributesPractical::get_motion_blur_intensity); + ClassDB::bind_method(D_METHOD("set_motion_blur_object_velocity_multiplier", "multiplier"), &CameraAttributesPractical::set_motion_blur_object_velocity_multiplier); + ClassDB::bind_method(D_METHOD("get_motion_blur_object_velocity_multiplier"), &CameraAttributesPractical::get_motion_blur_object_velocity_multiplier); + ClassDB::bind_method(D_METHOD("set_motion_blur_movement_velocity_multiplier", "multiplier"), &CameraAttributesPractical::set_motion_blur_movement_velocity_multiplier); + ClassDB::bind_method(D_METHOD("get_motion_blur_movement_velocity_multiplier"), &CameraAttributesPractical::get_motion_blur_movement_velocity_multiplier); + ClassDB::bind_method(D_METHOD("set_motion_blur_rotation_velocity_multiplier", "multiplier"), &CameraAttributesPractical::set_motion_blur_rotation_velocity_multiplier); + ClassDB::bind_method(D_METHOD("get_motion_blur_rotation_velocity_multiplier"), &CameraAttributesPractical::get_motion_blur_rotation_velocity_multiplier); + ClassDB::bind_method(D_METHOD("set_motion_blur_velocity_lower_threshold", "threshold"), &CameraAttributesPractical::set_motion_blur_velocity_lower_threshold); + ClassDB::bind_method(D_METHOD("get_motion_blur_velocity_lower_threshold"), &CameraAttributesPractical::get_motion_blur_velocity_lower_threshold); + ClassDB::bind_method(D_METHOD("set_motion_blur_velocity_upper_threshold", "threshold"), &CameraAttributesPractical::set_motion_blur_velocity_upper_threshold); + ClassDB::bind_method(D_METHOD("get_motion_blur_velocity_upper_threshold"), &CameraAttributesPractical::get_motion_blur_velocity_upper_threshold); + // DOF blur ClassDB::bind_method(D_METHOD("set_dof_blur_far_enabled", "enabled"), &CameraAttributesPractical::set_dof_blur_far_enabled); @@ -297,6 +418,20 @@ void CameraAttributesPractical::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_exposure_min_sensitivity", "min_sensitivity"), &CameraAttributesPractical::set_auto_exposure_min_sensitivity); ClassDB::bind_method(D_METHOD("get_auto_exposure_min_sensitivity"), &CameraAttributesPractical::get_auto_exposure_min_sensitivity); + ADD_GROUP("Motion Blur", "motion_blur_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "motion_blur_enabled", PROPERTY_HINT_GROUP_ENABLE), "set_motion_blur_enabled", "is_motion_blur_enabled"); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_blur_intensity", PROPERTY_HINT_RANGE, "0.0,1.0,0.01,or_greater"), "set_motion_blur_intensity", "get_motion_blur_intensity"); + + ADD_SUBGROUP("Velocity Multipliers", "motion_blur_velocity_multiplier_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_blur_velocity_multiplier_object", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_motion_blur_object_velocity_multiplier", "get_motion_blur_object_velocity_multiplier"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_blur_velocity_multiplier_camera_movement", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_motion_blur_movement_velocity_multiplier", "get_motion_blur_movement_velocity_multiplier"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_blur_velocity_multiplier_camera_rotation", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_motion_blur_rotation_velocity_multiplier", "get_motion_blur_rotation_velocity_multiplier"); + + ADD_SUBGROUP("Velocity Thresholds", "motion_blur_velocity_threshold_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_blur_velocity_threshold_lower", PROPERTY_HINT_RANGE, "0.0,100.0,0.01"), "set_motion_blur_velocity_lower_threshold", "get_motion_blur_velocity_lower_threshold"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_blur_velocity_threshold_upper", PROPERTY_HINT_RANGE, "0.0,100.0,0.01"), "set_motion_blur_velocity_upper_threshold", "get_motion_blur_velocity_upper_threshold"); + ADD_GROUP("DOF Blur", "dof_blur_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_far_enabled"), "set_dof_blur_far_enabled", "is_dof_blur_far_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp,suffix:m"), "set_dof_blur_far_distance", "get_dof_blur_far_distance"); @@ -312,6 +447,7 @@ void CameraAttributesPractical::_bind_methods() { } CameraAttributesPractical::CameraAttributesPractical() { + _update_motion_blur(); _update_dof_blur(); _update_exposure(); set_auto_exposure_min_sensitivity(0.0); diff --git a/scene/resources/camera_attributes.h b/scene/resources/camera_attributes.h index 99b8800645..154d10652e 100644 --- a/scene/resources/camera_attributes.h +++ b/scene/resources/camera_attributes.h @@ -86,6 +86,19 @@ class CameraAttributesPractical : public CameraAttributes { GDCLASS(CameraAttributesPractical, CameraAttributes); private: + /// @name 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(); + /// @} + /// @name DOF Blur /// @{ bool dof_blur_far_enabled = false; @@ -107,6 +120,26 @@ class CameraAttributesPractical : public CameraAttributes { void _validate_property(PropertyInfo &p_property) const; public: + /// @name 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; + /// @} + /// @name DOF Blur /// @{ void set_dof_blur_far_enabled(bool p_enabled); 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 0000000000..48b1e3778f --- /dev/null +++ b/servers/rendering/renderer_rd/effects/motion_blur.cpp @@ -0,0 +1,324 @@ +/**************************************************************************/ +/* 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 "servers/rendering/renderer_rd/effects/motion_blur.h" + +#include "core/config/engine.h" +#include "servers/rendering/renderer_rd/uniform_set_cache_rd.h" +#include "servers/rendering/rendering_server_globals.h" + +RendererRD::MotionBlur::MotionBlur(RSE::MotionBlurTileSize p_tile_size_level) { + // Init tile size (changes require restart) + switch (p_tile_size_level) { + case RSE::MOTION_BLUR_TILE_SIZE_SMALL: + tile_size = 20; + break; + case RSE::MOTION_BLUR_TILE_SIZE_MEDIUM: + tile_size = 40; + break; + case RSE::MOTION_BLUR_TILE_SIZE_LARGE: + tile_size = 60; + break; + case RSE::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 p_transparent_bg, float p_time_step, CopyEffects *p_copy_effects) { + if (!RSG::camera_attributes->camera_attributes_get_motion_blur_show_in_editor() && Engine::get_singleton()->is_editor_hint()) { + return; + } + + 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(); + const double time_scale = Engine::get_singleton()->get_effective_time_scale(); + float time_step = p_time_step / (float)time_scale; + switch (RSG::camera_attributes->camera_attributes_get_motion_blur_framerate_mode()) { + case RSE::MOTION_BLUR_FRAMERATE_MODE_NATIVE: + // Use raw intensity, ignore frame time + break; + case RSE::MOTION_BLUR_FRAMERATE_MODE_CAPPED: + intensity *= MIN(1.f / reference_framerate, time_step) / time_step; + break; + case RSE::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 RSE::MOTION_BLUR_QUALITY_LOW: + sample_count = 4; + break; + case RSE::MOTION_BLUR_QUALITY_MEDIUM: + sample_count = 8; + break; + case RSE::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 = CLAMP(RSG::camera_attributes->camera_attributes_get_motion_blur_velocity_lower_threshold(p_camera_attributes) / 100.0f, 0.0f, 1.0f); + float velocity_upper_threshold = CLAMP(RSG::camera_attributes->camera_attributes_get_motion_blur_velocity_upper_threshold(p_camera_attributes) / 100.0f, 0.0f, 1.0f); + + // 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 = p_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(); +} 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 0000000000..333ad97814 --- /dev/null +++ b/servers/rendering/renderer_rd/effects/motion_blur.h @@ -0,0 +1,142 @@ +/**************************************************************************/ +/* 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 "servers/rendering/renderer_rd/effects/copy_effects.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 p_transparent_bg, float p_time_step, CopyEffects *p_copy_effects); +}; +} //namespace RendererRD 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 6a3c821bff..5a48353c44 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1758,11 +1758,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) { @@ -2396,7 +2399,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/pipeline_deferred_rd.h b/servers/rendering/renderer_rd/pipeline_deferred_rd.h index ef5189b3ec..d4953f05d7 100644 --- a/servers/rendering/renderer_rd/pipeline_deferred_rd.h +++ b/servers/rendering/renderer_rd/pipeline_deferred_rd.h @@ -128,7 +128,7 @@ class PipelineDeferredRD { #ifdef DEV_ENABLED ERR_FAIL_COND_MSG(!(RD::get_singleton()->render_pipeline_is_valid(pipeline) || RD::get_singleton()->compute_pipeline_is_valid(pipeline)), "`free()` must be called manually before the dependent shader is freed."); #endif - RD::get_singleton()->free_rid(pipeline); + RD::get_singleton()->free(pipeline); pipeline = RID(); } } diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 19fc10cf26..66fa95ceb6 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -40,6 +40,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" @@ -548,6 +549,18 @@ 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 (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)) { @@ -1686,6 +1699,10 @@ 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(RSE::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_quality(RSE::MotionBlurQuality(int(GLOBAL_GET("rendering/camera/motion_blur/motion_blur_quality")))); + RSG::camera_attributes->camera_attributes_set_motion_blur_tile_size(RSE::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"); @@ -1714,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)); @@ -1738,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 d093d2210c..27d4a915e8 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -44,6 +44,7 @@ #include "servers/rendering/renderer_rd/effects/debug_effects.h" #include "servers/rendering/renderer_rd/effects/fsr.h" #include "servers/rendering/renderer_rd/effects/luminance.h" +#include "servers/rendering/renderer_rd/effects/motion_blur.h" #ifdef METAL_ENABLED #include "servers/rendering/renderer_rd/effects/metal_fx.h" #endif @@ -60,6 +61,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; @@ -67,6 +70,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 0000000000..0fe6bde119 --- /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); +} diff --git a/servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl b/servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl new file mode 100644 index 0000000000..79198bf7b9 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/effects/motion_blur_neighbor_max.glsl @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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)); +} 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 0000000000..8f07510cdd --- /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 depth == 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)); +} diff --git a/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl new file mode 100644 index 0000000000..58cac4a23f --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_x.glsl @@ -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); +} diff --git a/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl new file mode 100644 index 0000000000..4ebe4ce8cc --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/effects/motion_blur_tile_max_y.glsl @@ -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); +} diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 151ecfe93f..0fc1d031df 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -928,6 +928,13 @@ class RenderingServerDefault : public RenderingServer { FUNCRIDSPLIT(camera_attributes) + FUNC1(camera_attributes_set_motion_blur_show_in_editor, bool) + FUNC2(camera_attributes_set_motion_blur_framerate_mode, MotionBlurFramerateMode, int) + 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 ed6c00e91f..bacd1419eb 100644 --- a/servers/rendering/storage/camera_attributes_storage.cpp +++ b/servers/rendering/storage/camera_attributes_storage.cpp @@ -60,6 +60,85 @@ void RendererCameraAttributes::camera_attributes_initialize(RID p_rid) { void RendererCameraAttributes::camera_attributes_free(RID p_rid) { camera_attributes_owner.free(p_rid); } +void RendererCameraAttributes::camera_attributes_set_motion_blur_framerate_mode(RS::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(RS::MotionBlurQuality p_quality) { + motion_blur_quality = p_quality; +} + +void RendererCameraAttributes::camera_attributes_set_motion_blur_tile_size(RS::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 (p_enable && (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "mobile")) { + 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; +} + +bool RendererCameraAttributes::camera_attributes_get_motion_blur_clamp_velocities_to_tile(RID p_camera_attributes) { + CameraAttributes *cam_attributes = camera_attributes_owner.get_or_null(p_camera_attributes); + ERR_FAIL_NULL_V(cam_attributes, false); + return cam_attributes->motion_blur_clamp_velocities_to_tile; +} + +float RendererCameraAttributes::camera_attributes_get_motion_blur_object_velocity_multiplier(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_object_velocity_multiplier; +} + +float RendererCameraAttributes::camera_attributes_get_motion_blur_movement_velocity_multiplier(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_movement_velocity_multiplier; +} + +float RendererCameraAttributes::camera_attributes_get_motion_blur_rotation_velocity_multiplier(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_rotation_velocity_multiplier; +} + +float RendererCameraAttributes::camera_attributes_get_motion_blur_velocity_lower_threshold(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_velocity_lower_threshold; +} + +float RendererCameraAttributes::camera_attributes_get_motion_blur_velocity_upper_threshold(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_velocity_upper_threshold; +} void RendererCameraAttributes::camera_attributes_set_dof_blur_quality(RS::DOFBlurQuality p_quality, bool p_use_jitter) { dof_blur_quality = p_quality; diff --git a/servers/rendering/storage/camera_attributes_storage.h b/servers/rendering/storage/camera_attributes_storage.h index b674e4c9ed..de133e439c 100644 --- a/servers/rendering/storage/camera_attributes_storage.h +++ b/servers/rendering/storage/camera_attributes_storage.h @@ -57,6 +57,15 @@ 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; @@ -66,6 +75,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; @@ -86,6 +101,26 @@ class RendererCameraAttributes { void camera_attributes_initialize(RID p_rid); void camera_attributes_free(RID p_rid); + void camera_attributes_set_motion_blur_show_in_editor(bool p_enabled); + void camera_attributes_set_motion_blur_framerate_mode(RS::MotionBlurFramerateMode p_mode, int p_reference_framerate); + 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); @@ -120,6 +155,26 @@ class RendererCameraAttributes { return cam_attributes && cam_attributes->use_auto_exposure; } + _FORCE_INLINE_ bool camera_attributes_get_motion_blur_show_in_editor() { + return motion_blur_show_in_editor; + } + + _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_ 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 5fdaa61043..3907461f67 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3192,9 +3192,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_show_in_editor", "enabled"), &RenderingServer::camera_attributes_set_motion_blur_show_in_editor); + 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_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); @@ -3205,6 +3211,19 @@ 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); + + BIND_ENUM_CONSTANT(MOTION_BLUR_FRAMERATE_MODE_NATIVE); + BIND_ENUM_CONSTANT(MOTION_BLUR_FRAMERATE_MODE_CAPPED); + BIND_ENUM_CONSTANT(MOTION_BLUR_FRAMERATE_MODE_FIXED); + /* SCENARIO */ ClassDB::bind_method(D_METHOD("scenario_create"), &RenderingServer::scenario_create); @@ -3706,6 +3725,11 @@ 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(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 ebb56343bb..fe7a7b3229 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -1447,6 +1447,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_show_in_editor(bool p_enabled) = 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_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, @@ -2070,6 +2096,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);