diff options
4 files changed, 169 insertions, 5 deletions
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index cb5bb4331f9..8b38c22ae28 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -326,6 +326,7 @@ set(GLSL_SRC shaders/compositor/compositor_alpha_crop.glsl shaders/compositor/compositor_bilateral_blur.glsl + shaders/compositor/compositor_blur.glsl shaders/compositor/compositor_bokeh_image.glsl shaders/compositor/compositor_box_mask.glsl shaders/compositor/compositor_convert.glsl @@ -604,6 +605,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/compositor/infos/compositor_alpha_crop_info.hh shaders/compositor/infos/compositor_bilateral_blur_info.hh + shaders/compositor/infos/compositor_blur_info.hh shaders/compositor/infos/compositor_bokeh_image_info.hh shaders/compositor/infos/compositor_box_mask_info.hh shaders/compositor/infos/compositor_convert_info.hh diff --git a/source/blender/gpu/shaders/compositor/compositor_blur.glsl b/source/blender/gpu/shaders/compositor/compositor_blur.glsl new file mode 100644 index 00000000000..4f981c84f59 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_blur.glsl @@ -0,0 +1,55 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +vec4 load_input(ivec2 texel) +{ + vec4 color; + if (extend_bounds) { + /* If bounds are extended, then we treat the input as padded by a radius amount of pixels. So + * we load the input with an offset by the radius amount and fallback to a transparent color if + * it is out of bounds. */ + color = texture_load(input_tx, texel - radius, vec4(0.0)); + } + else { + color = texture_load(input_tx, texel); + } + + return color; +} + +/* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight from + * the weights texture, where the texel (0, 0) is considered the center of weights texture. */ +vec4 load_weight(ivec2 texel) +{ + /* Add the radius to transform the texel into the range [0, radius * 2], then divide by the upper + * bound plus one to transform the texel into the normalized range [0, 1] needed to sample the + * weights sampler. Finally, also add 0.5 to sample at the center of the pixels. */ + return texture(weights_tx, (texel + vec2(radius + 0.5)) / (radius * 2 + 1)); +} + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + + /* The mask input is treated as a boolean. If it is zero, then no blurring happens for this + * pixel. Otherwise, the pixel is blurred normally and the mask value is irrelevant. */ + float mask = texture_load(mask_tx, texel).x; + if (mask == 0.0) { + imageStore(output_img, texel, texture_load(input_tx, texel)); + return; + } + + /* Go over the window of the given radius and accumulate the colors multiplied by their + * respective weights as well as the weights themselves. */ + vec4 accumulated_color = vec4(0.0); + vec4 accumulated_weight = vec4(0.0); + for (int y = -radius; y <= radius; y++) { + for (int x = -radius; x <= radius; x++) { + vec4 weight = load_weight(ivec2(x, y)); + accumulated_color += load_input(texel + ivec2(x, y)) * weight; + accumulated_weight += weight; + } + } + + imageStore(output_img, texel, safe_divide(accumulated_color, accumulated_weight)); +} diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh new file mode 100644 index 00000000000..36b772aa486 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_blur) + .local_group_size(16, 16) + .push_constant(Type::INT, "radius") + .push_constant(Type::BOOL, "extend_bounds") + .sampler(0, ImageType::FLOAT_2D, "input_tx") + .sampler(1, ImageType::FLOAT_2D, "weights_tx") + .sampler(2, ImageType::FLOAT_2D, "mask_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .compute_source("compositor_blur.glsl") + .do_static_compilation(true); diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc index 538f00af12d..182169405de 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc +++ b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc @@ -5,10 +5,16 @@ * \ingroup cmpnodes */ +#include "BLI_math_base.hh" +#include "BLI_math_vec_types.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_texture.h" + #include "COM_node_operation.hh" +#include "COM_utilities.hh" #include "node_composite_util.hh" @@ -18,10 +24,22 @@ namespace blender::nodes::node_composite_bokehblur_cc { static void cmp_node_bokehblur_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::Color>(N_("Bokeh")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Size")).default_value(1.0f).min(0.0f).max(10.0f); - b.add_input<decl::Float>(N_("Bounding box")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Bokeh")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_skip_realization(); + b.add_input<decl::Float>(N_("Size")) + .default_value(1.0f) + .min(0.0f) + .max(10.0f) + .compositor_domain_priority(1); + b.add_input<decl::Float>(N_("Bounding box")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(2); b.add_output<decl::Color>(N_("Image")); } @@ -47,7 +65,82 @@ class BokehBlurOperation : 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_blur"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1i(shader, "radius", compute_blur_radius()); + GPU_shader_uniform_1b(shader, "extend_bounds", get_extend_bounds()); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_tx"); + + const Result &input_weights = get_input("Bokeh"); + input_weights.bind_as_texture(shader, "weights_tx"); + + const Result &input_mask = get_input("Bounding box"); + input_mask.bind_as_texture(shader, "mask_tx"); + + Domain domain = compute_domain(); + if (get_extend_bounds()) { + /* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */ + domain.size += int2(compute_blur_radius() * 2); + } + + 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(); + input_weights.unbind_as_texture(); + input_mask.unbind_as_texture(); + } + + int compute_blur_radius() + { + const int2 image_size = get_input("Image").domain().size; + const int max_size = math::max(image_size.x, image_size.y); + + /* The [0, 10] range of the size is arbitrary and is merely in place to avoid very long + * computations of the bokeh blur. */ + const float size = math::clamp(get_input("Size").get_float_value_default(1.0f), 0.0f, 10.0f); + + /* The 100 divisor is arbitrary and was chosen using visual judgement. */ + return size * (max_size / 100.0f); + } + + bool is_identity() + { + const Result &input = get_input("Image"); + if (input.is_single_value()) { + return true; + } + + if (compute_blur_radius() == 0) { + return true; + } + + /* This input is, in fact, a boolean mask. If it is zero, no blurring will take place. + * Otherwise, the blurring will take place ignoring the value of the input entirely. */ + const Result &bounding_box = get_input("Bounding box"); + if (bounding_box.is_single_value() && bounding_box.get_float_value() == 0.0) { + return true; + } + + return false; + } + + bool get_extend_bounds() + { + return bnode().custom1 & CMP_NODEFLAG_BLUR_EXTEND_BOUNDS; } }; |