top of page

A noise shader has many uses when applied to the full screen buffer. It can also be used in combination with the greyscale shader to enhance the flashback effect or to simulate an old television. Noise functions can also be used on low quality textures to hide any artifacts. The full source code can be found here.

(Please note code comments are removed from these snippets)

Shader "My Shaders/Noise"
{
    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 Noise, 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. Very similarly to the greyscale shader we set the scale input to be modifiable in real-time in the engine using a slider for ease of access. 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 value = rand((input.uv * (unity_DeltaTime.x + input.uv)));
                float3 randNoise = float3(value, value, value);
                
                float4 result = textureMap;
                result.rgb = lerp(textureMap.rgb, randNoise, _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.

After sampling the buffer texture for the current texture coordinate we obtain a random value between 0.0 and 1.0 using our input texture coordinates and Unity's built-in function for getting delta time - the amount of time that has passed since the application started running. We use delta time to ensure the result is always different, giving us an animated noise. We then take this result value and make it a float3, using the same result in each channel to make the noise a greyscale result and then, similar to the greyscale filter, we mix the results of the original buffer and the noise texture depending on the value of _Scale, a floating point value in the range of 0.0 and 1.0.

            float rand(float2 co)
            {
                const float2 random = float2(12.9898,78.233);
                const float multiplier = 43758.5453;

                return frac(sin(dot(co.xy, random)) * multiplier);
            }

So what does rand() actually do? Well it's essentially a hash function that uses a lot of big numbers to get a big range of results.

This one line random function takes a float2 value, the noise "seed" if you will.

Firstly it gets the dot product between this seed and a const float2 of (12.9898, 78.233), const of course making these values constant and therefore loaded once at compile time. This is nothing too special here as we are turning the two dimensional vectors into a single floating point. There is some method to the madness of the second float2. These numbers are chosen to ensure there are no immediate similar results.

We then get the sine of this dot product and multiply it by another constant value of 43758.545. Since sine provides smooth interpolation between -1 and 1 multiplying it by the rather large second constant value means that even the smallest change in sin(#) will have a big step between values that adds to the randomness of the final value. Think of it as breaking things to make them work as intended.

Now we return the fractional part of this final result to ensure it is between the ranges of 0.0 and 1.0, where 1.0 is white and 0.0 is black.

bottom of page