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