top of page

A greyscale shader is a trivial yet effective full screen effect for old time-y styles or flashback scenearios. The shader is applied to the camera or, in the case of the OpenGL project, applied to the result of the first pass as a second pass shader. The full source code can be found here.

(Please note code comments are removed from these snippets)

Shader "My Shaders/Greyscale"
{
    Properties
    {
        _MainTex ("Buffer", 2D) = "white" {}
        _Scale ("Intensity", Range (0, 1)) = 0
    }
    SubShader
    {
        Pass
        {

As with all shaders we start with the setup stuff. First we define the name of the shader. In Unity this will be the path in the dropdown menu the user will have to take when searching for shaders. We set the name of the shader to Greyscale, placing it in the My Shaders folder.

We the define the inputs of the shader, or the Properties. The underscore is an identifier for input variables. For ease of access we set the scale input to be modifiable in real-time in the engine using a slider. This slider, named Intensity, changes the value to a number in the range of 0 - 1.

SubShader contains all of our shaders. It is named SubShader as it contains all of the shaders in one file; Vertex and fragment, and multiple passes if necessary.

            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform float _Scale;

We tell the compiler to stop reading in ShaderLab and instead start compiling for CG, Nvidia's own shader language.

Pragma's are used to define the names of the vertex and fragment shader. As a vertex shader isn't needed for this post-process effect the standard built-in vertex shader is used.

Next we take the inputs of the shader and uniform them in to this specific pass (again, only one pass is needed here). 'uniform' doesn't actually have to be defined but for consistency and understanding of the code it is left in.

            float4 frag(v2f_img input) : COLOR
            {
                float4 textureMap = tex2D(_MainTex, input.uv);
                
                float lum = textureMap.r * 0.2126 + textureMap.g * 0.7152 + textureMap.b * 0.0722;
                float3 greyscale = float3(lum, lum, lum);
                
                float4 result = textureMap;
                result.rgb = lerp(textureMap.rgb, greyscale, _Scale);


                return result;
            }

            ENDCG
        }
    }
}

Now we're onto the fragment shader. We set the inputs of the fragment shader to the outputs of the passthrough vertex shader and define the output type here as a colour of type float4 for R, G, B, and alpha.

We read in the specific colour value of the texture map corresponding to the current texture coordinates and multiply it by specific colour values, adding all three contributions together. This is then used for all three colour channels of the greyscale vector as to get a shade of grey all colour values must be equal.

The constant values can be changed for different strengths of colour channels in the final greyscale image. I chose these values as they are the standard values for HDTV greyscale calibration however there is no wrong or right answer here. The values are different per colour channel as each colour is more or less perceivable to the human eye known as luminance. Our eyes are more sensitive to red and green colours and less to blue colours.

Finally we mix (lerp) the results of the original buffer and the greyscale result based on the value of _Scale. a higher value of scale results in more greyscaled image, and 1.0 is equal to no colour.

After outputting the final result of the mixing we tell ShaderLab that we are done with CG code so that it can correctly close off the rest of the shaderLab setup.

bottom of page