This can be also considered as a development blog post we will talk about some basics of Shadertoy fragment shader and converting RGB colors to grayscale.
A Tiny Introduction to Shadertoy
Shadertoy helps you to create/test your shaders via using a web browser, which can support OpenGL of course. “Image shaders” of Shadertoy are basically fragment shaders. mainImage() function is used to generate the image and it is called per pixel. mainImage() takes out vec4 fragColor and in vec2 fragCoord parameters where fragCoord contains the pixel coordinates in the 0.5 to resolution-0.5 range and fragColor is the resultant color for the pixel.
There are some built-in uniforms provided by Shadertoy, such as time (iTime), resolution of the image generated (iResolution), and so on. You can also easily add extra buffer, texture, input handlers, sound, and more.
You can check the whole details from Shadertoy’s How To page.
How to Grayscale
There are three algorithms for converting RGB colors to grayscale colors; lightness method, average method, and luminosity.
The lightness method gets the max and min values in the given RGB values and average the maximum and minimum.
1 2 3 |
RGB color = RGB(1, 0.75, 0.5); float lightnessCoef = (max(color) + min(color)) * 0.5f; color = RGB(lightnessCoef, lightnessCoef, lightnessCoef); |
The average method literally averages the values:
1 2 3 |
RGB color = RGB(1, 0.75, 0.5); float avgCoef = ( color.r + color.g + color.b ) * 0.3333f; color = RGB(avgCoef, avgCoef, avgCoef); |
The luminosity method still averages the RGB values, but produces better results than the methods above, since it performs a (predetermind) weighted average by taking account of human perception.
1 2 3 4 |
// Result = 0.2125 R + 0.7154 G + 0.0721 B RGB color = RGB(1, 0.75, 0.5); float lumiCoef = ( 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b ); color = RGB(lumiCoef, lumiCoef, lumiCoef); |
If you need more details or want to find out more algorithms, please check the references.
The Shader
So, here is the shader that will take your texture and transform the colors between grayscale and original RGB based on the time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Shadertoy link: https://www.shadertoy.com/view/XtdyR2 float getPixelColor(const float pixelColor, const float lumiCoef) { // We can use y = ax + b, where y is the fragment color and x is time return ( (pixelColor - lumiCoef) * abs( sin(iTime * 0.3f) ) + lumiCoef ); } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 coord2d = fragCoord.xy / iResolution.xy; // Better send as a uniform vec3 luminosity = vec3(0.2125, 0.7154, 0.0721); vec4 textureColor = texture(iChannel0, coord2d.xy); float lumiCoef = dot(luminosity, textureColor.xyz); // Luminosity coeficient - if you only want black and white //textureColor.r = lumiCoef; //textureColor.g = lumiCoef; //textureColor.b = lumiCoef; textureColor.r = getPixelColor(textureColor.r, lumiCoef); textureColor.g = getPixelColor(textureColor.g, lumiCoef); textureColor.b = getPixelColor(textureColor.b, lumiCoef); normalize(textureColor); fragColor = textureColor; } |
References