OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / shared / blender_ramp_blend.inc

314 lines · inc ref:
// ============================================================================
// BLENDER'S COLOUR BLEND MODES -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
//
// `ramp_blend` from `source/blender/blenkernel/intern/material.cc` and the HSV
// and HSL pairs from `source/blender/blenlib/intern/math_color.cc`, Blender
// source commit 61a36e08, BLENDER_VERSION 502. The enum is MA_RAMP_* from
// DNA_material_types.h.
//
// This is what the Mix node does for COLOUR data, and it is nineteen distinct
// functions, several of which are not a lerp of anything. proc-nodes
// implemented one of them (a plain lerp) and had no blend_type at all, so
// every colour Mix crossing the bridge came out as MIX regardless of what it
// said in Blender.
//
// The alpha channel is deliberately left alone by every mode except BLEND --
// that asymmetry is Blender's, not an oversight here.
//
// rgb_to_hsv is ported rather than using Godot's Color::get_h/s/v: Blender's
// adds 1e-20 to the divisors instead of branching on zero, so a grey or black
// input gives a defined hue and saturation rather than a NaN or an early out,
// and the four HSV modes are built on that behaviour.
//
// ---------------------------------------------------------------------------
// THE SUBSET
//
//   NO REFERENCE OUT-PARAMETERS. The four conversions wrote three floats
//   through `float &`; they return a vec3 now, and their call sites unpack it.
//
//   NO ARRAY IN/OUT PARAMETER. Blender's signature has `r_col` as both the A
//   input and the result; it is a vec4 in and a vec4 out here. The body still
//   INDEXES it, which GLSL allows on a vector and which the C++ shim's
//   operator[] provides.
// ============================================================================

#ifndef PN_BLENDER_RAMP_BLEND
#define PN_BLENDER_RAMP_BLEND

#ifndef BN_FN
#define BN_FN
#endif

const int BR_BLEND = 0;
const int BR_ADD = 1;
const int BR_MULT = 2;
const int BR_SUB = 3;
const int BR_SCREEN = 4;
const int BR_DIV = 5;
const int BR_DIFF = 6;
const int BR_DARK = 7;
const int BR_LIGHT = 8;
const int BR_OVERLAY = 9;
const int BR_DODGE = 10;
const int BR_BURN = 11;
const int BR_HUE = 12;
const int BR_SAT = 13;
const int BR_VAL = 14;
const int BR_COLOR = 15;
const int BR_SOFT = 16;
const int BR_LINEAR = 17;
const int BR_EXCLUSION = 18;


BN_FN vec3 br_rgb_to_hsv(float r, float g, float b) {
	float h, s, v;

	float k = 0.0f;
	if (g < b) {
		float t = g;
		g = b;
		b = t;
		k = -1.0f;
	}
	float min_gb = b;
	if (r < g) {
		float t = r;
		r = g;
		g = t;
		k = -2.0f / 6.0f - k;
		min_gb = min(g, b);
	}
	float chroma = r - min_gb;
	h = abs(k + (g - b) / (6.0f * chroma + 1e-20f));
	s = chroma / (r + 1e-20f);
	v = r;
	return vec3(h, s, v);
}

BN_FN vec3 br_hsv_to_rgb(float h, float s, float v) {
	float r, g, b;

	float nr = abs(h * 6.0f - 3.0f) - 1.0f;
	float ng = 2.0f - abs(h * 6.0f - 2.0f);
	float nb = 2.0f - abs(h * 6.0f - 4.0f);
	nr = clamp(nr, 0.0f, 1.0f);
	nb = clamp(nb, 0.0f, 1.0f);
	ng = clamp(ng, 0.0f, 1.0f);
	r = ((nr - 1.0f) * s + 1.0f) * v;
	g = ((ng - 1.0f) * s + 1.0f) * v;
	b = ((nb - 1.0f) * s + 1.0f) * v;
	return vec3(r, g, b);
}

