diff options
author | Omar Emara <mail@OmarEmara.dev> | 2022-08-18 12:53:08 +0300 |
---|---|---|
committer | Omar Emara <mail@OmarEmara.dev> | 2022-08-18 12:53:08 +0300 |
commit | 6cb0a122dfcd7d3e75e8bc0d12e09b75b97cee99 (patch) | |
tree | 9fe1ea108a1db3b69543bccf6415768a359cd3c0 /source | |
parent | 0aaff9a07d3bdf8588cef15d502aeb4fdab22e5e (diff) |
Realtime Compositor: Implement bokeh image node
This patch implements the bokeh image node for the realtime compositor.
Differential Revision: https://developer.blender.org/D15660
Reviewed By: Clement Foucault
Diffstat (limited to 'source')
4 files changed, 184 insertions, 1 deletions
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 92dacee1879..bbc27a26835 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -315,6 +315,7 @@ set(GLSL_SRC shaders/common/gpu_shader_common_mix_rgb.glsl shaders/compositor/compositor_alpha_crop.glsl + shaders/compositor/compositor_bokeh_image.glsl shaders/compositor/compositor_box_mask.glsl shaders/compositor/compositor_convert.glsl shaders/compositor/compositor_ellipse_mask.glsl @@ -561,6 +562,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/gpu_srgb_to_framebuffer_space_info.hh shaders/compositor/infos/compositor_alpha_crop_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 shaders/compositor/infos/compositor_ellipse_mask_info.hh diff --git a/source/blender/gpu/shaders/compositor/compositor_bokeh_image.glsl b/source/blender/gpu/shaders/compositor/compositor_bokeh_image.glsl new file mode 100644 index 00000000000..6e98aa9fe17 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_bokeh_image.glsl @@ -0,0 +1,118 @@ +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +/* Get the 2D vertex position of the vertex with the given index in the regular polygon + * representing this bokeh. The polygon is rotated by the rotation amount and have a unit + * circumradius. The regular polygon is one whose vertices' exterior angles are given by + * exterior_angle. See the bokeh function for more information. */ +vec2 get_regular_polygon_vertex_position(int vertex_index) +{ + float angle = exterior_angle * vertex_index - rotation; + return vec2(cos(angle), sin(angle)); +} + +/* Find the closest point to the given point on the given line. This assumes the length of the + * given line is not zero. */ +vec2 closest_point_on_line(vec2 point, vec2 line_start, vec2 line_end) +{ + vec2 line_vector = line_end - line_start; + vec2 point_vector = point - line_start; + float line_length_squared = dot(line_vector, line_vector); + float parameter = dot(point_vector, line_vector) / line_length_squared; + return line_start + line_vector * parameter; +} + +/* Compute the value of the bokeh at the given point. The computed bokeh is essentially a regular + * polygon centered in space having the given circumradius. The regular polygon is one whose + * vertices' exterior angles are given by "exterior_angle", which relates to the number of vertices + * n through the equation "exterior angle = 2 pi / n". The regular polygon may additionally morph + * into a shape with the given properties: + * + * - The regular polygon may have a circular hole in its center whose radius is controlled by the + * "catadioptric" value. + * - The regular polygon is rotated by the "rotation" value. + * - The regular polygon can morph into a circle controlled by the "roundness" value, such that it + * becomes a full circle at unit roundness. + * + * The function returns 0 when the point lies inside the regular polygon and 1 otherwise. However, + * at the edges, it returns a narrow band gradient as a form of anti-aliasing. */ +float bokeh(vec2 point, float circumradius) +{ + /* Get the index of the vertex of the regular polygon whose polar angle is maximum but less than + * the polar angle of the given point, taking rotation into account. This essentially finds the + * vertex closest to the given point in the clock-wise direction. */ + float angle = mod(atan(point.y, point.x) + rotation, M_2PI); + int vertex_index = int(angle / exterior_angle); + + /* Compute the shortest distance between the origin and the polygon edge composed from the + * previously selected vertex and the one following it. */ + vec2 first_vertex = get_regular_polygon_vertex_position(vertex_index) * circumradius; + vec2 second_vertex = get_regular_polygon_vertex_position(vertex_index + 1) * circumradius; + vec2 closest_point = closest_point_on_line(point, first_vertex, second_vertex); + float distance_to_edge = length(closest_point); + + /* Mix the distance to the edge with the circumradius, making it tend to the distance to a + * circle when roundness tends to 1. */ + float distance_to_edge_round = mix(distance_to_edge, circumradius, roundness); + + /* The point is outside of the bokeh, so we return 0. */ + float distance = length(point); + if (distance > distance_to_edge_round) { + return 0.0; + } + + /* The point is inside the catadioptric hole and is not part of the bokeh, so we return 0. */ + float catadioptric_distance = distance_to_edge_round * catadioptric; + if (distance < catadioptric_distance) { + return 0.0; + } + + /* The point is very close to the edge of the bokeh, so we return the difference between the + * distance to the edge and the distance as a form of anti-aliasing. */ + if (distance_to_edge_round - distance < 1.0) { + return distance_to_edge_round - distance; + } + + /* The point is very close to the edge of the catadioptric hole, so we return the difference + * between the distance to the hole and the distance as a form of anti-aliasing. */ + if (catadioptric != 0.0 && distance - catadioptric_distance < 1.0) { + return distance - catadioptric_distance; + } + + /* Otherwise, the point is part of the bokeh and we return 1. */ + return 1.0; +} + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + + /* Since we need the regular polygon to occupy the entirety of the output image, the circumradius + * of the regular polygon is half the width of the output image. */ + float circumradius = float(imageSize(output_img).x) / 2.0; + + /* Move the texel coordinates such that the regular polygon is centered. */ + vec2 point = vec2(texel) - circumradius; + + /* Each of the color channels of the output image contains a bokeh with a different circumradius. + * The largest one occupies the whole image as stated above, while the other two have circumradii + * that are shifted by an amount that is proportional to the "lens_shift" value. The alpha + * channel of the output is the average of all three values. */ + float min_shift = abs(lens_shift * circumradius); + float min = mix(bokeh(point, circumradius - min_shift), 0.0, min_shift == circumradius); + + float median_shift = min_shift / 2.0; + float median = bokeh(point, circumradius - median_shift); + + float max = bokeh(point, circumradius); + vec4 bokeh = vec4(min, median, max, (max + median + min) / 3.0); + + /* If the lens shift is negative, swap the min and max bokeh values, which are stored in the red + * and blue channels respectively. Note that we take the absolute value of the lens shift above, + * so the sign of the lens shift only controls this swap. */ + if (lens_shift < 0) { + bokeh = bokeh.zyxw; + } + + imageStore(output_img, texel, bokeh); +} diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_bokeh_image_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_bokeh_image_info.hh new file mode 100644 index 00000000000..3541de53070 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_bokeh_image_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_bokeh_image) + .local_group_size(16, 16) + .push_constant(Type::FLOAT, "exterior_angle") + .push_constant(Type::FLOAT, "rotation") + .push_constant(Type::FLOAT, "roundness") + .push_constant(Type::FLOAT, "catadioptric") + .push_constant(Type::FLOAT, "lens_shift") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .compute_source("compositor_bokeh_image.glsl") + .do_static_compilation(true); diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc b/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc index a11cba37191..ddb1e569c52 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc +++ b/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc @@ -5,10 +5,16 @@ * \ingroup cmpnodes */ +#include "BLI_math_base.h" +#include "BLI_math_vec_types.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" @@ -55,7 +61,50 @@ class BokehImageOperation : public NodeOperation { void execute() override { - get_result("Image").allocate_invalid(); + GPUShader *shader = shader_manager().get("compositor_bokeh_image"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1f(shader, "exterior_angle", get_exterior_angle()); + GPU_shader_uniform_1f(shader, "rotation", get_rotation()); + GPU_shader_uniform_1f(shader, "roundness", get_node_bokeh_image().rounding); + GPU_shader_uniform_1f(shader, "catadioptric", get_node_bokeh_image().catadioptric); + GPU_shader_uniform_1f(shader, "lens_shift", get_node_bokeh_image().lensshift); + + Result &output = get_result("Image"); + const Domain domain = compute_domain(); + output.allocate_texture(domain); + output.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, domain.size); + + output.unbind_as_image(); + GPU_shader_unbind(); + } + + Domain compute_domain() override + { + return Domain(int2(512)); + } + + NodeBokehImage &get_node_bokeh_image() + { + return *static_cast<NodeBokehImage *>(bnode().storage); + } + + /* The exterior angle is the angle between each two consective vertices of the regular polygon + * from its center. */ + float get_exterior_angle() + { + return (M_PI * 2.0f) / get_node_bokeh_image().flaps; + } + + float get_rotation() + { + /* Offset the rotation such that the second vertex of the regular polygon lies on the positive + * y axis, which is 90 degrees minus the angle that it makes with the positive x axis assuming + * the first vertex lies on the positive x axis. */ + const float offset = M_PI_2 - get_exterior_angle(); + return get_node_bokeh_image().angle - offset; } }; |