# how to make a fast fragment shader that converts intensity to saturation?

I have a simple fragment shader that looks like this:

#ifdef GL_ES
precision lowp float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec4 u_desiredColor;
uniform sampler2D u_texture;

void main()
{
gl_FragColor = texture2D(u_texture, v_texCoord) * u_desiredColor;
}


My goal is to take a grayscale image and apply a color to it, but maintain luminance. There was a stackoverflow post from years ago where the poster was after exactly what I want:

https://stackoverflow.com/questions/4361023/opengl-es-1-1-how-to-change-texture-color-without-losing-luminance

The answers however are outdated (OpenGL ES 1.0) and also I am a little confused by them. The accepted answer says to use multi-texturing, and the 2nd answer says that is "nonsense". Regardless, the code example there is incompatible with the version of OpenGL ES 2.0 that my game is using with cocos2d v2.x.

Here are my original grayscale sprites:

With my simple shader applied and setting u_desiredColor to vec4(1.0, 0.0, 0.0, 1.0), you can see it does exactly what I don't want it to do-- it completely tints the entire image, turning everything red, including the white.

What I really want is for the darker shades of gray have the richest color, but as the sampled grayscale pixel approaches white, the colorized pixels lose saturation, where ultimately a sampled pixel of white has 0% saturation-- but a sampled pixel of dark gray would be 100% saturation.

My desired output is:

My thought was that the simplest approach would be to have my shader make the output pixel color's saturation be something like (1.0 - samplePixelIntensity). So, if the sample pixel's intensity was 1.0, that would mean 0% saturation in the output, if the sample pixel's intensity was 0.5, it would have 50% saturation.. if the sample pixel's intensity was 0.2, then it would have 80% saturation.

However, I am not very familiar with writing shaders, so I am wondering how I might construct something like this?

Simple interpolation to get the color and then applying to the grayscale image

#ifdef GL_ES
precision lowp float;
#endif

varying vec2 v_texCoord;
uniform vec4 u_desiredColor;
uniform sampler2D u_texture;

vec4 saturation;
vec4 grayscale;

void main()
{
grayscale     = texture2D(u_texture, v_texCoord);
saturation     = (1.0,1.0,1.0,1.0) - grayscale;
gl_FragColor   = grayscale*(saturation * u_desiredColor + grayscale*vec4(1.0,1.0,1.0,1.0));
}

• That gives me ERROR: 0:14: Incompatible types (float and vec4) in assignment (and no available implicit conversion) Nov 8, 2016 at 16:48
• Sorry about that. I was thinking of saturation as a single variable, but texture2D() returns a vec4. Fixed it. Nov 8, 2016 at 22:11
• this works great, but somehow everything is just a little too washed out (too white)-- If I do "grayscale*(saturation * u_desiredColor + grayscale*vec4(0.5,0.5,0.5,1.0))" then it has the perfect color saturation amount, but the brightness is cut in half... How can I boost the brightness back up after adding more color? Nov 10, 2016 at 18:34
• That's because now you are interpolating between gray and red basically. You can just multiply all the color components by 2. Nov 11, 2016 at 6:34