// The HSL pair, same file. Not used by any blend mode -- it is here because
// Color Ramp's HSL colour mode needs it and this is where the math_color.cc
// ports live. Unlike rgb_to_hsv it DOES branch on the achromatic case rather
// than biasing the divisor, so grey gives h = s = 0 exactly.
BN_FN vec3 br_rgb_to_hsl(float r, float g, float b) {
	float h, s, l;

	float cmax = max(r, max(g, b));
	float cmin = min(r, min(g, b));
	l = min(1.0f, (cmax + cmin) / 2.0f);

	if (cmax == cmin) {
		h = s = 0.0f; // achromatic
	} else {
		float d = cmax - cmin;
		s = l > 0.5f ? d / (2.0f - cmax - cmin) : d / (cmax + cmin);
		if (cmax == r) {
			h = (g - b) / d + (g < b ? 6.0f : 0.0f);
		} else if (cmax == g) {
			h = (b - r) / d + 2.0f;
		} else {
			h = (r - g) / d + 4.0f;
		}
	}
	h /= 6.0f;
	return vec3(h, s, l);
}

BN_FN vec3 br_hsl_to_rgb(float h, float s, float l) {
	float r, g, b;

	float nr = abs(h * 6.0f - 3.0f) - 1.0f;
	float ng = 2.0f - abs(h * 6.0f - 2.0f);
	float nb = 2.0f - abs(h * 6.0f - 4.0f);
	nr = clamp(nr, 0.0f, 1.0f);
	nb = clamp(nb, 0.0f, 1.0f);
	ng = clamp(ng, 0.0f, 1.0f);

	float chroma = (1.0f - abs(2.0f * l - 1.0f)) * s;

	r = (nr - 0.5f) * chroma + l;
	g = (ng - 0.5f) * chroma + l;
	b = (nb - 0.5f) * chroma + l;
	return vec3(r, g, b);
}

