Blending modes

A common and simple way to add, for example, two textures together is with the mix() function. However, this function only does a linear interpolation between the two images and does not offer very much control. What if you want to get fancier and do it as you would in an image program, like Photoshop’s blend modes. You can absolutely do that with some math. Here are a few blending mode functions you can use.

Blending modes functions
Multiply     a * b
Screen       1 - (1 - a) * (1 - b)
Darken       min(a, b) 
Lighten      max(a, b) 
Difference   abs(a - b) 
Exclusion    a + b - 2 * a * b 
Overlay      a < 0.5 ? (2.0 * a * b) : (1.0 - 2.0 * (1.0 - a) * (1.0 - b)) 
Hard light   b < 0.5 ? (2.0 * a * b) : (1.0 - 2.0 * (1.0 - a) * (1.0 - b)) 
Soft light   b < 0.5 ? (2.0 * a * b + a * a * (1.0 - 2.0 * b)) : (sqrt(a) * (2.0 * b - 1.0) + (2.0 * a) * (1.0 - b)) 
Color dodge  a / (1.0 - b)
Linear dodge a + b
Burn         1.0 - (1 - a) / b
Linear burn  a + b - 1.0

Godot ready functions

Here are the blending modes above as functions ready to use in Godot. They take RGB colors base and blend, with channel values from 0.0 to 1.0. For example, COLOR.rgb = multiply(base.rgb, blend.rgb); changes the color while keeping the existing alpha.

Multiply

vec3 multiply(vec3 base, vec3 blend){
	return base * blend;
}

Screen

vec3 screen(vec3 base, vec3 blend){
	return 1.0 - (1.0 - base) * (1.0 - blend);
}

Darken

vec3 darken(vec3 base, vec3 blend){
	return min(base, blend);
}

Lighten

vec3 lighten(vec3 base, vec3 blend){
	return max(base, blend);
}

Difference

vec3 difference(vec3 base, vec3 blend){
	return abs(base - blend);
}

Exclusion

vec3 exclusion(vec3 base, vec3 blend){
	return base + blend - 2.0 * base * blend;
}

Overlay

vec3 overlay(vec3 base, vec3 blend){
	vec3 limit = step(0.5, base);
	return mix(2.0 * base * blend, 1.0 - 2.0 * (1.0 - base) * (1.0 - blend), limit);
}

Hard light

vec3 hard_light(vec3 base, vec3 blend){
	vec3 limit = step(0.5, blend);
	return mix(2.0 * base * blend, 1.0 - 2.0 * (1.0 - base) * (1.0 - blend), limit);
}

Soft light

vec3 soft_light(vec3 base, vec3 blend){
	vec3 limit = step(0.5, blend);
	return mix(2.0 * base * blend + base * base * (1.0 - 2.0 * blend), sqrt(base) * (2.0 * blend - 1.0) + (2.0 * base) * (1.0 - blend), limit);
}

Color dodge

float color_dodge_channel(float base, float blend){
	if (base == 0.0) return 0.0;
	if (blend == 1.0) return 1.0;
	return min(1.0, base / (1.0 - blend));
}

vec3 color_dodge(vec3 base, vec3 blend){
	return vec3(color_dodge_channel(base.r, blend.r),
				color_dodge_channel(base.g, blend.g),
				color_dodge_channel(base.b, blend.b));
}

Linear dodge

vec3 linear_dodge(vec3 base, vec3 blend){
	return base + blend;
}

Color burn

float color_burn_channel(float base, float blend){
	if (base == 1.0) return 1.0;
	if (blend == 0.0) return 0.0;
	return 1.0 - min(1.0, (1.0 - base) / blend);
}

vec3 color_burn(vec3 base, vec3 blend){
	return vec3(color_burn_channel(base.r, blend.r),
				color_burn_channel(base.g, blend.g),
				color_burn_channel(base.b, blend.b));
}

Linear burn

vec3 linear_burn (vec3 base, vec3 blend)
{
	return base + blend - 1.0;
}