Nodes: Add Exclusion color mix mode

Expands Color Mix nodes with new Exclusion mode.

Similar to Difference but produces less contrast.

Requested by Pierre Schiller @3D_director and
@OmarSquircleArt on twitter.

Differential Revision: https://developer.blender.org/D16543
This commit is contained in:
Charlie Jolly 2022-11-18 12:52:14 +00:00
parent a8530d31c2
commit bea5fe6505
Notes: blender-bot 2023-10-30 19:13:47 +01:00
Referenced by issue #114242, Nodes: Exclusion (Mix Color) clamps negative values even when Clamp Result is disabled
18 changed files with 136 additions and 1 deletions

View File

@ -73,6 +73,11 @@ color node_mix_diff(float t, color col1, color col2)
return mix(col1, abs(col1 - col2), t);
}
color node_mix_exclusion(float t, color col1, color col2)
{
return max(mix(col1, col1 + col2 - 2.0 * col1 * col2, t), 0.0);
}
color node_mix_dark(float t, color col1, color col2)
{
return mix(col1, min(col1, col2), t);

View File

@ -76,6 +76,11 @@ color node_mix_diff(float t, color col1, color col2)
return mix(col1, abs(col1 - col2), t);
}
color node_mix_exclusion(float t, color col1, color col2)
{
return max(mix(col1, col1 + col2 - 2.0 * col1 * col2, t), 0.0);
}
color node_mix_dark(float t, color col1, color col2)
{
return mix(col1, min(col1, col2), t);
@ -291,6 +296,8 @@ shader node_mix(string mix_type = "mix",
Color = node_mix_div(t, Color1, Color2);
if (mix_type == "difference")
Color = node_mix_diff(t, Color1, Color2);
if (mix_type == "exclusion")
Color = node_mix_exclusion(t, Color1, Color2);
if (mix_type == "darken")
Color = node_mix_dark(t, Color1, Color2);
if (mix_type == "lighten")

View File

@ -31,6 +31,8 @@ shader node_mix_color(string blend_type = "mix",
Result = node_mix_div(t, A, B);
if (blend_type == "difference")
Result = node_mix_diff(t, A, B);
if (blend_type == "exclusion")
Result = node_mix_exclusion(t, A, B);
if (blend_type == "darken")
Result = node_mix_dark(t, A, B);
if (blend_type == "lighten")

View File

@ -79,6 +79,11 @@ ccl_device float3 svm_mix_diff(float t, float3 col1, float3 col2)
return interp(col1, fabs(col1 - col2), t);
}
ccl_device float3 svm_mix_exclusion(float t, float3 col1, float3 col2)
{
return max(interp(col1, col1 + col2 - 2.0f * col1 * col2, t), zero_float3());
}
ccl_device float3 svm_mix_dark(float t, float3 col1, float3 col2)
{
return interp(col1, min(col1, col2), t);
@ -266,6 +271,8 @@ ccl_device_noinline_cpu float3 svm_mix(NodeMix type, float t, float3 c1, float3
return svm_mix_div(t, c1, c2);
case NODE_MIX_DIFF:
return svm_mix_diff(t, c1, c2);
case NODE_MIX_EXCLUSION:
return svm_mix_exclusion(t, c1, c2);
case NODE_MIX_DARK:
return svm_mix_dark(t, c1, c2);
case NODE_MIX_LIGHT:

View File

@ -136,6 +136,7 @@ typedef enum NodeMix {
NODE_MIX_COL,
NODE_MIX_SOFT,
NODE_MIX_LINEAR,
NODE_MIX_EXCLUSION,
NODE_MIX_CLAMP /* used for the clamp UI option */
} NodeMix;

View File

@ -4948,6 +4948,7 @@ NODE_DEFINE(MixNode)
type_enum.insert("color", NODE_MIX_COL);
type_enum.insert("soft_light", NODE_MIX_SOFT);
type_enum.insert("linear_light", NODE_MIX_LINEAR);
type_enum.insert("exclusion", NODE_MIX_EXCLUSION);
SOCKET_ENUM(mix_type, "Type", type_enum, NODE_MIX_BLEND);
SOCKET_BOOLEAN(use_clamp, "Use Clamp", false);
@ -5026,6 +5027,7 @@ NODE_DEFINE(MixColorNode)
type_enum.insert("color", NODE_MIX_COL);
type_enum.insert("soft_light", NODE_MIX_SOFT);
type_enum.insert("linear_light", NODE_MIX_LINEAR);
type_enum.insert("exclusion", NODE_MIX_EXCLUSION);
SOCKET_ENUM(blend_type, "Type", type_enum, NODE_MIX_BLEND);
SOCKET_IN_FLOAT(fac, "Factor", 0.5f);

View File

@ -1689,6 +1689,14 @@ void ramp_blend(int type, float r_col[3], const float fac, const float col[3])
r_col[1] = facm * (r_col[1]) + fac * fabsf(r_col[1] - col[1]);
r_col[2] = facm * (r_col[2]) + fac * fabsf(r_col[2] - col[2]);
break;
case MA_RAMP_EXCLUSION:
r_col[0] = max_ff(facm * (r_col[0]) + fac * (r_col[0] + col[0] - 2.0f * r_col[0] * col[0]),
0.0f);
r_col[1] = max_ff(facm * (r_col[1]) + fac * (r_col[1] + col[1] - 2.0f * r_col[1] * col[1]),
0.0f);
r_col[2] = max_ff(facm * (r_col[2]) + fac * (r_col[2] + col[2] - 2.0f * r_col[2] * col[2]),
0.0f);
break;
case MA_RAMP_DARK:
r_col[0] = min_ff(r_col[0], col[0]) * fac + r_col[0] * facm;
r_col[1] = min_ff(r_col[1], col[1]) * fac + r_col[1] * facm;

View File

@ -57,6 +57,9 @@ void MixNode::convert_to_operations(NodeConverter &converter,
case MA_RAMP_DIFF:
convert_prog = new MixDifferenceOperation();
break;
case MA_RAMP_EXCLUSION:
convert_prog = new MixExclusionOperation();
break;
case MA_RAMP_SAT:
convert_prog = new MixSaturationOperation();
break;

View File

@ -494,7 +494,65 @@ void MixDifferenceOperation::update_memory_buffer_row(PixelCursor &p)
}
}
/* ******** Mix Difference Operation ******** */
/* ******** Mix Exclusion Operation ******** */
void MixExclusionOperation::execute_pixel_sampled(float output[4],
float x,
float y,
PixelSampler sampler)
{
float input_color1[4];
float input_color2[4];
float input_value[4];
input_value_operation_->read_sampled(input_value, x, y, sampler);
input_color1_operation_->read_sampled(input_color1, x, y, sampler);
input_color2_operation_->read_sampled(input_color2, x, y, sampler);
float value = input_value[0];
if (this->use_value_alpha_multiply()) {
value *= input_color2[3];
}
float valuem = 1.0f - value;
output[0] = max_ff(valuem * input_color1[0] + value * (input_color1[0] + input_color2[0] -
2.0f * input_color1[0] * input_color2[0]),
0.0f);
output[1] = max_ff(valuem * input_color1[1] + value * (input_color1[1] + input_color2[1] -
2.0f * input_color1[1] * input_color2[1]),
0.0f);
output[2] = max_ff(valuem * input_color1[2] + value * (input_color1[2] + input_color2[2] -
2.0f * input_color1[2] * input_color2[2]),
0.0f);
output[3] = input_color1[3];
clamp_if_needed(output);
}
void MixExclusionOperation::update_memory_buffer_row(PixelCursor &p)
{
while (p.out < p.row_end) {
float value = p.value[0];
if (this->use_value_alpha_multiply()) {
value *= p.color2[3];
}
const float value_m = 1.0f - value;
p.out[0] = max_ff(value_m * p.color1[0] +
value * (p.color1[0] + p.color2[0] - 2.0f * p.color1[0] * p.color2[0]),
0.0f);
p.out[1] = max_ff(value_m * p.color1[1] +
value * (p.color1[1] + p.color2[1] - 2.0f * p.color1[1] * p.color2[1]),
0.0f);
p.out[2] = max_ff(value_m * p.color1[2] +
value * (p.color1[2] + p.color2[2] - 2.0f * p.color1[2] * p.color2[2]),
0.0f);
p.out[3] = p.color1[3];
clamp_if_needed(p.out);
p.next();
}
}
/* ******** Mix Divide Operation ******** */
void MixDivideOperation::execute_pixel_sampled(float output[4],
float x,

View File

@ -143,6 +143,14 @@ class MixDifferenceOperation : public MixBaseOperation {
void update_memory_buffer_row(PixelCursor &p) override;
};
class MixExclusionOperation : public MixBaseOperation {
public:
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;
protected:
void update_memory_buffer_row(PixelCursor &p) override;
};
class MixDivideOperation : public MixBaseOperation {
public:
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;

View File

@ -91,6 +91,9 @@ static int ramp_blend_type(const char *type)
if (STREQ(type, "DIFFERENCE")) {
return MA_RAMP_DIFF;
}
if (STREQ(type, "EXCLUSION")) {
return MA_RAMP_EXCLUSION;
}
if (STREQ(type, "DARKEN")) {
return MA_RAMP_DARK;
}

View File

@ -101,6 +101,12 @@ void mix_diff(float fac, vec4 col1, vec4 col2, out vec4 outcol)
outcol.a = col1.a;
}
void mix_exclusion(float fac, vec4 col1, vec4 col2, out vec4 outcol)
{
outcol = max(mix(col1, col1 + col2 - 2.0 * col1 * col2, fac), 0.0);
outcol.a = col1.a;
}
void mix_dark(float fac, vec4 col1, vec4 col2, out vec4 outcol)
{
outcol.rgb = mix(col1.rgb, min(col1.rgb, col2.rgb), fac);

View File

@ -171,6 +171,23 @@ void node_mix_diff(float fac,
outcol.a = col1.a;
}
void node_mix_exclusion(float fac,
vec3 facvec,
float f1,
float f2,
vec3 v1,
vec3 v2,
vec4 col1,
vec4 col2,
out float outfloat,
out vec3 outvec,
out vec4 outcol)
{
outcol = max(mix(col1, col1 + col2 - 2.0 * col1 * col2, fac), 0.0);
outcol.a = col1.a;
}
void node_mix_dark(float fac,
vec3 facvec,
float f1,

View File

@ -269,6 +269,7 @@ typedef struct Material {
#define MA_RAMP_COLOR 15
#define MA_RAMP_SOFT 16
#define MA_RAMP_LINEAR 17
#define MA_RAMP_EXCLUSION 18
/* texco */
#define TEXCO_ORCO (1 << 0)

View File

@ -40,6 +40,7 @@ const EnumPropertyItem rna_enum_ramp_blend_items[] = {
{MA_RAMP_LINEAR, "LINEAR_LIGHT", 0, "Linear Light", ""},
RNA_ENUM_ITEM_SEPR,
{MA_RAMP_DIFF, "DIFFERENCE", 0, "Difference", ""},
{MA_RAMP_EXCLUSION, "EXCLUSION", 0, "Exclusion", ""},
{MA_RAMP_SUB, "SUBTRACT", 0, "Subtract", ""},
{MA_RAMP_DIV, "DIVIDE", 0, "Divide", ""},
RNA_ENUM_ITEM_SEPR,

View File

@ -93,6 +93,8 @@ class MixRGBShaderNode : public ShaderNode {
return "mix_div";
case MA_RAMP_DIFF:
return "mix_diff";
case MA_RAMP_EXCLUSION:
return "mix_exclusion";
case MA_RAMP_DARK:
return "mix_dark";
case MA_RAMP_LIGHT:

View File

@ -258,6 +258,8 @@ static const char *gpu_shader_get_name(eNodeSocketDatatype data_type,
return "node_mix_div_fallback";
case MA_RAMP_DIFF:
return "node_mix_diff";
case MA_RAMP_EXCLUSION:
return "node_mix_exclusion";
case MA_RAMP_DARK:
return "node_mix_dark";
case MA_RAMP_LIGHT:

View File

@ -35,6 +35,8 @@ static const char *gpu_shader_get_name(int mode)
return "mix_div_fallback";
case MA_RAMP_DIFF:
return "mix_diff";
case MA_RAMP_EXCLUSION:
return "mix_exclusion";
case MA_RAMP_DARK:
return "mix_dark";
case MA_RAMP_LIGHT: