Compositor: Allow variable size Kuwahara
This patch changes the size property of the Kuwahara into a node socket to allow variable size Kuwahara. Pull Request: https://projects.blender.org/blender/blender/pulls/112946
This commit is contained in:
@@ -24,10 +24,10 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
|
||||
switch (data->variation) {
|
||||
case CMP_NODE_KUWAHARA_CLASSIC: {
|
||||
KuwaharaClassicOperation *operation = new KuwaharaClassicOperation();
|
||||
operation->set_kernel_size(data->size);
|
||||
|
||||
converter.add_operation(operation);
|
||||
converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0));
|
||||
converter.map_input_socket(get_input_socket(1), operation->get_input_socket(1));
|
||||
converter.map_output_socket(get_output_socket(0), operation->get_output_socket());
|
||||
break;
|
||||
}
|
||||
@@ -68,8 +68,10 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
|
||||
converter.add_operation(kuwahara_anisotropic_operation);
|
||||
converter.map_input_socket(get_input_socket(0),
|
||||
kuwahara_anisotropic_operation->get_input_socket(0));
|
||||
converter.map_input_socket(get_input_socket(1),
|
||||
kuwahara_anisotropic_operation->get_input_socket(1));
|
||||
converter.add_link(blur_y_operation->get_output_socket(0),
|
||||
kuwahara_anisotropic_operation->get_input_socket(1));
|
||||
kuwahara_anisotropic_operation->get_input_socket(2));
|
||||
|
||||
converter.map_output_socket(get_output_socket(0),
|
||||
kuwahara_anisotropic_operation->get_output_socket(0));
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace blender::compositor {
|
||||
KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation()
|
||||
{
|
||||
this->add_input_socket(DataType::Color);
|
||||
this->add_input_socket(DataType::Value);
|
||||
this->add_input_socket(DataType::Color);
|
||||
this->add_output_socket(DataType::Color);
|
||||
this->flags_.is_fullframe_operation = true;
|
||||
@@ -24,12 +25,14 @@ KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation()
|
||||
void KuwaharaAnisotropicOperation::init_execution()
|
||||
{
|
||||
image_reader_ = this->get_input_socket_reader(0);
|
||||
structure_tensor_reader_ = this->get_input_socket_reader(1);
|
||||
size_reader_ = this->get_input_socket_reader(1);
|
||||
structure_tensor_reader_ = this->get_input_socket_reader(2);
|
||||
}
|
||||
|
||||
void KuwaharaAnisotropicOperation::deinit_execution()
|
||||
{
|
||||
image_reader_ = nullptr;
|
||||
size_reader_ = nullptr;
|
||||
structure_tensor_reader_ = nullptr;
|
||||
}
|
||||
|
||||
@@ -87,14 +90,18 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
|
||||
float eigenvalue_difference = first_eigenvalue - second_eigenvalue;
|
||||
float anisotropy = eigenvalue_sum > 0.0f ? eigenvalue_difference / eigenvalue_sum : 0.0f;
|
||||
|
||||
float4 size;
|
||||
size_reader_->read(size, x, y, nullptr);
|
||||
float radius = max(0.0f, size.x);
|
||||
|
||||
/* Compute the width and height of an ellipse that is more width-elongated for high anisotropy
|
||||
* and more circular for low anisotropy, controlled using the eccentricity factor. Since the
|
||||
* anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to
|
||||
* infinity and tends to infinity when the eccentricity tends to zero. This is based on the
|
||||
* equations in section "3.2. Anisotropic Kuwahara Filtering" of the paper. */
|
||||
float ellipse_width_factor = (get_eccentricity() + anisotropy) / get_eccentricity();
|
||||
float ellipse_width = ellipse_width_factor * data.size;
|
||||
float ellipse_height = data.size / ellipse_width_factor;
|
||||
float ellipse_width = ellipse_width_factor * radius;
|
||||
float ellipse_height = radius / ellipse_width_factor;
|
||||
|
||||
/* Compute the cosine and sine of the angle that the eigenvector makes with the x axis. Since
|
||||
* the eigenvector is normalized, its x and y components are the cosine and sine of the angle
|
||||
@@ -126,7 +133,7 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
|
||||
* section "3 Alternative Weighting Functions" of the polynomial weights paper. More on this
|
||||
* later in the code. */
|
||||
const int number_of_sectors = 8;
|
||||
float sector_center_overlap_parameter = 2.0f / data.size;
|
||||
float sector_center_overlap_parameter = 2.0f / radius;
|
||||
float sector_envelope_angle = ((3.0f / 2.0f) * M_PI) / number_of_sectors;
|
||||
float cross_sector_overlap_parameter = (sector_center_overlap_parameter +
|
||||
cos(sector_envelope_angle)) /
|
||||
@@ -307,7 +314,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
|
||||
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
|
||||
/* The structure tensor is encoded in a float4 using a column major storage order, as can be
|
||||
* seen in the KuwaharaAnisotropicStructureTensorOperation. */
|
||||
float4 encoded_structure_tensor = float4(inputs[1]->get_elem(it.x, it.y));
|
||||
float4 encoded_structure_tensor = float4(inputs[2]->get_elem(it.x, it.y));
|
||||
float dxdx = encoded_structure_tensor.x;
|
||||
float dxdy = encoded_structure_tensor.y;
|
||||
float dydy = encoded_structure_tensor.w;
|
||||
@@ -334,14 +341,16 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
|
||||
float eigenvalue_difference = first_eigenvalue - second_eigenvalue;
|
||||
float anisotropy = eigenvalue_sum > 0.0f ? eigenvalue_difference / eigenvalue_sum : 0.0f;
|
||||
|
||||
float radius = max(0.0f, *inputs[1]->get_elem(it.x, it.y));
|
||||
|
||||
/* Compute the width and height of an ellipse that is more width-elongated for high anisotropy
|
||||
* and more circular for low anisotropy, controlled using the eccentricity factor. Since the
|
||||
* anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to
|
||||
* infinity and tends to infinity when the eccentricity tends to zero. This is based on the
|
||||
* equations in section "3.2. Anisotropic Kuwahara Filtering" of the paper. */
|
||||
float ellipse_width_factor = (get_eccentricity() + anisotropy) / get_eccentricity();
|
||||
float ellipse_width = ellipse_width_factor * data.size;
|
||||
float ellipse_height = data.size / ellipse_width_factor;
|
||||
float ellipse_width = ellipse_width_factor * radius;
|
||||
float ellipse_height = radius / ellipse_width_factor;
|
||||
|
||||
/* Compute the cosine and sine of the angle that the eigenvector makes with the x axis. Since
|
||||
* the eigenvector is normalized, its x and y components are the cosine and sine of the angle
|
||||
@@ -374,7 +383,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
|
||||
* section "3 Alternative Weighting Functions" of the polynomial weights paper. More on this
|
||||
* later in the code. */
|
||||
int number_of_sectors = 8;
|
||||
float sector_center_overlap_parameter = 2.0f / data.size;
|
||||
float sector_center_overlap_parameter = 2.0f / radius;
|
||||
float sector_envelope_angle = ((3.0f / 2.0f) * M_PI) / number_of_sectors;
|
||||
float cross_sector_overlap_parameter = (sector_center_overlap_parameter +
|
||||
cos(sector_envelope_angle)) /
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace blender::compositor {
|
||||
|
||||
class KuwaharaAnisotropicOperation : public MultiThreadedOperation {
|
||||
SocketReader *image_reader_;
|
||||
SocketReader *size_reader_;
|
||||
SocketReader *structure_tensor_reader_;
|
||||
|
||||
public:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
@@ -14,8 +15,8 @@ namespace blender::compositor {
|
||||
KuwaharaClassicOperation::KuwaharaClassicOperation()
|
||||
{
|
||||
this->add_input_socket(DataType::Color);
|
||||
this->add_input_socket(DataType::Value);
|
||||
this->add_output_socket(DataType::Color);
|
||||
this->set_kernel_size(4);
|
||||
|
||||
this->flags_.is_fullframe_operation = true;
|
||||
}
|
||||
@@ -23,11 +24,13 @@ KuwaharaClassicOperation::KuwaharaClassicOperation()
|
||||
void KuwaharaClassicOperation::init_execution()
|
||||
{
|
||||
image_reader_ = this->get_input_socket_reader(0);
|
||||
size_reader_ = this->get_input_socket_reader(1);
|
||||
}
|
||||
|
||||
void KuwaharaClassicOperation::deinit_execution()
|
||||
{
|
||||
image_reader_ = nullptr;
|
||||
size_reader_ = nullptr;
|
||||
}
|
||||
|
||||
void KuwaharaClassicOperation::execute_pixel_sampled(float output[4],
|
||||
@@ -39,9 +42,13 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4],
|
||||
float4 mean_of_squared_color[] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
|
||||
int quadrant_pixel_count[] = {0, 0, 0, 0};
|
||||
|
||||
float4 size;
|
||||
size_reader_->read_sampled(size, x, y, sampler);
|
||||
const int kernel_size = int(math::max(0.0f, size[0]));
|
||||
|
||||
/* Split surroundings of pixel into 4 overlapping regions. */
|
||||
for (int dy = -kernel_size_; dy <= kernel_size_; dy++) {
|
||||
for (int dx = -kernel_size_; dx <= kernel_size_; dx++) {
|
||||
for (int dy = -kernel_size; dy <= kernel_size; dy++) {
|
||||
for (int dx = -kernel_size; dx <= kernel_size; dx++) {
|
||||
|
||||
int xx = x + dx;
|
||||
int yy = y + dy;
|
||||
@@ -102,21 +109,12 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4],
|
||||
output[3] = mean_of_color[min_index].w; /* Also apply filter to alpha channel. */
|
||||
}
|
||||
|
||||
void KuwaharaClassicOperation::set_kernel_size(int kernel_size)
|
||||
{
|
||||
kernel_size_ = kernel_size;
|
||||
}
|
||||
|
||||
int KuwaharaClassicOperation::get_kernel_size()
|
||||
{
|
||||
return kernel_size_;
|
||||
}
|
||||
|
||||
void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output,
|
||||
const rcti &area,
|
||||
Span<MemoryBuffer *> inputs)
|
||||
{
|
||||
MemoryBuffer *image = inputs[0];
|
||||
MemoryBuffer *size_image = inputs[1];
|
||||
|
||||
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
|
||||
const int x = it.x;
|
||||
@@ -126,9 +124,11 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
|
||||
float4 mean_of_squared_color[] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
|
||||
int quadrant_pixel_count[] = {0, 0, 0, 0};
|
||||
|
||||
const int kernel_size = int(math::max(0.0f, *size_image->get_elem(x, y)));
|
||||
|
||||
/* Split surroundings of pixel into 4 overlapping regions. */
|
||||
for (int dy = -kernel_size_; dy <= kernel_size_; dy++) {
|
||||
for (int dx = -kernel_size_; dx <= kernel_size_; dx++) {
|
||||
for (int dy = -kernel_size; dy <= kernel_size; dy++) {
|
||||
for (int dx = -kernel_size; dx <= kernel_size; dx++) {
|
||||
|
||||
int xx = x + dx;
|
||||
int yy = y + dy;
|
||||
|
||||
@@ -10,8 +10,7 @@ namespace blender::compositor {
|
||||
|
||||
class KuwaharaClassicOperation : public MultiThreadedOperation {
|
||||
SocketReader *image_reader_;
|
||||
|
||||
int kernel_size_;
|
||||
SocketReader *size_reader_;
|
||||
|
||||
public:
|
||||
KuwaharaClassicOperation();
|
||||
@@ -20,9 +19,6 @@ class KuwaharaClassicOperation : public MultiThreadedOperation {
|
||||
void deinit_execution() override;
|
||||
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;
|
||||
|
||||
void set_kernel_size(int kernel_size);
|
||||
int get_kernel_size();
|
||||
|
||||
void update_memory_buffer_partial(MemoryBuffer *output,
|
||||
const rcti &area,
|
||||
Span<MemoryBuffer *> inputs) override;
|
||||
|
||||
@@ -56,6 +56,12 @@ void main()
|
||||
float eigenvalue_difference = first_eigenvalue - second_eigenvalue;
|
||||
float anisotropy = eigenvalue_sum > 0.0 ? eigenvalue_difference / eigenvalue_sum : 0.0;
|
||||
|
||||
#if defined(VARIABLE_SIZE)
|
||||
float radius = max(0.0, texture_load(size_tx, texel).x);
|
||||
#elif defined(CONSTANT_SIZE)
|
||||
float radius = max(0.0, size);
|
||||
#endif
|
||||
|
||||
/* Compute the width and height of an ellipse that is more width-elongated for high anisotropy
|
||||
* and more circular for low anisotropy, controlled using the eccentricity factor. Since the
|
||||
* anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to
|
||||
|
||||
@@ -10,6 +10,12 @@ void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
#if defined(VARIABLE_SIZE)
|
||||
int radius = max(0, int(texture_load(size_tx, texel).x));
|
||||
#elif defined(CONSTANT_SIZE)
|
||||
int radius = max(0, size);
|
||||
#endif
|
||||
|
||||
vec4 mean_of_squared_color_of_quadrants[4] = vec4[](vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0));
|
||||
vec4 mean_of_color_of_quadrants[4] = vec4[](vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0));
|
||||
|
||||
|
||||
@@ -6,20 +6,41 @@
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_shared)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::INT, "radius")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.compute_source("compositor_kuwahara_classic.glsl");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic)
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_shared)
|
||||
.additional_info("compositor_kuwahara_classic_shared")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_constant_size)
|
||||
.additional_info("compositor_kuwahara_classic_convolution_shared")
|
||||
.push_constant(Type::INT, "size")
|
||||
.define("CONSTANT_SIZE")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table)
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_variable_size)
|
||||
.additional_info("compositor_kuwahara_classic_convolution_shared")
|
||||
.sampler(1, ImageType::FLOAT_2D, "size_tx")
|
||||
.define("VARIABLE_SIZE")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table_shared)
|
||||
.additional_info("compositor_kuwahara_classic_shared")
|
||||
.define("SUMMED_AREA_TABLE")
|
||||
.sampler(0, ImageType::FLOAT_2D, "table_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "squared_table_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "squared_table_tx");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table_constant_size)
|
||||
.additional_info("compositor_kuwahara_classic_summed_area_table_shared")
|
||||
.push_constant(Type::INT, "size")
|
||||
.define("CONSTANT_SIZE")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table_variable_size)
|
||||
.additional_info("compositor_kuwahara_classic_summed_area_table_shared")
|
||||
.sampler(2, ImageType::FLOAT_2D, "size_tx")
|
||||
.define("VARIABLE_SIZE")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_compute_structure_tensor)
|
||||
@@ -29,13 +50,23 @@ GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_compute_structure_tensor)
|
||||
.compute_source("compositor_kuwahara_anisotropic_compute_structure_tensor.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic)
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_shared)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::INT, "radius")
|
||||
.push_constant(Type::FLOAT, "eccentricity")
|
||||
.push_constant(Type::FLOAT, "sharpness")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "structure_tensor_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.compute_source("compositor_kuwahara_anisotropic.glsl")
|
||||
.compute_source("compositor_kuwahara_anisotropic.glsl");
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_constant_size)
|
||||
.additional_info("compositor_kuwahara_anisotropic_shared")
|
||||
.define("CONSTANT_SIZE")
|
||||
.push_constant(Type::FLOAT, "size")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_variable_size)
|
||||
.additional_info("compositor_kuwahara_anisotropic_shared")
|
||||
.define("VARIABLE_SIZE")
|
||||
.sampler(2, ImageType::FLOAT_2D, "size_tx")
|
||||
.do_static_compilation(true);
|
||||
|
||||
@@ -1043,7 +1043,7 @@ typedef struct NodeBilateralBlurData {
|
||||
} NodeBilateralBlurData;
|
||||
|
||||
typedef struct NodeKuwaharaData {
|
||||
short size;
|
||||
short size DNA_DEPRECATED;
|
||||
short variation;
|
||||
int uniformity;
|
||||
float sharpness;
|
||||
|
||||
@@ -8432,14 +8432,6 @@ static void def_cmp_kuwahara(StructRNA *srna)
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "size");
|
||||
RNA_def_property_range(prop, 1.0, 100.0);
|
||||
RNA_def_property_ui_range(prop, 1, 100, 1, -1);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Size", "Size of filter. Larger values give stronger stylized effect");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
|
||||
prop = RNA_def_property(srna, "variation", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_sdna(prop, nullptr, "variation");
|
||||
RNA_def_property_enum_items(prop, variation_items);
|
||||
|
||||
@@ -32,6 +32,7 @@ static void cmp_node_kuwahara_declare(NodeDeclarationBuilder &b)
|
||||
b.add_input<decl::Color>("Image")
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.compositor_domain_priority(0);
|
||||
b.add_input<decl::Float>("Size").default_value(6.0f).compositor_domain_priority(1);
|
||||
b.add_output<decl::Color>("Image");
|
||||
}
|
||||
|
||||
@@ -41,7 +42,6 @@ static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node)
|
||||
node->storage = data;
|
||||
|
||||
/* Set defaults. */
|
||||
data->size = 6;
|
||||
data->uniformity = 4;
|
||||
data->eccentricity = 1.0;
|
||||
data->sharpness = 0.5;
|
||||
@@ -54,7 +54,6 @@ static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, Poin
|
||||
col = uiLayoutColumn(layout, false);
|
||||
|
||||
uiItemR(col, ptr, "variation", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "size", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
const int variation = RNA_enum_get(ptr, "variation");
|
||||
|
||||
@@ -90,19 +89,25 @@ class ConvertKuwaharaOperation : public NodeOperation {
|
||||
{
|
||||
/* For high radii, we accelerate the filter using a summed area table, making the filter
|
||||
* execute in constant time as opposed to the trivial quadratic complexity. */
|
||||
if (node_storage(bnode()).size > 5) {
|
||||
Result &size_input = get_input("Size");
|
||||
if (size_input.is_single_value() && size_input.get_float_value() > 5.0f) {
|
||||
execute_classic_summed_area_table();
|
||||
return;
|
||||
}
|
||||
|
||||
GPUShader *shader = shader_manager().get("compositor_kuwahara_classic");
|
||||
GPUShader *shader = shader_manager().get(get_classic_convolution_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", node_storage(bnode()).size);
|
||||
|
||||
const Result &input_image = get_input("Image");
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
if (size_input.is_single_value()) {
|
||||
GPU_shader_uniform_1i(shader, "size", int(size_input.get_float_value()));
|
||||
}
|
||||
else {
|
||||
size_input.bind_as_texture(shader, "size_tx");
|
||||
}
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
@@ -125,10 +130,16 @@ class ConvertKuwaharaOperation : public NodeOperation {
|
||||
summed_area_table(
|
||||
context(), get_input("Image"), squared_table, SummedAreaTableOperation::Square);
|
||||
|
||||
GPUShader *shader = shader_manager().get("compositor_kuwahara_classic_summed_area_table");
|
||||
GPUShader *shader = shader_manager().get(get_classic_summed_area_table_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", node_storage(bnode()).size);
|
||||
Result &size_input = get_input("Size");
|
||||
if (size_input.is_single_value()) {
|
||||
GPU_shader_uniform_1i(shader, "size", int(size_input.get_float_value()));
|
||||
}
|
||||
else {
|
||||
size_input.bind_as_texture(shader, "size_tx");
|
||||
}
|
||||
|
||||
table.bind_as_texture(shader, "table_tx");
|
||||
squared_table.bind_as_texture(shader, "squared_table_tx");
|
||||
@@ -164,16 +175,23 @@ class ConvertKuwaharaOperation : public NodeOperation {
|
||||
float2(node_storage(bnode()).uniformity));
|
||||
structure_tensor.release();
|
||||
|
||||
GPUShader *shader = shader_manager().get("compositor_kuwahara_anisotropic");
|
||||
GPUShader *shader = shader_manager().get(get_anisotropic_shader_name());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1i(shader, "radius", node_storage(bnode()).size);
|
||||
GPU_shader_uniform_1f(shader, "eccentricity", get_eccentricity());
|
||||
GPU_shader_uniform_1f(shader, "sharpness", get_sharpness());
|
||||
|
||||
Result &input = get_input("Image");
|
||||
input.bind_as_texture(shader, "input_tx");
|
||||
|
||||
Result &size_input = get_input("Size");
|
||||
if (size_input.is_single_value()) {
|
||||
GPU_shader_uniform_1f(shader, "size", size_input.get_float_value());
|
||||
}
|
||||
else {
|
||||
size_input.bind_as_texture(shader, "size_tx");
|
||||
}
|
||||
|
||||
smoothed_structure_tensor.bind_as_texture(shader, "structure_tensor_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
@@ -214,15 +232,44 @@ class ConvertKuwaharaOperation : public NodeOperation {
|
||||
return structure_tensor;
|
||||
}
|
||||
|
||||
/* The sharpness controls the sharpness of the transitions between the kuwahara sectors, which is
|
||||
* controlled by the weighting function pow(standard_deviation, -sharpness) as can be seen in the
|
||||
* shader. The transition is completely smooth when the sharpness is zero and completely sharp
|
||||
* when it is infinity. But realistically, the sharpness doesn't change much beyond the value of
|
||||
* 16 due to its exponential nature, so we just assume a maximum sharpness of 16.
|
||||
const char *get_classic_convolution_shader_name()
|
||||
{
|
||||
if (is_constant_size()) {
|
||||
return "compositor_kuwahara_classic_convolution_constant_size";
|
||||
}
|
||||
return "compositor_kuwahara_classic_convolution_variable_size";
|
||||
}
|
||||
|
||||
const char *get_classic_summed_area_table_shader_name()
|
||||
{
|
||||
if (is_constant_size()) {
|
||||
return "compositor_kuwahara_classic_summed_area_table_constant_size";
|
||||
}
|
||||
return "compositor_kuwahara_classic_summed_area_table_variable_size";
|
||||
}
|
||||
|
||||
const char *get_anisotropic_shader_name()
|
||||
{
|
||||
if (is_constant_size()) {
|
||||
return "compositor_kuwahara_anisotropic_constant_size";
|
||||
}
|
||||
return "compositor_kuwahara_anisotropic_variable_size";
|
||||
}
|
||||
|
||||
bool is_constant_size()
|
||||
{
|
||||
return get_input("Size").is_single_value();
|
||||
}
|
||||
|
||||
/* The sharpness controls the sharpness of the transitions between the kuwahara sectors, which
|
||||
* is controlled by the weighting function pow(standard_deviation, -sharpness) as can be seen
|
||||
* in the shader. The transition is completely smooth when the sharpness is zero and completely
|
||||
* sharp when it is infinity. But realistically, the sharpness doesn't change much beyond the
|
||||
* value of 16 due to its exponential nature, so we just assume a maximum sharpness of 16.
|
||||
*
|
||||
* The stored sharpness is in the range [0, 1], so we multiply by 16 to get it in the range
|
||||
* [0, 16], however, we also square it before multiplication to slow down the rate of change near
|
||||
* zero to counter its exponential nature for more intuitive user control. */
|
||||
* [0, 16], however, we also square it before multiplication to slow down the rate of change
|
||||
* near zero to counter its exponential nature for more intuitive user control. */
|
||||
float get_sharpness()
|
||||
{
|
||||
const float sharpness_factor = node_storage(bnode()).sharpness;
|
||||
@@ -236,12 +283,12 @@ class ConvertKuwaharaOperation : public NodeOperation {
|
||||
* (eccentricity + anisotropy) / eccentricity
|
||||
*
|
||||
* Since the anisotropy is in the [0, 1] range, the factor tends to 1 as the eccentricity tends
|
||||
* to infinity and tends to infinity when the eccentricity tends to zero. The stored eccentricity
|
||||
* is in the range [0, 2], we map that to the range [infinity, 0.5] by taking the reciprocal,
|
||||
* satisfying the aforementioned limits. The upper limit doubles the computed default
|
||||
* eccentricity, which users can use to enhance the directionality of the filter. Instead of
|
||||
* actual infinity, we just use an eccentricity of 1 / 0.01 since the result is very similar to
|
||||
* that of infinity. */
|
||||
* to infinity and tends to infinity when the eccentricity tends to zero. The stored
|
||||
* eccentricity is in the range [0, 2], we map that to the range [infinity, 0.5] by taking the
|
||||
* reciprocal, satisfying the aforementioned limits. The upper limit doubles the computed
|
||||
* default eccentricity, which users can use to enhance the directionality of the filter.
|
||||
* Instead of actual infinity, we just use an eccentricity of 1 / 0.01 since the result is very
|
||||
* similar to that of infinity. */
|
||||
float get_eccentricity()
|
||||
{
|
||||
return 1.0f / math::max(0.01f, node_storage(bnode()).eccentricity);
|
||||
|
||||
Reference in New Issue
Block a user