From a3b7674c6ef9490bb80bcea6802cb51ee3e0b825 Mon Sep 17 00:00:00 2001 From: Martijn Versteegh Date: Fri, 19 Jan 2024 13:24:22 +0100 Subject: [PATCH] Compositor: Add a Nearest sampling to Map UV node When using the MapUV node for certain NPR workflows (for example palette based remapping of colors) it can be useful to not use the default anisotropic filtering. In preparation of potentially adding more filter modes at a later stage and to keep things consistent with the 'transform' node we use the full set of interpolation modes in the enum, but expose only the implemented ones in RNA.. --- .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenloader/intern/versioning_400.cc | 13 ++++ .../blender/compositor/nodes/COM_MapUVNode.cc | 2 + .../operations/COM_MapUVOperation.cc | 71 +++++++++++-------- .../operations/COM_MapUVOperation.h | 7 +- .../realtime_compositor/CMakeLists.txt | 3 +- ...lsl => compositor_map_uv_anisotropic.glsl} | 0 .../compositor_map_uv_nearest_neighbour.glsl | 25 +++++++ .../shaders/infos/compositor_map_uv_info.hh | 16 +++-- source/blender/makesdna/DNA_node_types.h | 8 +++ .../blender/makesrna/intern/rna_nodetree.cc | 13 ++++ .../composite/nodes/node_composite_map_uv.cc | 39 ++++++++-- 12 files changed, 157 insertions(+), 42 deletions(-) rename source/blender/compositor/realtime_compositor/shaders/{compositor_map_uv.glsl => compositor_map_uv_anisotropic.glsl} (100%) create mode 100644 source/blender/compositor/realtime_compositor/shaders/compositor_map_uv_nearest_neighbour.glsl diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index f499f28a5a3..d30e8c891a1 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 12 +#define BLENDER_FILE_SUBVERSION 13 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index fbdf0b342a5..709be0a10ad 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -2651,6 +2651,19 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } FOREACH_NODETREE_END; + + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 13)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { + if (node->type == CMP_NODE_MAP_UV) { + node->custom2 = CMP_NODE_MAP_UV_FILTERING_ANISOTROPIC; + } + } + } + } + FOREACH_NODETREE_END; + } /** diff --git a/source/blender/compositor/nodes/COM_MapUVNode.cc b/source/blender/compositor/nodes/COM_MapUVNode.cc index 48abc763081..21740bf6948 100644 --- a/source/blender/compositor/nodes/COM_MapUVNode.cc +++ b/source/blender/compositor/nodes/COM_MapUVNode.cc @@ -19,6 +19,8 @@ void MapUVNode::convert_to_operations(NodeConverter &converter, MapUVOperation *operation = new MapUVOperation(); operation->set_alpha(float(node->custom1)); + operation->set_nearest_neighbour(static_cast(node->custom2) == + CMP_NODE_MAP_UV_FILTERING_NEAREST); operation->set_canvas_input_index(1); converter.add_operation(operation); diff --git a/source/blender/compositor/operations/COM_MapUVOperation.cc b/source/blender/compositor/operations/COM_MapUVOperation.cc index 15722712a75..a9e8e50f260 100644 --- a/source/blender/compositor/operations/COM_MapUVOperation.cc +++ b/source/blender/compositor/operations/COM_MapUVOperation.cc @@ -12,6 +12,7 @@ MapUVOperation::MapUVOperation() this->add_input_socket(DataType::Vector); this->add_output_socket(DataType::Color); alpha_ = 0.0f; + nearest_neighbour_ = false; flags_.complex = true; flags_.can_be_constant = true; set_canvas_input_index(UV_INPUT_INDEX); @@ -56,23 +57,28 @@ void MapUVOperation::execute_pixel_sampled(float output[4], return; } - /* EWA filtering */ - input_color_program_->read_filtered(output, uv[0], uv[1], deriv[0], deriv[1]); - - /* UV to alpha threshold */ - const float threshold = alpha_ * 0.05f; - /* XXX alpha threshold is used to fade out pixels on boundaries with invalid derivatives. - * this calculation is not very well defined, should be looked into if it becomes a problem ... - */ - float du = len_v2(deriv[0]); - float dv = len_v2(deriv[1]); - float factor = 1.0f - threshold * (du / input_color_program_->get_width() + - dv / input_color_program_->get_height()); - if (factor < 0.0f) { - alpha = 0.0f; + if (nearest_neighbour_) { + input_color_program_->read_sampled(output, uv[0], uv[1], PixelSampler::Nearest); } else { - alpha *= factor; + /* EWA filtering */ + input_color_program_->read_filtered(output, uv[0], uv[1], deriv[0], deriv[1]); + + /* UV to alpha threshold */ + const float threshold = alpha_ * 0.05f; + /* XXX alpha threshold is used to fade out pixels on boundaries with invalid derivatives. + * this calculation is not very well defined, should be looked into if it becomes a problem ... + */ + float du = len_v2(deriv[0]); + float dv = len_v2(deriv[1]); + float factor = 1.0f - threshold * (du / input_color_program_->get_width() + + dv / input_color_program_->get_height()); + if (factor < 0.0f) { + alpha = 0.0f; + } + else { + alpha *= factor; + } } /* "premul" */ @@ -229,24 +235,29 @@ void MapUVOperation::update_memory_buffer_partial(MemoryBuffer *output, continue; } - /* EWA filtering. */ - input_image->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out); - - /* UV to alpha threshold. */ - const float threshold = alpha_ * 0.05f; - /* XXX alpha threshold is used to fade out pixels on boundaries with invalid derivatives. - * this calculation is not very well defined, should be looked into if it becomes a problem ... - */ - const float du = len_v2(deriv[0]); - const float dv = len_v2(deriv[1]); - const float factor = 1.0f - threshold * (du / image_width_ + dv / image_height_); - if (factor < 0.0f) { - alpha = 0.0f; + if (nearest_neighbour_) { + input_image->read_elem_sampled(uv[0], uv[1], PixelSampler::Nearest, it.out); } else { - alpha *= factor; - } + /* EWA filtering. */ + input_image->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out); + /* UV to alpha threshold. */ + const float threshold = alpha_ * 0.05f; + /* XXX alpha threshold is used to fade out pixels on boundaries with invalid derivatives. + * this calculation is not very well defined, should be looked into if it becomes a problem + * ... + */ + const float du = len_v2(deriv[0]); + const float dv = len_v2(deriv[1]); + const float factor = 1.0f - threshold * (du / image_width_ + dv / image_height_); + if (factor < 0.0f) { + alpha = 0.0f; + } + else { + alpha *= factor; + } + } /* "premul" */ if (alpha < 1.0f) { mul_v4_fl(it.out, alpha); diff --git a/source/blender/compositor/operations/COM_MapUVOperation.h b/source/blender/compositor/operations/COM_MapUVOperation.h index bdc2880ea1f..a20b09dc00e 100644 --- a/source/blender/compositor/operations/COM_MapUVOperation.h +++ b/source/blender/compositor/operations/COM_MapUVOperation.h @@ -24,7 +24,7 @@ class MapUVOperation : public MultiThreadedOperation { int image_height_; float alpha_; - + bool nearest_neighbour_; std::function uv_input_read_fn_; public: @@ -61,6 +61,11 @@ class MapUVOperation : public MultiThreadedOperation { alpha_ = alpha; } + void set_nearest_neighbour(bool nearest_neighbour) + { + nearest_neighbour_ = nearest_neighbour; + } + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_started(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index abf1e4e639c..8b1e246dc27 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -177,7 +177,8 @@ set(GLSL_SRC shaders/compositor_kuwahara_anisotropic.glsl shaders/compositor_kuwahara_anisotropic_compute_structure_tensor.glsl shaders/compositor_kuwahara_classic.glsl - shaders/compositor_map_uv.glsl + shaders/compositor_map_uv_anisotropic.glsl + shaders/compositor_map_uv_nearest_neighbour.glsl shaders/compositor_morphological_blur.glsl shaders/compositor_morphological_distance.glsl shaders/compositor_morphological_distance_feather.glsl diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_map_uv.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_map_uv_anisotropic.glsl similarity index 100% rename from source/blender/compositor/realtime_compositor/shaders/compositor_map_uv.glsl rename to source/blender/compositor/realtime_compositor/shaders/compositor_map_uv_anisotropic.glsl diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_map_uv_nearest_neighbour.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_map_uv_nearest_neighbour.glsl new file mode 100644 index 00000000000..d3d709fe6ea --- /dev/null +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_map_uv_nearest_neighbour.glsl @@ -0,0 +1,25 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + + vec2 uv_coordinates = texture_load(uv_tx, texel).xy; + + vec4 sampled_color = texture(input_tx, uv_coordinates); + + /* The UV texture is assumed to contain an alpha channel as its third channel, since the UV + * coordinates might be defined in only a subset area of the UV texture as mentioned. In that + * case, the alpha is typically opaque at the subset area and transparent everywhere else, and + * alpha pre-multiplication is then performed. This format of having an alpha channel in the UV + * coordinates is the format used by UV passes in render engines, hence the mentioned logic. */ + float alpha = texture_load(uv_tx, texel).z; + + vec4 result = sampled_color * alpha; + + imageStore(output_img, texel, result); +} diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_map_uv_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_map_uv_info.hh index c33f95f2446..73d87a020df 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_map_uv_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_map_uv_info.hh @@ -4,11 +4,19 @@ #include "gpu_shader_create_info.hh" -GPU_SHADER_CREATE_INFO(compositor_map_uv) +GPU_SHADER_CREATE_INFO(compositor_map_uv_shared) .local_group_size(16, 16) - .push_constant(Type::FLOAT, "gradient_attenuation_factor") .sampler(0, ImageType::FLOAT_2D, "input_tx") .sampler(1, ImageType::FLOAT_2D, "uv_tx") - .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .compute_source("compositor_map_uv.glsl") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img"); + +GPU_SHADER_CREATE_INFO(compositor_map_uv_anisotropic) + .additional_info("compositor_map_uv_shared") + .push_constant(Type::FLOAT, "gradient_attenuation_factor") + .compute_source("compositor_map_uv_anisotropic.glsl") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_map_uv_nearest_neighbour) + .additional_info("compositor_map_uv_shared") + .compute_source("compositor_map_uv_nearest_neighbour.glsl") .do_static_compilation(true); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 74d9636fd89..63deb8b72ce 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -2541,6 +2541,14 @@ typedef enum CMPNodeCombSepColorMode { CMP_NODE_COMBSEP_COLOR_YUV = 4, } CMPNodeCombSepColorMode; +/* Filtering modes Compositor MapUV node, stored in custom2. */ +typedef enum CMPNodeMapUVFiltering { + CMP_NODE_MAP_UV_FILTERING_NEAREST = 0, + CMP_NODE_MAP_UV_FILTERING_BILINEAR = 1, + CMP_NODE_MAP_UV_FILTERING_BICUBIC = 2, + CMP_NODE_MAP_UV_FILTERING_ANISOTROPIC = 3, +} CMPNodeMapUVFiltering; + /* Cryptomatte node source. */ typedef enum CMPNodeCryptomatteSource { CMP_NODE_CRYPTOMATTE_SOURCE_RENDER = 0, diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 2d98ccee367..77b5b4b7f81 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -6930,11 +6930,24 @@ static void def_cmp_map_uv(StructRNA *srna) { PropertyRNA *prop; + static const EnumPropertyItem filter_type_items[] = { + {CMP_NODE_MAP_UV_FILTERING_NEAREST, "NEAREST", 0, "Nearest", ""}, + {CMP_NODE_MAP_UV_FILTERING_ANISOTROPIC, "ANISOTROPIC", 0, "Anisotropic", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + prop = RNA_def_property(srna, "alpha", PROP_INT, PROP_FACTOR); RNA_def_property_int_sdna(prop, nullptr, "custom1"); RNA_def_property_range(prop, 0, 100); RNA_def_property_ui_text(prop, "Alpha", ""); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "filter_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "custom2"); + RNA_def_property_enum_items(prop, filter_type_items); + RNA_def_property_ui_text(prop, "Filter Type", ""); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_NODETREE); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } static void def_cmp_defocus(StructRNA *srna) diff --git a/source/blender/nodes/composite/nodes/node_composite_map_uv.cc b/source/blender/nodes/composite/nodes/node_composite_map_uv.cc index d192b9136db..4ff1ef630a3 100644 --- a/source/blender/nodes/composite/nodes/node_composite_map_uv.cc +++ b/source/blender/nodes/composite/nodes/node_composite_map_uv.cc @@ -36,9 +36,15 @@ static void cmp_node_map_uv_declare(NodeDeclarationBuilder &b) static void node_composit_buts_map_uv(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { + uiItemR(layout, ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); uiItemR(layout, ptr, "alpha", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +static void node_composit_init_map_uv(bNodeTree * /*ntree*/, bNode *node) +{ + node->custom2 = CMP_NODE_MAP_UV_FILTERING_ANISOTROPIC; +} + using namespace blender::realtime_compositor; class MapUVOperation : public NodeOperation { @@ -51,16 +57,27 @@ class MapUVOperation : public NodeOperation { get_input("Image").pass_through(get_result("Image")); return; } + bool nearest_neighbour = get_nearest_neighbour(); + + GPUShader *shader = context().get_shader(get_shader_name()); - GPUShader *shader = context().get_shader("compositor_map_uv"); GPU_shader_bind(shader); - GPU_shader_uniform_1f( - shader, "gradient_attenuation_factor", get_gradient_attenuation_factor()); + if (!nearest_neighbour) { + GPU_shader_uniform_1f( + shader, "gradient_attenuation_factor", get_gradient_attenuation_factor()); + } const Result &input_image = get_input("Image"); - GPU_texture_mipmap_mode(input_image.texture(), true, true); - GPU_texture_anisotropic_filter(input_image.texture(), true); + if (nearest_neighbour) { + GPU_texture_mipmap_mode(input_image.texture(), false, false); + GPU_texture_anisotropic_filter(input_image.texture(), false); + } + else { + GPU_texture_mipmap_mode(input_image.texture(), true, true); + GPU_texture_anisotropic_filter(input_image.texture(), true); + } + GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); input_image.bind_as_texture(shader, "input_tx"); @@ -87,6 +104,17 @@ class MapUVOperation : public NodeOperation { { return bnode().custom1 / 100.0f; } + + bool get_nearest_neighbour() + { + return bnode().custom2 == CMP_NODE_MAP_UV_FILTERING_NEAREST; + } + + char const *get_shader_name() + { + return get_nearest_neighbour() ? "compositor_map_uv_nearest_neighbour" : + "compositor_map_uv_anisotropic"; + } }; static NodeOperation *get_compositor_operation(Context &context, DNode node) @@ -106,6 +134,7 @@ void register_node_type_cmp_mapuv() ntype.declare = file_ns::cmp_node_map_uv_declare; ntype.draw_buttons = file_ns::node_composit_buts_map_uv; ntype.get_compositor_operation = file_ns::get_compositor_operation; + ntype.initfunc = file_ns::node_composit_init_map_uv; nodeRegisterType(&ntype); }