[Bf-blender-cvs] [f4e5a865448] master: Realtime Compositor: Implement bokeh blur node

Omar Emara noreply at git.blender.org
Fri Sep 9 14:34:12 CEST 2022


Commit: f4e5a8654487ef8b66cf5fb2ca3f4d66cb207618
Author: Omar Emara
Date:   Fri Sep 9 14:32:58 2022 +0200
Branches: master
https://developer.blender.org/rBf4e5a8654487ef8b66cf5fb2ca3f4d66cb207618

Realtime Compositor: Implement bokeh blur node

This patch implements the bokeh blur node for the realtime compositor.
The patch is still missing the Variable Size option because it depends
on the Levels node, which is yet to be implemented. In particular, it
requires the computation of global texture properties like the maximum
color.

Differential Revision: https://developer.blender.org/D15768

Reviewed By: Clement Foucault

===================================================================

M	source/blender/gpu/CMakeLists.txt
A	source/blender/gpu/shaders/compositor/compositor_blur.glsl
A	source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh
M	source/blender/nodes/composite/nodes/node_composite_bokehblur.cc

===================================================================

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;
   }
 };



More information about the Bf-blender-cvs mailing list