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
This commit is contained in:
Omar Emara
2025-10-10 08:00:01 +02:00
committed by Omar Emara
parent 24573cf537
commit 2f745308ed
8 changed files with 226 additions and 8 deletions

View File

@@ -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

View File

@@ -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]);
/** \} */

View File

@@ -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];

View File

@@ -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<NodeShaderMix *>(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<NodeCMPCombSepColor>(__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<bNodeSocketValueRGBA>()->value,
first_input->default_value_typed<bNodeSocketValueRGBA>()->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<NodeCMPCombSepColor>(__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<bNodeSocketValueMenu>()->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<NodeShaderMix *>(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<NodeCombSepColor>(__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<bNodeSocketValueRGBA>()->value,
first_input->default_value_typed<bNodeSocketValueRGBA>()->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<NodeCombSepColor>(__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<NodeCombSepColor>(__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.

View File

@@ -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;

View File

@@ -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,

View File

@@ -465,7 +465,7 @@ class MixColorFunction : public mf::MultiFunction {
if (clamp_result_) {
mask.foreach_index_optimized<int64_t>(
[&](const int64_t i) { clamp_v3(results[i], 0.0f, 1.0f); });
[&](const int64_t i) { clamp_v4(results[i], 0.0f, 1.0f); });
}
}
};

View File

@@ -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); });
}
}
};