Compositor: Unify Bokeh Image node between CPU and GPU

This patch unifies the CPU and GPU implementation for the Bokeh Image
node. The bokeh is now evaluated at the center of pixels and allows
different sizes for the output kernel.
This commit is contained in:
Omar Emara 2024-03-28 13:23:58 +02:00
parent 1b797cc0c3
commit 042c50b65f
4 changed files with 157 additions and 164 deletions

View File

@ -4,7 +4,10 @@
#include "COM_BokehImageOperation.h"
#include "BLI_math_geom.h"
#include "BLI_math_base.h"
#include "BLI_math_base.hh"
#include "BLI_math_numbers.hh"
#include "BLI_math_vector.hh"
namespace blender::compositor {
@ -13,91 +16,151 @@ BokehImageOperation::BokehImageOperation()
this->add_output_socket(DataType::Color);
delete_data_ = false;
}
/* The exterior angle is the angle between each two consecutive vertices of the regular polygon
* from its center. */
static float compute_exterior_angle(int sides)
{
return (math::numbers::pi * 2.0f) / sides;
}
static float compute_rotation(float angle, int sides)
{
/* 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 = math::numbers::pi / 2.0f - compute_exterior_angle(sides);
return angle - offset;
}
void BokehImageOperation::init_execution()
{
center_[0] = get_width() / 2;
center_[1] = get_height() / 2;
inverse_rounding_ = 1.0f - data_->rounding;
circular_distance_ = get_width() / 2;
flap_rad_ = float(M_PI * 2) / data_->flaps;
flap_rad_add_ = data_->angle;
while (flap_rad_add_ < 0.0f) {
flap_rad_add_ += float(M_PI * 2.0);
}
while (flap_rad_add_ > float(M_PI)) {
flap_rad_add_ -= float(M_PI * 2.0);
}
exterior_angle_ = compute_exterior_angle(data_->flaps);
rotation_ = compute_rotation(data_->angle, data_->flaps);
roundness_ = data_->rounding;
catadioptric_ = data_->catadioptric;
lens_shift_ = data_->lensshift;
}
void BokehImageOperation::detemine_start_point_of_flap(float r[2], int flap_number, float distance)
/* 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. */
float2 BokehImageOperation::get_regular_polygon_vertex_position(int vertex_index)
{
r[0] = sinf(flap_rad_ * flap_number + flap_rad_add_) * distance + center_[0];
r[1] = cosf(flap_rad_ * flap_number + flap_rad_add_) * distance + center_[1];
float angle = exterior_angle_ * vertex_index - rotation_;
return float2(math::cos(angle), math::sin(angle));
}
float BokehImageOperation::is_inside_bokeh(float distance, float x, float y)
/* Find the closest point to the given point on the given line. This assumes the length of the
* given line is not zero. */
float2 BokehImageOperation::closest_point_on_line(float2 point, float2 line_start, float2 line_end)
{
float inside_bokeh = 0.0f;
const float deltaX = x - center_[0];
const float deltaY = y - center_[1];
float closest_point[2];
float line_p1[2];
float line_p2[2];
float point[2];
point[0] = x;
point[1] = y;
const float distance_to_center = len_v2v2(point, center_);
const float bearing = (atan2f(deltaX, deltaY) + float(M_PI * 2.0));
int flap_number = int((bearing - flap_rad_add_) / flap_rad_);
detemine_start_point_of_flap(line_p1, flap_number, distance);
detemine_start_point_of_flap(line_p2, flap_number + 1, distance);
closest_to_line_v2(closest_point, point, line_p1, line_p2);
const float distance_line_to_center = len_v2v2(center_, closest_point);
const float distance_rounding_to_center = inverse_rounding_ * distance_line_to_center +
data_->rounding * distance;
const float catadioptric_distance_to_center = distance_rounding_to_center * data_->catadioptric;
if (distance_rounding_to_center >= distance_to_center &&
catadioptric_distance_to_center <= distance_to_center)
{
if (distance_rounding_to_center - distance_to_center < 1.0f) {
inside_bokeh = (distance_rounding_to_center - distance_to_center);
}
else if (data_->catadioptric != 0.0f &&
distance_to_center - catadioptric_distance_to_center < 1.0f)
{
inside_bokeh = (distance_to_center - catadioptric_distance_to_center);
}
else {
inside_bokeh = 1.0f;
}
}
return inside_bokeh;
float2 line_vector = line_end - line_start;
float2 point_vector = point - line_start;
float line_length_squared = math::dot(line_vector, line_vector);
float parameter = math::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 BokehImageOperation::bokeh(float2 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 = floored_fmod(math::atan2(point.y, point.x) + rotation_,
math::numbers::pi_v<float> * 2.0f);
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. */
float2 first_vertex = this->get_regular_polygon_vertex_position(vertex_index) * circumradius;
float2 second_vertex = this->get_regular_polygon_vertex_position(vertex_index + 1) *
circumradius;
float2 closest_point = this->closest_point_on_line(point, first_vertex, second_vertex);
float distance_to_edge = math::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 = math::interpolate(distance_to_edge, circumradius, roundness_);
/* The point is outside of the bokeh, so we return 0. */
float distance = math::length(point);
if (distance > distance_to_edge_round) {
return 0.0f;
}
/* 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.0f;
}
/* 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.0f) {
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.0f && distance - catadioptric_distance < 1.0f) {
return distance - catadioptric_distance;
}
/* Otherwise, the point is part of the bokeh and we return 1. */
return 1.0f;
}
void BokehImageOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> /*inputs*/)
{
const float shift = data_->lensshift;
const float shift2 = shift / 2.0f;
const float distance = circular_distance_;
/* 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(resolution_) / 2.0f;
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
const float inside_bokeh_max = is_inside_bokeh(distance, it.x, it.y);
const float inside_bokeh_med = is_inside_bokeh(
distance - fabsf(shift2 * distance), it.x, it.y);
const float inside_bokeh_min = is_inside_bokeh(distance - fabsf(shift * distance), it.x, it.y);
if (shift < 0) {
it.out[0] = inside_bokeh_max;
it.out[1] = inside_bokeh_med;
it.out[2] = inside_bokeh_min;
int2 texel = int2(it.x, it.y);
/* Move the texel coordinates such that the regular polygon is centered. */
float2 point = float2(texel) + float2(0.5f) - 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 = math::abs(lens_shift_ * circumradius);
float min = min_shift == circumradius ? 0.0f : this->bokeh(point, circumradius - min_shift);
float median_shift = min_shift / 2.0f;
float median = this->bokeh(point, circumradius - median_shift);
float max = this->bokeh(point, circumradius);
float4 bokeh = float4(min, median, max, (max + median + min) / 3.0f);
/* 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.0f) {
std::swap(bokeh.x, bokeh.z);
}
else {
it.out[0] = inside_bokeh_min;
it.out[1] = inside_bokeh_med;
it.out[2] = inside_bokeh_max;
}
it.out[3] = (inside_bokeh_max + inside_bokeh_med + inside_bokeh_min) / 3.0f;
copy_v4_v4(it.out, bokeh);
}
}
@ -111,13 +174,9 @@ void BokehImageOperation::deinit_execution()
}
}
void BokehImageOperation::determine_canvas(const rcti &preferred_area, rcti &r_area)
void BokehImageOperation::determine_canvas(const rcti & /*preferred_area*/, rcti &r_area)
{
BLI_rcti_init(&r_area,
preferred_area.xmin,
preferred_area.xmin + COM_BLUR_BOKEH_PIXELS,
preferred_area.ymin,
preferred_area.ymin + COM_BLUR_BOKEH_PIXELS);
BLI_rcti_init(&r_area, 0, resolution_, 0, resolution_);
}
} // namespace blender::compositor

