Color curves (Godot 3.x – GLES2)

This shader adjusts colors with curves, similar to the filter found in art/photo editing software such as Krita and GIMP. Supports RGB, Red, Green, Blue, Alpha, Hue, Saturation and Value channels. It takes a CurveTexture as a uniform sampler2D for each channel.

NOTE: This shader only works as intended using Godot 3’s GLES2 renderer. For the GLES3/Godot 4 version, see: https://godotshaders.com/shader/color-curves-2/

Artwork in the cover image is made by Erevoid, with Pixelorama.

Shader code
shader_type canvas_item;

// CurveTexture(s)
uniform sampler2D rgb_curve;
uniform sampler2D red_curve;
uniform sampler2D green_curve;
uniform sampler2D blue_curve;
uniform sampler2D alpha_curve;
uniform sampler2D hue_curve;
uniform sampler2D sat_curve;
uniform sampler2D value_curve;


vec3 rgb2hsb(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);
}

vec3 hsb2rgb(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);
}


void fragment() {
	COLOR = texture(TEXTURE, UV);

	float red_curve_color = texture(red_curve, vec2(COLOR.r, 0.0)).a;
	float green_curve_color = texture(green_curve, vec2(COLOR.g, 0.0)).a;
	float blue_curve_color = texture(blue_curve, vec2(COLOR.b, 0.0)).a;
	float alpha_curve_color = texture(alpha_curve, vec2(COLOR.a, 0.0)).a;
	COLOR.r = red_curve_color;
	COLOR.g = green_curve_color;
	COLOR.b = blue_curve_color;
	COLOR.a = alpha_curve_color;
	
	vec3 hsb = rgb2hsb(COLOR.rgb);
	float hue_curve_color = texture(hue_curve, vec2(hsb.r, 0.0)).a;
	float sat_curve_color = texture(sat_curve, vec2(hsb.g, 0.0)).a;
	float value_curve_color = texture(value_curve, vec2(hsb.b, 0.0)).a;
	hsb.r = hue_curve_color;
	hsb.g = sat_curve_color;
	hsb.b = value_curve_color;

	COLOR.rgb = hsb2rgb(hsb);

	float rgb_curve_color_r = texture(rgb_curve, vec2(COLOR.r, 0.0)).a;
	float rgb_curve_color_g = texture(rgb_curve, vec2(COLOR.g, 0.0)).a;
	float rgb_curve_color_b = texture(rgb_curve, vec2(COLOR.b, 0.0)).a;
	COLOR.rgb = vec3(rgb_curve_color_r, rgb_curve_color_g, rgb_curve_color_b);
}
Tags
color adjustment, color correction, color curves, color filter, filter
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Overloaded

Color curves

Linear Gradient

Related shaders

Color curves

Gaussian Blur Functions for GLES2

color splash (show only one color)

Subscribe
Notify of
guest

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jeno
Jeno
2 years ago

Thanks for making this shader!
Unfortunately, I cant get it to work on any texture (Sprite2D node).
Everything just shows up as white. Any instructions on how to use it?

Leon
Leon
7 months ago
Reply to  Overloaded

Thanks a lot for the shader! But I have changed .a to .r and still can’t get it to work in Godot 4. Here is the code with the changes. Am I missing something? Tried to change ALL the .a to .r (not just in texture()), didn’t work either.

shader_type canvas_item;

// CurveTexture(s)
uniform sampler2D rgb_curve;
uniform sampler2D red_curve;
uniform sampler2D green_curve;
uniform sampler2D blue_curve;
uniform sampler2D alpha_curve;
uniform sampler2D hue_curve;
uniform sampler2D sat_curve;
uniform sampler2D value_curve;


vec3 rgb2hsb(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);
}

vec3 hsb2rgb(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);
}


void fragment() {
    COLOR = texture(TEXTURE, UV);

    float red_curve_color = texture(red_curve, vec2(COLOR.r, 0.0)).r;
    float green_curve_color = texture(green_curve, vec2(COLOR.g, 0.0)).r;
    float blue_curve_color = texture(blue_curve, vec2(COLOR.b, 0.0)).r;
    float alpha_curve_color = texture(alpha_curve, vec2(COLOR.a, 0.0)).r;
    COLOR.r = red_curve_color;
    COLOR.g = green_curve_color;
    COLOR.b = blue_curve_color;
    COLOR.a = alpha_curve_color;
    
    vec3 hsb = rgb2hsb(COLOR.rgb);
    float hue_curve_color = texture(hue_curve, vec2(hsb.r, 0.0)).r;
    float sat_curve_color = texture(sat_curve, vec2(hsb.g, 0.0)).r;
    float value_curve_color = texture(value_curve, vec2(hsb.b, 0.0)).r;
    hsb.r = hue_curve_color;
    hsb.g = sat_curve_color;
    hsb.b = value_curve_color;

    COLOR.rgb = hsb2rgb(hsb);

    float rgb_curve_color_r = texture(rgb_curve, vec2(COLOR.r, 0.0)).r;
    float rgb_curve_color_g = texture(rgb_curve, vec2(COLOR.g, 0.0)).r;
    float rgb_curve_color_b = texture(rgb_curve, vec2(COLOR.b, 0.0)).r;
    COLOR.rgb = vec3(rgb_curve_color_r, rgb_curve_color_g, rgb_curve_color_b);
}