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..
This commit is contained in:
committed by
Martijn Versteegh
parent
56f3cef4c1
commit
a3b7674c6e
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<CMPNodeMapUVFiltering>(node->custom2) ==
|
||||
CMP_NODE_MAP_UV_FILTERING_NEAREST);
|
||||
operation->set_canvas_input_index(1);
|
||||
converter.add_operation(operation);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,7 +24,7 @@ class MapUVOperation : public MultiThreadedOperation {
|
||||
int image_height_;
|
||||
|
||||
float alpha_;
|
||||
|
||||
bool nearest_neighbour_;
|
||||
std::function<void(float x, float y, float *out)> 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+25
@@ -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);
|
||||
}
|
||||
+12
-4
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user