From 5462d90fa8e8270e47539622533ddd0548f03d43 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Wed, 24 Sep 2025 14:45:54 +0200 Subject: [PATCH] Compositor: Make Bokeh Blur size input in pixels This patch makes the Size input of the Bokeh Blur node defined in pixels as opposed to being relative to 0.01 the greater dimension of the image. Pull Request: https://projects.blender.org/blender/blender/pulls/146367 --- .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenloader/intern/versioning_500.cc | 90 +++++++++++++++++++ .../compositor_bokeh_blur_variable_size.glsl | 4 +- ...ompositor_bokeh_blur_variable_size_info.hh | 1 - .../nodes/node_composite_bokehblur.cc | 88 +++++++----------- 5 files changed, 124 insertions(+), 61 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 2695dafb55a..1b0a6e09ba9 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 88 +#define BLENDER_FILE_SUBVERSION 89 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index a01503d7fb0..a3ac12872c0 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -2366,6 +2366,81 @@ static void do_version_displace_node_remove_xy_scale(bNodeTree &node_tree, bNode } } +/* The Size input is now in pixels, while previously, it was relative to 0.01 of the greater image + * dimension. */ +static void do_version_bokeh_blur_pixel_size(bNodeTree &node_tree, bNode &node) +{ + blender::bke::node_tree_set_type(node_tree); + + bNodeSocket *image_input = blender::bke::node_find_socket(node, SOCK_IN, "Image"); + bNodeSocket *size_input = blender::bke::node_find_socket(node, SOCK_IN, "Size"); + + /* Find the link going into the inputs of the node. */ + bNodeLink *image_link = nullptr; + bNodeLink *size_link = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { + if (link->tosock == size_input) { + size_link = link; + } + if (link->tosock == image_input) { + image_link = link; + } + } + + bNode &multiply_node = version_node_add_empty(node_tree, "ShaderNodeMath"); + multiply_node.parent = node.parent; + multiply_node.location[0] = node.location[0] - node.width - 20.0f; + multiply_node.location[1] = node.location[1]; + multiply_node.custom1 = NODE_MATH_MULTIPLY; + + bNodeSocket &multiply_a_input = version_node_add_socket( + node_tree, multiply_node, SOCK_IN, "NodeSocketFloat", "Value"); + bNodeSocket &multiply_b_input = version_node_add_socket( + node_tree, multiply_node, SOCK_IN, "NodeSocketFloat", "Value_001"); + bNodeSocket &multiply_output = version_node_add_socket( + node_tree, multiply_node, SOCK_OUT, "NodeSocketFloat", "Value"); + + multiply_a_input.default_value_typed()->value = + size_input->default_value_typed()->value; + if (size_link) { + version_node_add_link( + node_tree, *size_link->fromnode, *size_link->fromsock, multiply_node, multiply_a_input); + blender::bke::node_remove_link(&node_tree, *size_link); + } + + version_node_add_link(node_tree, multiply_node, multiply_output, node, *size_input); + + bNode *relative_to_pixel_node = blender::bke::node_add_node( + nullptr, node_tree, "CompositorNodeRelativeToPixel"); + relative_to_pixel_node->parent = node.parent; + relative_to_pixel_node->location[0] = multiply_node.location[0] - multiply_node.width - 20.0f; + relative_to_pixel_node->location[1] = multiply_node.location[1]; + relative_to_pixel_node->custom1 = CMP_NODE_RELATIVE_TO_PIXEL_DATA_TYPE_FLOAT; + relative_to_pixel_node->custom2 = CMP_NODE_RELATIVE_TO_PIXEL_REFERENCE_DIMENSION_GREATER; + + bNodeSocket *relative_to_pixel_image_input = blender::bke::node_find_socket( + *relative_to_pixel_node, SOCK_IN, "Image"); + bNodeSocket *relative_to_pixel_value_input = blender::bke::node_find_socket( + *relative_to_pixel_node, SOCK_IN, "Float Value"); + bNodeSocket *relative_to_pixel_value_output = blender::bke::node_find_socket( + *relative_to_pixel_node, SOCK_OUT, "Float Value"); + + version_node_add_link(node_tree, + *relative_to_pixel_node, + *relative_to_pixel_value_output, + multiply_node, + multiply_b_input); + + relative_to_pixel_value_input->default_value_typed()->value = 0.01f; + if (image_link) { + version_node_add_link(node_tree, + *image_link->fromnode, + *image_link->fromsock, + *relative_to_pixel_node, + *relative_to_pixel_image_input); + } +} + void do_versions_after_linking_500(FileData *fd, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 9)) { @@ -3514,6 +3589,21 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 89)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type != NTREE_COMPOSIT) { + continue; + } + version_node_input_socket_name(node_tree, CMP_NODE_BOKEHBLUR, "Bounding box", "Mask"); + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_BOKEHBLUR) { + do_version_bokeh_blur_pixel_size(*node_tree, *node); + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/compositor/shaders/compositor_bokeh_blur_variable_size.glsl b/source/blender/compositor/shaders/compositor_bokeh_blur_variable_size.glsl index 0c4909a76dc..b7d61f763ed 100644 --- a/source/blender/compositor/shaders/compositor_bokeh_blur_variable_size.glsl +++ b/source/blender/compositor/shaders/compositor_bokeh_blur_variable_size.glsl @@ -47,7 +47,7 @@ void main() return; } - float center_size = max(0.0f, texture_load(size_tx, texel).x * base_size); + float center_size = max(0.0f, texture_load(size_tx, texel).x); /* Go over the window of the given search radius and accumulate the colors multiplied by their * respective weights as well as the weights themselves, but only if both the size of the center @@ -57,7 +57,7 @@ void main() float4 accumulated_weight = float4(0.0f); for (int y = -search_radius; y <= search_radius; y++) { for (int x = -search_radius; x <= search_radius; x++) { - float candidate_size = max(0.0f, texture_load(size_tx, texel + int2(x, y)).x * base_size); + float candidate_size = max(0.0f, texture_load(size_tx, texel + int2(x, y)).x); /* Skip accumulation if either the x or y distances of the candidate pixel are larger than * either the center or candidate pixel size. Note that the max and min functions here denote diff --git a/source/blender/compositor/shaders/infos/compositor_bokeh_blur_variable_size_info.hh b/source/blender/compositor/shaders/infos/compositor_bokeh_blur_variable_size_info.hh index 384582e89f5..4e36448a8a3 100644 --- a/source/blender/compositor/shaders/infos/compositor_bokeh_blur_variable_size_info.hh +++ b/source/blender/compositor/shaders/infos/compositor_bokeh_blur_variable_size_info.hh @@ -6,7 +6,6 @@ GPU_SHADER_CREATE_INFO(compositor_bokeh_blur_variable_size) LOCAL_GROUP_SIZE(16, 16) -PUSH_CONSTANT(float, base_size) PUSH_CONSTANT(int, search_radius) SAMPLER(0, sampler2D, input_tx) SAMPLER(1, sampler2D, weights_tx) diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc index 8b2ff9cac64..29338838628 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc +++ b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc @@ -2,15 +2,9 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -/** \file - * \ingroup cmpnodes - */ - #include "BLI_math_base.hh" #include "BLI_math_vector_types.hh" -#include "UI_resources.hh" - #include "COM_algorithm_pad.hh" #include "COM_algorithm_parallel_reduction.hh" #include "COM_node_operation.hh" @@ -18,8 +12,6 @@ #include "node_composite_util.hh" -/* **************** BLUR ******************** */ - namespace blender::nodes::node_composite_bokehblur_cc { static void cmp_node_bokehblur_declare(NodeDeclarationBuilder &b) @@ -31,13 +23,10 @@ static void cmp_node_bokehblur_declare(NodeDeclarationBuilder &b) .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .compositor_realization_mode(CompositorInputRealizationMode::Transforms) .structure_type(StructureType::Dynamic); - b.add_input("Size").default_value(1.0f).min(0.0f).max(10.0f).structure_type( + b.add_input("Size").default_value(0.0f).min(0.0f).structure_type( + StructureType::Dynamic); + b.add_input("Mask").default_value(1.0f).min(0.0f).max(1.0f).structure_type( StructureType::Dynamic); - b.add_input("Bounding box") - .default_value(1.0f) - .min(0.0f) - .max(1.0f) - .structure_type(StructureType::Dynamic); b.add_input("Extend Bounds").default_value(false); b.add_output("Image").structure_type(StructureType::Dynamic); @@ -85,7 +74,7 @@ class BokehBlurOperation : public NodeOperation { /* For constant sized blur, the extension should just be the blur radius. */ if (size.is_single_value()) { - return int(math::ceil(this->compute_blur_radius())); + return this->get_blur_radius(); } /* For variable sized blur, the extension should be the bokeh search radius. */ @@ -95,10 +84,10 @@ class BokehBlurOperation : public NodeOperation { void execute_blur(const Result &input, const Result &size) { if (size.is_single_value()) { - execute_constant_size(input); + this->execute_constant_size(input); } else { - execute_variable_size(input, size); + this->execute_variable_size(input, size); } } @@ -117,18 +106,18 @@ class BokehBlurOperation : public NodeOperation { gpu::Shader *shader = context().get_shader("compositor_bokeh_blur"); GPU_shader_bind(shader); - GPU_shader_uniform_1i(shader, "radius", int(compute_blur_radius())); + GPU_shader_uniform_1i(shader, "radius", this->get_blur_radius()); input.bind_as_texture(shader, "input_tx"); - const Result &input_weights = get_input("Bokeh"); + const Result &input_weights = this->get_input("Bokeh"); input_weights.bind_as_texture(shader, "weights_tx"); - const Result &input_mask = get_input("Bounding box"); + const Result &input_mask = this->get_input("Mask"); input_mask.bind_as_texture(shader, "mask_tx"); const Domain domain = input.domain(); - Result &output_image = get_result("Image"); + Result &output_image = this->get_result("Image"); output_image.allocate_texture(domain); output_image.bind_as_image(shader, "output_img"); @@ -143,9 +132,9 @@ class BokehBlurOperation : public NodeOperation { void execute_constant_size_cpu(const Result &input) { - const int radius = int(this->compute_blur_radius()); + const int radius = this->get_blur_radius(); - const Result &mask_image = this->get_input("Bounding box"); + const Result &mask_image = this->get_input("Mask"); const Domain domain = input.domain(); Result &output = this->get_result("Image"); @@ -192,26 +181,25 @@ class BokehBlurOperation : public NodeOperation { void execute_variable_size_gpu(const Result &input, const Result &size) { - const int search_radius = compute_variable_size_search_radius(); + const int search_radius = this->compute_variable_size_search_radius(); - gpu::Shader *shader = context().get_shader("compositor_bokeh_blur_variable_size"); + gpu::Shader *shader = this->context().get_shader("compositor_bokeh_blur_variable_size"); GPU_shader_bind(shader); - GPU_shader_uniform_1f(shader, "base_size", compute_blur_radius()); GPU_shader_uniform_1i(shader, "search_radius", search_radius); input.bind_as_texture(shader, "input_tx"); - const Result &input_weights = get_input("Bokeh"); + const Result &input_weights = this->get_input("Bokeh"); input_weights.bind_as_texture(shader, "weights_tx"); size.bind_as_texture(shader, "size_tx"); - const Result &input_mask = get_input("Bounding box"); + const Result &input_mask = this->get_input("Mask"); input_mask.bind_as_texture(shader, "mask_tx"); const Domain domain = input.domain(); - Result &output_image = get_result("Image"); + Result &output_image = this->get_result("Image"); output_image.allocate_texture(domain); output_image.bind_as_image(shader, "output_img"); @@ -227,14 +215,13 @@ class BokehBlurOperation : public NodeOperation { void execute_variable_size_cpu(const Result &input, const Result &size_input) { - const float base_size = this->compute_blur_radius(); const int search_radius = this->compute_variable_size_search_radius(); - const Result &weights = get_input("Bokeh"); - const Result &mask_image = get_input("Bounding box"); + const Result &weights = this->get_input("Bokeh"); + const Result &mask_image = this->get_input("Mask"); const Domain domain = input.domain(); - Result &output = get_result("Image"); + Result &output = this->get_result("Image"); output.allocate_texture(domain); /* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight @@ -275,7 +262,7 @@ class BokehBlurOperation : public NodeOperation { return; } - float center_size = math::max(0.0f, size_input.load_pixel(texel) * base_size); + float center_size = math::max(0.0f, size_input.load_pixel(texel)); /* Go over the window of the given search radius and accumulate the colors multiplied by * their respective weights as well as the weights themselves, but only if both the size of @@ -286,7 +273,7 @@ class BokehBlurOperation : public NodeOperation { for (int y = -search_radius; y <= search_radius; y++) { for (int x = -search_radius; x <= search_radius; x++) { float candidate_size = math::max( - 0.0f, size_input.load_pixel_extended(texel + int2(x, y)) * base_size); + 0.0f, size_input.load_pixel_extended(texel + int2(x, y))); /* Skip accumulation if either the x or y distances of the candidate pixel are larger * than either the center or candidate pixel size. Note that the max and min functions @@ -320,7 +307,7 @@ class BokehBlurOperation : public NodeOperation { { const Result &bokeh = this->get_input("Bokeh"); - Result kernel = context().create_result(ResultType::Color); + Result kernel = this->context().create_result(ResultType::Color); const int2 kernel_size = int2(radius * 2 + 1); kernel.allocate_texture(kernel_size); parallel_for(kernel_size, [&](const int2 texel) { @@ -338,41 +325,28 @@ class BokehBlurOperation : public NodeOperation { int compute_variable_size_search_radius() { - const Result &input_size = get_input("Size"); - const float maximum_size = maximum_float(context(), input_size); - - const float base_size = compute_blur_radius(); - return math::max(0, int(maximum_size * base_size)); + return math::max(0, int(maximum_float(context(), this->get_input("Size")))); } - float compute_blur_radius() + int get_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_single_value_default(1.0f), 0.0f, 10.0f); - - /* The 100 divisor is arbitrary and was chosen using visual judgment. */ - return size * (max_size / 100.0f); + return math::max(0, int(this->get_input("Size").get_single_value())); } bool is_identity() { - const Result &input = get_input("Image"); + const Result &input = this->get_input("Image"); if (input.is_single_value()) { return true; } - if (compute_blur_radius() == 0.0f) { + const Result &size = this->get_input("Size"); + if (size.is_single_value() && this->get_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_single_value() == 0.0) { + const Result &mask = this->get_input("Mask"); + if (mask.is_single_value() && mask.get_single_value() == 0.0) { return true; }