From 2f745308ed87550a67a52869492307e67ee33a78 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Fri, 10 Oct 2025 08:00:01 +0200 Subject: [PATCH] Nodes: Mix alpha in Mix node Mix mode This patch mixes the alpha channel of the color in the Mix mode of the Mix node. This has no effect on EEVEE/Cycles since they do not support alpha, but affects the Compositor, Geometry Nodes, and Texture Nodes. Previously, the alpha of the first color was assumed, which meant mixing two images with transparency using a mask in the compositor resulted in part of the image having bad alpha and required manually mixing of the alpha channel. And this is the main motivation of this patch. Pull Request: https://projects.blender.org/blender/blender/pulls/146461 --- .../blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenkernel/BKE_material.hh | 2 +- source/blender/blenkernel/intern/material.cc | 3 +- .../blenloader/intern/versioning_510.cc | 220 +++++++++++++++++- .../freestyle/intern/python/BPy_Freestyle.cpp | 2 +- .../gpu_shader_material_mix_color.glsl | 1 - .../nodes/shader/nodes/node_shader_mix.cc | 2 +- .../nodes/shader/nodes/node_shader_mix_rgb.cc | 2 +- 8 files changed, 226 insertions(+), 8 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 4f02e2dfc60..f5555852c08 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 0 +#define BLENDER_FILE_SUBVERSION 1 /* 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/blenkernel/BKE_material.hh b/source/blender/blenkernel/BKE_material.hh index d455560507b..5e5f01a39ab 100644 --- a/source/blender/blenkernel/BKE_material.hh +++ b/source/blender/blenkernel/BKE_material.hh @@ -209,7 +209,7 @@ void BKE_id_material_eval_ensure_default_slot(ID *id); * \param col: new value. * \param fac: Zero for is no change. */ -void ramp_blend(int type, float r_col[3], float fac, const float col[3]); +void ramp_blend(int type, float r_col[4], float fac, const float col[4]); /** \} */ diff --git a/source/blender/blenkernel/intern/material.cc b/source/blender/blenkernel/intern/material.cc index 7048f2776f6..face1a0a1b5 100644 --- a/source/blender/blenkernel/intern/material.cc +++ b/source/blender/blenkernel/intern/material.cc @@ -1779,7 +1779,7 @@ bNode *BKE_texpaint_slot_material_find_node(Material *ma, short texpaint_slot) return find_data.r_node; } -void ramp_blend(int type, float r_col[3], const float fac, const float col[3]) +void ramp_blend(int type, float r_col[4], const float fac, const float col[4]) { float tmp, facm = 1.0f - fac; @@ -1788,6 +1788,7 @@ void ramp_blend(int type, float r_col[3], const float fac, const float col[3]) r_col[0] = facm * (r_col[0]) + fac * col[0]; r_col[1] = facm * (r_col[1]) + fac * col[1]; r_col[2] = facm * (r_col[2]) + fac * col[2]; + r_col[3] = facm * (r_col[3]) + fac * col[3]; break; case MA_RAMP_ADD: r_col[0] += fac * col[0]; diff --git a/source/blender/blenloader/intern/versioning_510.cc b/source/blender/blenloader/intern/versioning_510.cc index ee02c61cee7..526854289d8 100644 --- a/source/blender/blenloader/intern/versioning_510.cc +++ b/source/blender/blenloader/intern/versioning_510.cc @@ -9,10 +9,17 @@ #define DNA_DEPRECATED_ALLOW #include "DNA_ID.h" +#include "DNA_material_types.h" +#include "DNA_node_types.h" +#include "BLI_listbase.h" +#include "BLI_math_vector.h" #include "BLI_sys_types.h" #include "BKE_main.hh" +#include "BKE_node.hh" +#include "BKE_node_legacy_types.hh" +#include "BKE_node_runtime.hh" #include "readfile.hh" @@ -21,6 +28,197 @@ // #include "CLG_log.h" // static CLG_LogRef LOG = {"blend.doversion"}; +/* The Mix mode of the Mix node previously assumed the alpha of the first input as opposed to + * mixing the alpha as well. So we add a separate color node to get the alpha of the first input + * and set it to the result using a set alpha node. */ +static void do_version_mix_node_mix_mode_compositor(bNodeTree &node_tree, bNode &node) +{ + const NodeShaderMix *data = reinterpret_cast(node.storage); + if (data->data_type != SOCK_RGBA) { + return; + } + + if (data->blend_type != MA_RAMP_BLEND) { + return; + } + + bNodeSocket *first_input = blender::bke::node_find_socket(node, SOCK_IN, "A_Color"); + bNodeSocket *output = blender::bke::node_find_socket(node, SOCK_OUT, "Result_Color"); + + /* Find the link going into the inputs of the node. */ + bNodeLink *first_link = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { + if (link->tosock == first_input) { + first_link = link; + } + } + + bNode &separate_node = version_node_add_empty(node_tree, "CompositorNodeSeparateColor"); + separate_node.parent = node.parent; + separate_node.location[0] = node.location[0] - 10.0f; + separate_node.location[1] = node.location[1]; + NodeCMPCombSepColor *storage = MEM_callocN(__func__); + storage->mode = CMP_NODE_COMBSEP_COLOR_RGB; + separate_node.storage = storage; + + bNodeSocket &separate_input = version_node_add_socket( + node_tree, separate_node, SOCK_IN, "NodeSocketColor", "Image"); + bNodeSocket &separate_alpha_output = version_node_add_socket( + node_tree, separate_node, SOCK_OUT, "NodeSocketFloat", "Alpha"); + + copy_v4_v4(separate_input.default_value_typed()->value, + first_input->default_value_typed()->value); + if (first_link) { + version_node_add_link( + node_tree, *first_link->fromnode, *first_link->fromsock, separate_node, separate_input); + } + + bNode &set_alpha_node = version_node_add_empty(node_tree, "CompositorNodeSetAlpha"); + set_alpha_node.parent = node.parent; + set_alpha_node.location[0] = node.location[0] - 10.0f; + set_alpha_node.location[1] = node.location[1]; + set_alpha_node.storage = MEM_callocN(__func__); + + bNodeSocket &set_alpha_image_input = version_node_add_socket( + node_tree, set_alpha_node, SOCK_IN, "NodeSocketColor", "Image"); + bNodeSocket &set_alpha_alpha_input = version_node_add_socket( + node_tree, set_alpha_node, SOCK_IN, "NodeSocketFloat", "Alpha"); + bNodeSocket &set_alpha_type_input = version_node_add_socket( + node_tree, set_alpha_node, SOCK_IN, "NodeSocketMenu", "Type"); + bNodeSocket &set_alpha_output = version_node_add_socket( + node_tree, set_alpha_node, SOCK_OUT, "NodeSocketColor", "Image"); + + set_alpha_type_input.default_value_typed()->value = + CMP_NODE_SETALPHA_MODE_REPLACE_ALPHA; + version_node_add_link(node_tree, node, *output, set_alpha_node, set_alpha_image_input); + version_node_add_link( + node_tree, separate_node, separate_alpha_output, set_alpha_node, set_alpha_alpha_input); + + LISTBASE_FOREACH_BACKWARD_MUTABLE (bNodeLink *, link, &node_tree.links) { + if (link->fromsock == output && link->tonode != &set_alpha_node) { + version_node_add_link( + node_tree, set_alpha_node, set_alpha_output, *link->tonode, *link->tosock); + blender::bke::node_remove_link(&node_tree, *link); + } + } +} + +/* The Mix mode of the Mix node previously assumed the alpha of the first input as opposed to + * mixing the alpha as well. So we add a separate color node to get the alpha of the first input + * and set it to the result using a pair of separate and combine color nodes. */ +static void do_version_mix_node_mix_mode_geometry(bNodeTree &node_tree, bNode &node) +{ + const NodeShaderMix *data = reinterpret_cast(node.storage); + if (data->data_type != SOCK_RGBA) { + return; + } + + if (data->blend_type != MA_RAMP_BLEND) { + return; + } + + bNodeSocket *first_input = blender::bke::node_find_socket(node, SOCK_IN, "A_Color"); + bNodeSocket *output = blender::bke::node_find_socket(node, SOCK_OUT, "Result_Color"); + + /* Find the link going into the inputs of the node. */ + bNodeLink *first_link = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) { + if (link->tosock == first_input) { + first_link = link; + } + } + + bNode &separate_alpha_node = version_node_add_empty(node_tree, "FunctionNodeSeparateColor"); + separate_alpha_node.parent = node.parent; + separate_alpha_node.location[0] = node.location[0] - 10.0f; + separate_alpha_node.location[1] = node.location[1]; + NodeCombSepColor *separate_alpha_storage = MEM_callocN(__func__); + separate_alpha_storage->mode = NODE_COMBSEP_COLOR_RGB; + separate_alpha_node.storage = separate_alpha_storage; + + bNodeSocket &separate_alpha_input = version_node_add_socket( + node_tree, separate_alpha_node, SOCK_IN, "NodeSocketColor", "Color"); + bNodeSocket &separate_alpha_output = version_node_add_socket( + node_tree, separate_alpha_node, SOCK_OUT, "NodeSocketFloat", "Alpha"); + + copy_v4_v4(separate_alpha_input.default_value_typed()->value, + first_input->default_value_typed()->value); + if (first_link) { + version_node_add_link(node_tree, + *first_link->fromnode, + *first_link->fromsock, + separate_alpha_node, + separate_alpha_input); + } + + bNode &separate_color_node = version_node_add_empty(node_tree, "FunctionNodeSeparateColor"); + separate_color_node.parent = node.parent; + separate_color_node.location[0] = node.location[0] - 10.0f; + separate_color_node.location[1] = node.location[1]; + NodeCombSepColor *separate_color_storage = MEM_callocN(__func__); + separate_color_storage->mode = NODE_COMBSEP_COLOR_RGB; + separate_color_node.storage = separate_color_storage; + + bNodeSocket &separate_color_input = version_node_add_socket( + node_tree, separate_color_node, SOCK_IN, "NodeSocketColor", "Color"); + bNodeSocket &separate_color_red_output = version_node_add_socket( + node_tree, separate_color_node, SOCK_OUT, "NodeSocketFloat", "Red"); + bNodeSocket &separate_color_green_output = version_node_add_socket( + node_tree, separate_color_node, SOCK_OUT, "NodeSocketFloat", "Green"); + bNodeSocket &separate_color_blue_output = version_node_add_socket( + node_tree, separate_color_node, SOCK_OUT, "NodeSocketFloat", "Blue"); + + version_node_add_link(node_tree, node, *output, separate_color_node, separate_color_input); + + bNode &combine_color_node = version_node_add_empty(node_tree, "FunctionNodeCombineColor"); + combine_color_node.parent = node.parent; + combine_color_node.location[0] = node.location[0] - 10.0f; + combine_color_node.location[1] = node.location[1]; + NodeCombSepColor *combine_color_storage = MEM_callocN(__func__); + combine_color_storage->mode = NODE_COMBSEP_COLOR_RGB; + combine_color_node.storage = combine_color_storage; + + bNodeSocket &combine_color_red_input = version_node_add_socket( + node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Red"); + bNodeSocket &combine_color_green_input = version_node_add_socket( + node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Green"); + bNodeSocket &combine_color_blue_input = version_node_add_socket( + node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Blue"); + bNodeSocket &combine_color_alpha_input = version_node_add_socket( + node_tree, combine_color_node, SOCK_IN, "NodeSocketFloat", "Alpha"); + bNodeSocket &combine_color_output = version_node_add_socket( + node_tree, combine_color_node, SOCK_OUT, "NodeSocketColor", "Color"); + + version_node_add_link(node_tree, + separate_color_node, + separate_color_red_output, + combine_color_node, + combine_color_red_input); + version_node_add_link(node_tree, + separate_color_node, + separate_color_green_output, + combine_color_node, + combine_color_green_input); + version_node_add_link(node_tree, + separate_color_node, + separate_color_blue_output, + combine_color_node, + combine_color_blue_input); + version_node_add_link(node_tree, + separate_alpha_node, + separate_alpha_output, + combine_color_node, + combine_color_alpha_input); + + LISTBASE_FOREACH_BACKWARD_MUTABLE (bNodeLink *, link, &node_tree.links) { + if (link->fromsock == output && link->tonode != &separate_color_node) { + version_node_add_link( + node_tree, combine_color_node, combine_color_output, *link->tonode, *link->tosock); + blender::bke::node_remove_link(&node_tree, *link); + } + } +} + void do_versions_after_linking_510(FileData * /*fd*/, Main * /*bmain*/) { /** @@ -31,8 +229,28 @@ void do_versions_after_linking_510(FileData * /*fd*/, Main * /*bmain*/) */ } -void blo_do_versions_510(FileData * /*fd*/, Library * /*lib*/, Main * /*bmain*/) +void blo_do_versions_510(FileData * /*fd*/, Library * /*lib*/, Main *bmain) { + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 1)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == SH_NODE_MIX) { + do_version_mix_node_mix_mode_compositor(*node_tree, *node); + } + } + } + else if (node_tree->type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == SH_NODE_MIX) { + do_version_mix_node_mix_mode_geometry(*node_tree, *node); + } + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/freestyle/intern/python/BPy_Freestyle.cpp b/source/blender/freestyle/intern/python/BPy_Freestyle.cpp index e5060e07b06..e24b0e88a69 100644 --- a/source/blender/freestyle/intern/python/BPy_Freestyle.cpp +++ b/source/blender/freestyle/intern/python/BPy_Freestyle.cpp @@ -156,7 +156,7 @@ static PyObject *Freestyle_blendRamp(PyObject * /*self*/, PyObject *args) PyObject *obj1, *obj2; char *s; int type; - float a[3], fac, b[3]; + float a[4], fac, b[4]; if (!PyArg_ParseTuple(args, "sOfO", &s, &obj1, &fac, &obj2)) { return nullptr; diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_mix_color.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_mix_color.glsl index c82f3fb3a1c..d05231b767b 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_mix_color.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_mix_color.glsl @@ -17,7 +17,6 @@ void node_mix_blend(float fac, out float4 outcol) { outcol = mix(col1, col2, fac); - outcol.a = col1.a; } void node_mix_add(float fac, diff --git a/source/blender/nodes/shader/nodes/node_shader_mix.cc b/source/blender/nodes/shader/nodes/node_shader_mix.cc index cdc7a30b698..8d4400f5583 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mix.cc +++ b/source/blender/nodes/shader/nodes/node_shader_mix.cc @@ -465,7 +465,7 @@ class MixColorFunction : public mf::MultiFunction { if (clamp_result_) { mask.foreach_index_optimized( - [&](const int64_t i) { clamp_v3(results[i], 0.0f, 1.0f); }); + [&](const int64_t i) { clamp_v4(results[i], 0.0f, 1.0f); }); } } }; diff --git a/source/blender/nodes/shader/nodes/node_shader_mix_rgb.cc b/source/blender/nodes/shader/nodes/node_shader_mix_rgb.cc index 9541c9ac45a..3f0fc5520d3 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mix_rgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_mix_rgb.cc @@ -141,7 +141,7 @@ class MixRGBFunction : public mf::MultiFunction { }); if (clamp_) { - mask.foreach_index([&](const int64_t i) { clamp_v3(results[i], 0.0f, 1.0f); }); + mask.foreach_index([&](const int64_t i) { clamp_v4(results[i], 0.0f, 1.0f); }); } } };