Compositor: Add High Precision option to Kuwahara
For high radii Kuwahara, we use a Summed Area Table (SAT) implementation to accelerate the classic variant of the algorithm. The problem is that due to limited floating point precision, the SAT can produce artifacts in its output. An attempt to fix this was implemented in #114191, and while that patch improved precision by 10x, the artifacts still existed, albeit less noticeable. But since the improved precision also meant a performance penalty, it was decided that the improvement is not worth it. Since the artifacts are only noticeable for scenes with very high values, this patch adds a High Precision option that defaults to false and can be enabled by the user upon noticing any artifacts. The option simply uses direction convolution instead of SAT in this case. The downside, of course, is that it can be orders of magnitude slower. An alternative to using this option is for the user to clamp the input or downsample the image. Both methods should be documented in the documentation. Fixes: #113578. Pull Request: https://projects.blender.org/blender/blender/pulls/115763
This commit is contained in:
@@ -25,6 +25,7 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
|
||||
switch (data->variation) {
|
||||
case CMP_NODE_KUWAHARA_CLASSIC: {
|
||||
KuwaharaClassicOperation *kuwahara_classic = new KuwaharaClassicOperation();
|
||||
kuwahara_classic->set_data(data);
|
||||
converter.add_operation(kuwahara_classic);
|
||||
converter.map_input_socket(get_input_socket(0), kuwahara_classic->get_input_socket(0));
|
||||
converter.map_input_socket(get_input_socket(1), kuwahara_classic->get_input_socket(1));
|
||||
|
||||
@@ -54,8 +54,10 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4],
|
||||
size_reader_->read_sampled(size, x, y, sampler);
|
||||
const int kernel_size = int(math::max(0.0f, size[0]));
|
||||
|
||||
/* Naive implementation is more accurate for small kernel sizes. */
|
||||
if (kernel_size >= 4) {
|
||||
/* For high radii, we accelerate the filter using a summed area table, making the filter
|
||||
* execute in constant time as opposed to having quadratic complexity. Except if high precision
|
||||
* is enabled, since summed area tables are less precise. */
|
||||
if (!data_->high_precision && size[0] > 5.0f) {
|
||||
for (int q = 0; q < 4; q++) {
|
||||
/* A fancy expression to compute the sign of the quadrant q. */
|
||||
int2 sign = int2((q % 2) * 2 - 1, ((q / 2) * 2 - 1));
|
||||
@@ -172,10 +174,13 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
|
||||
float4 mean_of_squared_color[4] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)};
|
||||
int quadrant_pixel_count[4] = {0, 0, 0, 0};
|
||||
|
||||
const int kernel_size = int(math::max(0.0f, *size_image->get_elem(x, y)));
|
||||
const float size = *size_image->get_elem(x, y);
|
||||
const int kernel_size = int(math::max(0.0f, size));
|
||||
|
||||
/* Naive implementation is more accurate for small kernel sizes. */
|
||||
if (kernel_size >= 4) {
|
||||
/* For high radii, we accelerate the filter using a summed area table, making the filter
|
||||
* execute in constant time as opposed to having quadratic complexity. Except if high precision
|
||||
* is enabled, since summed area tables are less precise. */
|
||||
if (!data_->high_precision && size > 5.0f) {
|
||||
for (int q = 0; q < 4; q++) {
|
||||
/* A fancy expression to compute the sign of the quadrant q. */
|
||||
int2 sign = int2((q % 2) * 2 - 1, ((q / 2) * 2 - 1));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace blender::compositor {
|
||||
|
||||
class KuwaharaClassicOperation : public MultiThreadedOperation {
|
||||
const NodeKuwaharaData *data_;
|
||||
SocketReader *image_reader_;
|
||||
SocketReader *size_reader_;
|
||||
SocketReader *sat_reader_;
|
||||
@@ -17,6 +18,11 @@ class KuwaharaClassicOperation : public MultiThreadedOperation {
|
||||
public:
|
||||
KuwaharaClassicOperation();
|
||||
|
||||
void set_data(const NodeKuwaharaData *data)
|
||||
{
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
void init_execution() override;
|
||||
void deinit_execution() override;
|
||||
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;
|
||||
|
||||
@@ -1067,6 +1067,8 @@ typedef struct NodeKuwaharaData {
|
||||
int uniformity;
|
||||
float sharpness;
|
||||
float eccentricity;
|
||||
char high_precision;
|
||||
char _pad[3];
|
||||
} NodeKuwaharaData;
|
||||
|
||||
typedef struct NodeAntiAliasingData {
|
||||
|
||||
@@ -8582,6 +8582,14 @@ static void def_cmp_kuwahara(StructRNA *srna)
|
||||
RNA_def_property_ui_text(prop, "", "Variation of Kuwahara filter to use");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
|
||||
prop = RNA_def_property(srna, "use_high_precision", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "high_precision", 1);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"High Precision",
|
||||
"Uses a more precise but slower method. Use if the output contains undesirable noise");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
|
||||
prop = RNA_def_property(srna, "uniformity", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "uniformity");
|
||||
RNA_def_property_range(prop, 0.0, 50.0);
|
||||
|
||||
@@ -57,7 +57,10 @@ static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, Poin
|
||||
|
||||
const int variation = RNA_enum_get(ptr, "variation");
|
||||
|
||||
if (variation == CMP_NODE_KUWAHARA_ANISOTROPIC) {
|
||||
if (variation == CMP_NODE_KUWAHARA_CLASSIC) {
|
||||
uiItemR(col, ptr, "use_high_precision", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
}
|
||||
else if (variation == CMP_NODE_KUWAHARA_ANISOTROPIC) {
|
||||
uiItemR(col, ptr, "uniformity", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "sharpness", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(col, ptr, "eccentricity", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
@@ -88,9 +91,12 @@ class ConvertKuwaharaOperation : public NodeOperation {
|
||||
void execute_classic()
|
||||
{
|
||||
/* 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. */
|
||||
* execute in constant time as opposed to having quadratic complexity. Except if high precision
|
||||
* is enabled, since summed area tables are less precise. */
|
||||
Result &size_input = get_input("Size");
|
||||
if (size_input.is_single_value() && size_input.get_float_value() > 5.0f) {
|
||||
if (!node_storage(bnode()).high_precision &&
|
||||
(size_input.is_texture() || size_input.get_float_value() > 5.0f))
|
||||
{
|
||||
execute_classic_summed_area_table();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user