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
This commit is contained in:
Omar Emara 2022-09-09 14:32:58 +02:00
parent 03f33a6f23
commit f4e5a86544
4 changed files with 169 additions and 5 deletions

View File

@ -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

View File

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

View File

@ -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);

View File

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