diff options
4 files changed, 156 insertions, 1 deletions
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 33a0ccec24b..fd732a09e08 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -318,6 +318,7 @@ set(GLSL_SRC shaders/compositor/compositor_bokeh_image.glsl shaders/compositor/compositor_box_mask.glsl shaders/compositor/compositor_convert.glsl + shaders/compositor/compositor_directional_blur.glsl shaders/compositor/compositor_edge_filter.glsl shaders/compositor/compositor_ellipse_mask.glsl shaders/compositor/compositor_filter.glsl @@ -567,6 +568,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/compositor/infos/compositor_bokeh_image_info.hh shaders/compositor/infos/compositor_box_mask_info.hh shaders/compositor/infos/compositor_convert_info.hh + shaders/compositor/infos/compositor_directional_blur_info.hh shaders/compositor/infos/compositor_edge_filter_info.hh shaders/compositor/infos/compositor_ellipse_mask_info.hh shaders/compositor/infos/compositor_filter_info.hh diff --git a/source/blender/gpu/shaders/compositor/compositor_directional_blur.glsl b/source/blender/gpu/shaders/compositor/compositor_directional_blur.glsl new file mode 100644 index 00000000000..1805cb5a7f5 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_directional_blur.glsl @@ -0,0 +1,21 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + ivec2 input_size = texture_size(input_tx); + + /* Add 0.5 to evaluate the input sampler at the center of the pixel. */ + vec2 coordinates = vec2(texel) + vec2(0.5); + + /* For each iteration, accumulate the input at the normalize coordinates, hence the divide by + * input size, then transform the coordinates for the next iteration. */ + vec4 accumulated_color = vec4(0.0); + for (int i = 0; i < iterations; i++) { + accumulated_color += texture(input_tx, coordinates / input_size); + coordinates = (mat3(inverse_transformation) * vec3(coordinates, 1.0)).xy; + } + + /* Write the accumulated color divided by the number of iterations. */ + imageStore(output_img, texel, accumulated_color / iterations); +} diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_directional_blur_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_directional_blur_info.hh new file mode 100644 index 00000000000..bb9199dcd26 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_directional_blur_info.hh @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_directional_blur) + .local_group_size(16, 16) + .push_constant(Type::INT, "iterations") + .push_constant(Type::MAT4, "inverse_transformation") + .sampler(0, ImageType::FLOAT_2D, "input_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .compute_source("compositor_directional_blur.glsl") + .do_static_compilation(true); diff --git a/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc b/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc index eacba5ad12d..d317c442ab3 100644 --- a/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc +++ b/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc @@ -5,10 +5,18 @@ * \ingroup cmpnodes */ +#include "BLI_float3x3.hh" +#include "BLI_math_base.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_math_vector.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_shader.h" + #include "COM_node_operation.hh" +#include "COM_utilities.hh" #include "node_composite_util.hh" @@ -61,7 +69,119 @@ class DirectionalBlurOperation : public NodeOperation { void execute() override { - get_input("Image").pass_through(get_result("Image")); + if (is_identity()) { + get_input("Image").pass_through(get_result("Image")); + return; + } + + GPUShader *shader = shader_manager().get("compositor_directional_blur"); + GPU_shader_bind(shader); + + /* The number of iterations does not cover the original image, that is, the image with no + * transformation. So add an extra iteration for the original image and put that into + * consideration in the shader. */ + GPU_shader_uniform_1i(shader, "iterations", get_iterations() + 1); + GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", get_transformation().ptr()); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_tx"); + + GPU_texture_filter_mode(input_image.texture(), true); + GPU_texture_wrap_mode(input_image.texture(), false, false); + + const Domain domain = compute_domain(); + Result &output_image = get_result("Image"); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, domain.size); + + GPU_shader_unbind(); + output_image.unbind_as_image(); + input_image.unbind_as_texture(); + } + + /* Get the amount of translation that will be applied on each iteration. The translation is in + * the negative x direction rotated in the clock-wise direction, hence the negative sign for the + * rotation and translation vector. */ + float2 get_translation() + { + const float diagonal_length = math::length(float2(get_input("Image").domain().size)); + const float translation_amount = diagonal_length * get_node_directional_blur_data().distance; + const float3x3 rotation = float3x3::from_rotation(-get_node_directional_blur_data().angle); + return rotation * float2(-translation_amount / get_iterations(), 0.0f); + } + + /* Get the amount of rotation that will be applied on each iteration. */ + float get_rotation() + { + return get_node_directional_blur_data().spin / get_iterations(); + } + + /* Get the amount of scale that will be applied on each iteration. The scale is identity when the + * user supplies 0, so we add 1. */ + float2 get_scale() + { + return float2(1.0f + get_node_directional_blur_data().zoom / get_iterations()); + } + + float2 get_origin() + { + const float2 center = float2(get_node_directional_blur_data().center_x, + get_node_directional_blur_data().center_y); + return float2(get_input("Image").domain().size) * center; + } + + float3x3 get_transformation() + { + /* Construct the transformation that will be applied on each iteration. */ + const float3x3 transformation = float3x3::from_translation_rotation_scale( + get_translation(), get_rotation(), get_scale()); + /* Change the origin of the transformation to the user-specified origin. */ + const float3x3 origin_transformation = float3x3::from_origin_transformation(transformation, + get_origin()); + /* The shader will transform the coordinates, not the image itself, so take the inverse. */ + return origin_transformation.inverted(); + } + + /* The actual number of iterations is 2 to the power of the user supplied iterations. The power + * is implemented using a bit shift. But also make sure it doesn't exceed the upper limit which + * is the number of diagonal pixels. */ + int get_iterations() + { + const int iterations = 2 << (get_node_directional_blur_data().iter - 1); + const int upper_limit = math::ceil(math::length(float2(get_input("Image").domain().size))); + return math::min(iterations, upper_limit); + } + + /* Returns true if the operation does nothing and the input can be passed through. */ + bool is_identity() + { + const Result &input = get_input("Image"); + /* Single value inputs can't be blurred and are returned as is. */ + if (input.is_single_value()) { + return true; + } + + /* If any of the following options are non-zero, then the operation is not an identity. */ + if (get_node_directional_blur_data().distance != 0.0f) { + return false; + } + + if (get_node_directional_blur_data().spin != 0.0f) { + return false; + } + + if (get_node_directional_blur_data().zoom != 0.0f) { + return false; + } + + return true; + } + + NodeDBlurData &get_node_directional_blur_data() + { + return *static_cast<NodeDBlurData *>(bnode().storage); } }; |