// r_col is BOTH the A input and the result, exactly as Blender's signature has
// it; col is B.
BN_FN vec4 br_ramp_blend(int type, vec4 r_col, float fac, vec4 col) {
	float tmp;
	float facm = 1.0f - fac;

	switch (type) {
		case BR_BLEND:
			for (int i = 0; i < 4; i++) {
				r_col[i] = facm * r_col[i] + fac * col[i];
			}
			break;
		case BR_ADD:
			for (int i = 0; i < 3; i++) {
				r_col[i] += fac * col[i];
			}
			break;
		case BR_MULT:
			for (int i = 0; i < 3; i++) {
				r_col[i] *= (facm + fac * col[i]);
			}
			break;
		case BR_SCREEN:
			for (int i = 0; i < 3; i++) {
				r_col[i] = 1.0f - (facm + fac * (1.0f - col[i])) * (1.0f - r_col[i]);
			}
			break;
		case BR_OVERLAY:
			for (int i = 0; i < 3; i++) {
				if (r_col[i] < 0.5f) {
					r_col[i] *= (facm + 2.0f * fac * col[i]);
				} else {
					r_col[i] = 1.0f - (facm + 2.0f * fac * (1.0f - col[i])) * (1.0f - r_col[i]);
				}
			}
			break;
		case BR_SUB:
			for (int i = 0; i < 3; i++) {
				r_col[i] -= fac * col[i];
			}
			break;
		case BR_DIV:
			for (int i = 0; i < 3; i++) {
				if (col[i] != 0.0f) {
					r_col[i] = facm * r_col[i] + fac * r_col[i] / col[i];
				}
			}
			break;
		case BR_DIFF:
			for (int i = 0; i < 3; i++) {
				r_col[i] = facm * r_col[i] + fac * abs(r_col[i] - col[i]);
			}
			break;
		case BR_EXCLUSION:
			for (int i = 0; i < 3; i++) {
				r_col[i] = max(facm * r_col[i] +
								fac * (r_col[i] + col[i] - 2.0f * r_col[i] * col[i]),
						0.0f);
			}
			break;
		case BR_DARK:
			for (int i = 0; i < 3; i++) {
				r_col[i] = min(r_col[i], col[i]) * fac + r_col[i] * facm;
			}
			break;
		case BR_LIGHT:
			for (int i = 0; i < 3; i++) {
				r_col[i] = max(r_col[i], col[i]) * fac + r_col[i] * facm;
			}
			break;
		case BR_DODGE:
			for (int i = 0; i < 3; i++) {
				if (r_col[i] != 0.0f) {
					tmp = 1.0f - fac * col[i];
					if (tmp <= 0.0f) {
						r_col[i] = 1.0f;
					} else if ((tmp = r_col[i] / tmp) > 1.0f) {
						r_col[i] = 1.0f;
					} else {
						r_col[i] = tmp;
					}
				}
			}
			break;
		case BR_BURN:
			for (int i = 0; i < 3; i++) {
				tmp = facm + fac * col[i];
				if (tmp <= 0.0f) {
					r_col[i] = 0.0f;
				} else if ((tmp = (1.0f - (1.0f - r_col[i]) / tmp)) < 0.0f) {
					r_col[i] = 0.0f;
				} else if (tmp > 1.0f) {
					r_col[i] = 1.0f;
				} else {
					r_col[i] = tmp;
				}
			}
			break;
		case BR_HUE: {
			float rH, rS, rV, colH, colS, colV, tr, tg, tb;
			{ vec3 _hsv1 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv1.x; colS = _hsv1.y; colV = _hsv1.z; }
			if (colS != 0.0f) {
				{ vec3 _hsv2 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv2.x; rS = _hsv2.y; rV = _hsv2.z; }
				{ vec3 _hsv3 = br_hsv_to_rgb(colH, rS, rV); tr = _hsv3.x; tg = _hsv3.y; tb = _hsv3.z; }
				r_col[0] = facm * r_col[0] + fac * tr;
				r_col[1] = facm * r_col[1] + fac * tg;
				r_col[2] = facm * r_col[2] + fac * tb;
			}
			break;
		}
		case BR_SAT: {
			float rH, rS, rV, colH, colS, colV;
			{ vec3 _hsv4 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv4.x; rS = _hsv4.y; rV = _hsv4.z; }
			if (rS != 0.0f) {
				{ vec3 _hsv5 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv5.x; colS = _hsv5.y; colV = _hsv5.z; }
				{ vec3 _hsv6 = br_hsv_to_rgb(rH, facm * rS + fac * colS, rV); r_col[0] = _hsv6.x; r_col[1] = _hsv6.y; r_col[2] = _hsv6.z; }
			}
			break;
		}
		case BR_VAL: {
			float rH, rS, rV, colH, colS, colV;
			{ vec3 _hsv7 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv7.x; rS = _hsv7.y; rV = _hsv7.z; }
			{ vec3 _hsv8 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv8.x; colS = _hsv8.y; colV = _hsv8.z; }
			{ vec3 _hsv9 = br_hsv_to_rgb(rH, rS, facm * rV + fac * colV); r_col[0] = _hsv9.x; r_col[1] = _hsv9.y; r_col[2] = _hsv9.z; }
			break;
		}
		case BR_COLOR: {
			float rH, rS, rV, colH, colS, colV, tr, tg, tb;
			{ vec3 _hsv10 = br_rgb_to_hsv(col[0], col[1], col[2]); colH = _hsv10.x; colS = _hsv10.y; colV = _hsv10.z; }
			if (colS != 0.0f) {
				{ vec3 _hsv11 = br_rgb_to_hsv(r_col[0], r_col[1], r_col[2]); rH = _hsv11.x; rS = _hsv11.y; rV = _hsv11.z; }
				{ vec3 _hsv12 = br_hsv_to_rgb(colH, colS, rV); tr = _hsv12.x; tg = _hsv12.y; tb = _hsv12.z; }
				r_col[0] = facm * r_col[0] + fac * tr;
				r_col[1] = facm * r_col[1] + fac * tg;
				r_col[2] = facm * r_col[2] + fac * tb;
			}
			break;
		}
		case BR_SOFT: {
			// The non-fac Screen mix first, then blended in.
			float sc[3];
			for (int i = 0; i < 3; i++) {
				sc[i] = 1.0f - (1.0f - col[i]) * (1.0f - r_col[i]);
			}
			for (int i = 0; i < 3; i++) {
				r_col[i] = facm * r_col[i] +
						fac * (((1.0f - r_col[i]) * col[i] * r_col[i]) + (r_col[i] * sc[i]));
			}
			break;
		}
		case BR_LINEAR:
			for (int i = 0; i < 3; i++) {
				if (col[i] > 0.5f) {
					r_col[i] = r_col[i] + fac * (2.0f * (col[i] - 0.5f));
				} else {
					r_col[i] = r_col[i] + fac * (2.0f * col[i] - 1.0f);
				}
			}
			break;
	}

	return r_col;
}

#endif // PN_BLENDER_RAMP_BLEND