View File

@ -4,93 +4,30 @@
#pragma once
#include "BLI_math_vector_types.hh"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
/**
* \brief The #BokehImageOperation class is an operation that creates an image useful to mimic the
* internals of a camera.
*
* features:
* - number of flaps
* - angle offset of the flaps
* - rounding of the flaps (also used to make a circular lens)
* - simulate catadioptric
* - simulate lens-shift
*
* Per pixel the algorithm determines the edge of the bokeh on the same line as the center of the
* image and the pixel is evaluating.
*
* The edge is detected by finding the closest point on the direct line between the two nearest
* flap-corners. this edge is interpolated with a full circle. Result of this edge detection is
* stored as the distance between the center of the image and the edge.
*
* catadioptric lenses are simulated to interpolate between the center of the image and the
* distance of the edge. We now have three distances:
* - Distance between the center of the image and the pixel to be evaluated.
* - Distance between the center of the image and the outer-edge.
* - Distance between the center of the image and the inner-edge.
*
* With a simple compare it can be detected if the evaluated pixel is between the outer and inner
* edge.
*/
class BokehImageOperation : public MultiThreadedOperation {
private:
/**
* \brief Settings of the bokeh image
*/
const NodeBokehImage *data_;
/**
* \brief precalculate center of the image
*/
float center_[2];
int resolution_ = COM_BLUR_BOKEH_PIXELS;
/**
* \brief 1.0-rounding
*/
float inverse_rounding_;
float exterior_angle_;
float rotation_;
float roundness_;
float catadioptric_;
float lens_shift_;
/**
* \brief distance of a full circle lens
*/
float circular_distance_;
/**
* \brief radius when the first flap starts
*/
float flap_rad_;
/**
* \brief radians of a single flap
*/
float flap_rad_add_;
/**
* \brief should the data_ field by deleted when this operation is finished
*/
/* See the delete_data_on_finish method. */
bool delete_data_;
/**
* \brief determine the coordinate of a flap corner.
*
* \param r: result in bokeh-image space are stored [x,y]
* \param flap_number: the flap number to calculate
* \param distance: the lens distance is used to simulate lens shifts
*/
void detemine_start_point_of_flap(float r[2], int flap_number, float distance);
/**
* \brief Determine if a coordinate is inside the bokeh image
*
* \param distance: the distance that will be used.
* This parameter is modified a bit to mimic lens shifts.
* \param x: the x coordinate of the pixel to evaluate
* \param y: the y coordinate of the pixel to evaluate
* \return float range 0..1 0 is completely outside
*/
float is_inside_bokeh(float distance, float x, float y);
float2 get_regular_polygon_vertex_position(int vertex_index);
float2 closest_point_on_line(float2 point, float2 line_start, float2 line_end);
float bokeh(float2 point, float circumradius);
public:
BokehImageOperation();
@ -98,21 +35,18 @@ class BokehImageOperation : public MultiThreadedOperation {
void init_execution() override;
void deinit_execution() override;
/**
* \brief determine the resolution of this operation. currently fixed at [COM_BLUR_BOKEH_PIXELS,
* COM_BLUR_BOKEH_PIXELS] \param resolution: \param preferred_resolution:
*/
void determine_canvas(const rcti &preferred_area, rcti &r_area) override;
/**
* \brief set the node data
* \param data:
*/
void set_data(const NodeBokehImage *data)
{
data_ = data;
}
void set_resolution(int resolution)
{
resolution_ = resolution;
}
/**
* \brief delete_data_on_finish
*

View File

@ -96,7 +96,7 @@ void main()
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;
vec2 point = vec2(texel) + vec2(0.5) - 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

@ -1 +1 @@
Subproject commit 7d6518ef5fbada0da13736173b1b06e6fbd1941b
Subproject commit 1b1cbfd78896fd6979eca11597700026ad191352