# 3D Game Shaders For Beginners

## Film Grain

Film grain (when applied in subtle doses, unlike here) can add a bit of realism you don't notice until it's removed. Typically, it's the imperfections that make a digitally generated image more believable. In terms of the shader graph, film grain is usually the last effect applied before the game is put on screen.

### Amount

``````  // ...

float amount = 0.1;

// ...``````

The `amount` controls how noticeable the film grain is. Crank it up for a snowy picture.

### Random Intensity

``````// ...

uniform float osg_FrameTime;

//...

float toRadians = 3.14 / 180;

//...

float randomIntensity =
fract
( 10000
* sin
(
( gl_FragCoord.x
+ gl_FragCoord.y
* osg_FrameTime
)
)
);

// ...``````

This snippet calculates the random intensity needed to adjust the amount.

``````Time Since F1 = 00 01 02 03 04 05 06 07 08 09 10
Frame Number  = F1    F3    F4       F5 F6
osg_FrameTime = 00    02    04       07 08``````

`osg_FrameTime` is provided by Panda3D. The frame time is a timestamp of how many seconds have passed since the first frame. The example code uses this to animate the film grain as `osg_FrameTime` will always be different each frame.

``````
// ...

( gl_FragCoord.x
+ gl_FragCoord.y
* 8009 // Large number here.

// ...``````

For static film grain, replace `osg_FrameTime` with a large number. You may have to try different numbers to avoid seeing any patterns.

``````        // ...

* sin
(
( gl_FragCoord.x
+ gl_FragCoord.y

// ...``````

Both the x and y coordinate are used to create points or specs of film grain. If only x was used, there would only be vertical lines. Similarly, if only y was used, there would be only horizontal lines.

The reason the snippet multiplies one coordinate by some number is to break up the diagonal symmetry.

You can of course remove the coordinate multiplier for a somewhat decent looking rain effect. To animate the rain effect, multiply the output of `sin` by `osg_FrameTime`.

``````              // ...

( gl_FragCoord.x
+ gl_FragCoord.y

// ...``````

Play around with the x and y coordinate to try and get the rain to change directions. Keep only the x coordinate for a straight downpour.

``````input = (gl_FragCoord.x + gl_FragCoord.y * osg_FrameTime) * toRadians
frame(10000 * sin(input)) =
fract(10000 * sin(6.977777777777778)) =
fract(10000 * 0.6400723818964882) =``````

`sin` is used as a hashing function. The fragment's coordinates are hashed to some output of `sin`. This has the nice property that no matter the input (big or small), the output range is negative one to one.

``````fract(10000 * sin(6.977777777777778)) =
fract(10000 * 0.6400723818964882) =
fract(6400.723818964882) =
0.723818964882``````

`sin` is also used as a pseudo random number generator when combined with `fract`.

``````>>> [floor(fract(4     * sin(x * toRadians)) * 10) for x in range(0, 10)]
[0, 0, 1, 2, 2, 3, 4, 4, 5, 6]

>>> [floor(fract(10000 * sin(x * toRadians)) * 10) for x in range(0, 10)]
[0, 4, 8, 0, 2, 1, 7, 0, 0, 5]``````

Take a look at the first sequence of numbers and then the second. Each sequence is deterministic but the second sequence has less of a pattern than the first. So while the output of `fract(10000 * sin(...))` is deterministic, it doesn't have much of a discernible pattern.

Here you see the `sin` multiplier going from 1, to 10, to 100, and then to 1000.

As you increase the `sin` output multiplier, you get less and less of a pattern. This is the reason the snippet multiplies `sin` by 10,000.

### Fragment Color

``````  // ...

vec2 texSize  = textureSize(colorTexture, 0).xy;
vec2 texCoord = gl_FragCoord.xy / texSize;

vec4 color = texture(colorTexture, texCoord);

// ...``````

Convert the fragment's coordinates to UV coordinates. Using these UV coordinates, look up the texture color for this fragment.

``````    // ...

amount *= randomIntensity;

color.rgb += amount;

// ...``````

Adjust the amount by the random intensity and add this to the color.

``````  // ...

fragColor = color;

// ...``````

Set the fragment color and you're done.

### Source

(C) 2019 David Lettier
lettier.com