Color Adjustments
Hello! I researched and compiled many resources to create this shader.
Shader code
shader_type canvas_item;
group_uniforms R_G_B;
// Uses a calculation for saturation.
uniform float dot_saturation : hint_range(-1.0, 1.0, 0.1) = 0.0;
// Averages the RGB values for saturation.
uniform float avg_saturation : hint_range(-1.0, 1.0, 0.1) = 0.0;
// Mixes the texture color with white or black.
uniform float luminance : hint_range(-1.0, 1.0, 0.1) = 0.0;
// Directly modify the RGB values.
group_uniforms R_G_B.Red;
uniform float mix_red : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float add_red : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_red : hint_range(-1.0, 1.0, 0.1) = 0.0;
group_uniforms R_G_B.Green;
uniform float mix_green : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float add_green : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_green : hint_range(-1.0, 1.0, 0.1) = 0.0;
group_uniforms R_G_B.Blue;
uniform float mix_blue : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float add_blue : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_blue : hint_range(-1.0, 1.0, 0.1) = 0.0;
// Allows the user to adjust color in the HSV color space.
group_uniforms H_S_V;
uniform float hue_hsv : hint_range(-1.0, 1.0, 0.1) = 0.0;
group_uniforms H_S_V.Saturation;
uniform float add_saturation_hsv : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_saturation_hsv : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float scale_saturation_hsv : hint_range(1.0, 10.0, 0.1) = 1.0;
group_uniforms H_S_V.Value;
uniform float add_value : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_value : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float scale_value : hint_range(1.0, 10.0, 0.1) = 1.0;
// Allows the user to adjust color in the HSV color space.
group_uniforms H_S_L;
uniform float hue_hsl : hint_range(-1.0, 1.0, 0.1) = 0.0;
group_uniforms H_S_L.Saturation;
uniform float add_saturation_hsl : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_saturation_hsl : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float scale_saturation_hsl : hint_range(1.0, 10.0, 0.1) = 1.0;
group_uniforms H_S_L.Lightness;
uniform float add_lightness : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float multiply_lightness : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float scale_lightness : hint_range(1.0, 10.0, 0.1) = 1.0;
group_uniforms Tint;
// Allows the user to pick a tint color, a strength (weight), and blend type.
uniform vec3 tint : source_color;
uniform float tint_strength : hint_range(0.0, 1.0) = 0.0;
uniform int tint_mode : hint_enum("Mix", "Multiply", "Divide", "Add", "Subtract") = 0;
group_uniforms;
group_uniforms Contrast;
// Modify other settings such as brightness, contrast, and invert.
uniform float brightness : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float contrast : hint_range(-1.0, 1.0, 0.1) = 0.0;
uniform float scale_contrast : hint_range(1.0, 10.0, 0.1) = 1.0;
uniform bool invert = false;
// Converts an RGB color to HSV.
vec3 rgb_to_hsv(vec3 c)
{
vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, k.wz), vec4(c.gb, k.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
// Converts an HSV color back to RGB.
vec3 hsv_to_rgb(vec3 c)
{
vec4 k = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + k.xyz) * 6.0 - k.www);
return c.z * mix(k.xxx, clamp(p - k.xxx, 0.0, 1.0), c.y);
}
// Converts an HSV color to HSL.
vec3 hsv_to_hsl(vec3 c)
{
float l = 0.5 * c.z * (2.0 - c.y);
float s = l > 0.0 && l < 1.0 ? c.z * c.y / (1.0 - abs(2.0 * l - 1.0)) : 0.0;
return vec3(c.x, s, l);
}
// Converts an HSL color to HSV.
vec3 hsl_to_hsv(vec3 c)
{
float v = (2.0 * c.z + c.y * (1.0 - abs(2.0 * c.z - 1.0))) / 2.0;
float s = 2.0 * (v - c.z) / v;
return vec3(c.x, s, v);
}
// Converts an RGB color to HSL.
vec3 rgb_to_hsl(vec3 c)
{
float vmax = max(c.r, max(c.g, c.b));
float vmin = min(c.r, min(c.g, c.b));
float h, s, l = (vmax + vmin) / 2.0;
float d = vmax - vmin;
if (d < 0.00001) { return vec3(0.0, 0.0, l); }
s = l > 0.5 ? d / (2.0 - vmax - vmin) : d / (vmax + vmin);
if (vmax == c.r) { h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0); }
if (vmax == c.g) { h = (c.b - c.r) / d + 2.0; }
if (vmax == c.b) { h = (c.r - c.g) / d + 4.0; }
h /= 6.0;
return vec3(h, s, l);
}
// Psudeo-private helper function for HSL to RBG function.
float _hsl_hue_to_rgb(float p, float q, float t)
{
if (t < 0.0) t += 1.0;
if (t > 1.0) t -= 1.0;
if (t < 1.0 / 6.0) { return p + (q - p) * 6.0 * t; }
if (t < 1.0 / 2.0) { return q; }
if (t < 2.0 / 3.0) { return p + (q - p) * (2.0 / 3.0 - t) * 6.0; }
return p;
}
// Converts and HSL color to RGB.
vec3 hsl_to_rgb(vec3 c)
{
float r, g, b;
if (c.y < 0.00001) { r = g = b = c.z; }
else {
float q = c.z < 0.5 ? c.z * (1.0 + c.y) : c.z + c.y - c.z * c.y;
float p = 2.0 * c.z - q;
r = _hsl_hue_to_rgb(p, q, c.x + 1.0 / 3.0);
g = _hsl_hue_to_rgb(p, q, c.x);
b = _hsl_hue_to_rgb(p, q, c.x - 1.0 / 3.0); }
return vec3(r, g, b);
}
// Blend a color between three values in the range of -1.0 to 1.0.
vec3 _blend_rgb(vec3 input_color, vec3 min_color, vec3 max_color, float time)
{
// An easy way to switch 0s and 1s mathematically is to use the expression 1 - x.
//vec3 min_color = vec3(1.0 - max_color.x, 1.0 - max_color.y, 1.0 - max_color.z);
return time < 0.0 ? mix(input_color, min_color, abs(time)) : mix(input_color, max_color, time);
}
// Modify the texture's saturation.
vec3 saturate_dot(vec3 input_color)
{
vec3 color_dot = vec3(dot(input_color, vec3(0.299, 0.587, 0.114)));
vec3 max_color = any(isnan(input_color / color_dot)) ? vec3(1.0, 1.0, 1.0) : input_color / color_dot;
return _blend_rgb(input_color, color_dot, max_color, dot_saturation);
}
// Modify the texture's saturation using the average RGB value. Produces a result with less depth.
vec3 saturate_avg(vec3 input_color)
{
float rgb_average = (input_color.r + input_color.g + input_color.b) / 3.0;
vec3 color_avg = vec3(rgb_average, rgb_average, rgb_average);
vec3 max_color = any(isnan(input_color / color_avg)) ? vec3(1.0, 1.0, 1.0) : input_color / color_avg;
return _blend_rgb(input_color, color_avg, max_color, avg_saturation);
}
// Mix black or white to change luminance. This lightens the color without affecting hue.
vec3 adjust_luminance(vec3 input_color)
{
return _blend_rgb(input_color, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0), luminance);
}
// Experiment with different methods to modify RGB values.
vec3 adjust_rgb(vec3 input_color)
{
// Update red value.
input_color = _blend_rgb(input_color, vec3(0.0, 1.0, 1.0), vec3(1.0, 0.0, 0.0), mix_red);
input_color.r += add_red;
input_color.r *= 1.0 + multiply_red;
// Update green value.
input_color = _blend_rgb(input_color, vec3(1.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), mix_green);
input_color.g += add_green;
input_color.g *= 1.0 + multiply_green;
// Update blue value.
input_color = _blend_rgb(input_color, vec3(1.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0), mix_blue);
input_color.b += add_blue;
input_color.b *= 1.0 + multiply_blue;
return input_color;
}
// Performs all RGB color updates in one method; easier, but there is less control of method order.
vec3 shift_color_rgb(vec3 input_color)
{
return adjust_rgb(adjust_luminance(saturate_avg(saturate_dot(input_color))));
}
// Wrap the hue around it's orginal hue.
vec3 shift_hue_hsv(vec3 input_color)
{
return vec3(input_color.x + mod(hue_hsv, 1.0), input_color.y, input_color.z);
}
// Modifies increase, decrease, multiply, and scale the saturation.
vec3 adjust_saturation_hsv(vec3 input_color)
{
vec3 adjusted_color = input_color;
float new_saturation = (input_color.y + add_saturation_hsv) * (1.0 + multiply_saturation_hsv);
//float new_saturation = input_color.y * (1.0 + multiply_saturation_hsv) + add_saturation_hsv;
adjusted_color.y = clamp(new_saturation, 0.0, 1.0) * scale_saturation_hsv;
return adjusted_color;
}
// Modifies increase, decrease, multiply, and scale the value.
vec3 adjust_value(vec3 input_color)
{
vec3 adjusted_color = input_color;
float new_value = (input_color.z + add_value) * (1.0 + multiply_value);
//float new_value = input_color.z * (1.0 + multiply_value) + add_value;
adjusted_color.z = clamp(new_value, 0.0, 1.0) * scale_value;
return adjusted_color;
}
// Performs all color operations in a single method.
vec3 shift_color_hsv(vec3 input_color)
{
return adjust_value(adjust_saturation_hsv(shift_hue_hsv(input_color)));
}
// Wrap the hue around it's orginal hue.
vec3 shift_hue_hsl(vec3 input_color)
{
return vec3(input_color.x + mod(hue_hsl, 1.0), input_color.y, input_color.z);
}
// Modifies increase, decrease, multiply, and scale the saturation.
vec3 adjust_saturation_hsl(vec3 input_color)
{
vec3 adjusted_color = input_color;
float new_saturation = (input_color.y + add_saturation_hsl) * (1.0 + multiply_saturation_hsl);
//float new_saturation = input_color.y * (1.0 + multiply_saturation_hsl) + add_saturation_hsl;
adjusted_color.y = clamp(new_saturation, 0.0, 1.0) * scale_saturation_hsl;
return adjusted_color;
}
// Modifies increase, decrease, multiply, and scale the lightness.
vec3 adjust_lightness(vec3 input_color)
{
vec3 adjusted_color = input_color;
float new_lightness = (input_color.z + add_lightness) * (1.0 + multiply_lightness);
//float new_lightness = input_color.z * (1.0 + multiply_lightness) + add_lightness;
adjusted_color.z = clamp(new_lightness, 0.0, 1.0) * scale_lightness;
return adjusted_color;
}
vec3 shift_color_hsl(vec3 input_color)
{
return adjust_lightness(adjust_saturation_hsl(shift_hue_hsl(input_color)));
}
// Uses the various tint modes to apply blending effects.
vec3 tint_color (vec3 input_color_rgb)
{
// Mixes the colors together smoothly, resulting in a solid tint colored image at 1.0.
if (tint_mode == 0) { return mix(input_color_rgb, tint, tint_strength); }
// Multiplies the colors together, retaining visual contrast while darkening the base color.
if (tint_mode == 1) { return input_color_rgb * mix(vec3(1.0, 1.0, 1.0), tint, tint_strength); }
// Divides the colors, retaining visual contrast but negating the selected color and lightening.
if (tint_mode == 2) { return input_color_rgb / max(vec3(0.00001, 0.00001, 0.00001), mix(vec3(1.0, 1.0, 1.0), tint, tint_strength)); }
// Adds the colors together, creating a lightening effect (colors move toward white).
if (tint_mode == 3) { return input_color_rgb + tint * tint_strength; }
// Subtracts the colors, negating the selected color and creating a darkening effect (colors move toward black).
if (tint_mode == 4) { return input_color_rgb - tint * tint_strength; }
// Default case return the input color.
return input_color_rgb;
}
void fragment() {
// Get the color from our sprite texture.
vec4 input_color = texture(TEXTURE, UV);
// Drop the alpha value so modulate transparency effects still work on the sprite.
vec3 color_rgb = input_color.rgb;
// Updates the color in the HSV color space.
vec3 color_hsv = rgb_to_hsv(color_rgb);
color_hsv = shift_color_hsv(color_hsv);
color_rgb = hsv_to_rgb(color_hsv);
// Updates the color in the HSL color space.
vec3 color_hsl = rgb_to_hsl(color_rgb);
color_hsl = shift_color_hsl(color_hsl);
color_rgb = hsl_to_rgb(color_hsl);
// Updates the color in the RGB color space.
// Note: this has to be done after the HSV and HSL updates or it will produce poor results.
color_rgb = shift_color_rgb(color_rgb);
// Tinting the color behaves the same as adding / mixing / multiplying RGB values.
// Note: desaturation in the RGB color space produces significantly better grayscale depth.
color_rgb = tint_color(color_rgb);
// Changes brightness with a subtle effect on hue.
color_rgb += vec3(1.0, 1.0, 1.0) * brightness;
// Adjust color contrast around 0.5.
if (!invert) { color_rgb = (color_rgb - 0.5) * (1.0 + contrast * scale_contrast) + 0.5; }
// Use negative contrast to invert the color.
if (invert) { color_rgb = (color_rgb - 0.5) * (1.0 + contrast * scale_contrast) * -1.0 + 0.5; }
// Update the sprite texture color.
COLOR.rgb = color_rgb;
}
