This was built using the TWGL library, and the relevant fragment shader code is as follows:
precision mediump float;
uniform vec2 resolution;
uniform float time;
uniform sampler2D bgTexture;
mat2 rotate(float a)
{
return mat2(cos(a), -sin(a), sin(a), cos(a));
}
float random (vec2 uv)
{
// bookofshaders.com
return fract(sin(dot(uv, vec2(12.9898,78.233))) * 43758.5453123);
}
float rain(vec2 uv, float cols, float rows)
{
vec2 uv2 = vec2(uv.x * cols, uv.y * rows);
vec2 gv = fract(uv2) - 0.5;
vec2 id = floor(uv2);
float n = random(id) - 0.2 + sin(time + id.x) * 0.5;
float r = smoothstep(0.05, 0.0, abs(gv.x + n));
r *= smoothstep(n, n - 0.1, abs(gv.y));
return r * n;
}
void main()
{
vec2 uv = gl_FragCoord.xy / resolution;
vec4 t = texture2D(bgTexture, uv);
uv *= rotate(0.4);
float y = uv.y + time * 2.;
float r = rain(vec2(uv.x, y), 20., 16.) * 0.5;
y = uv.y + time * 2.5;
r += rain(vec2(uv.x, y), 25., 12.) * 0.5;
y = uv.y + time * 3.;
r += rain(vec2(uv.x, y), 26., 18.) * 0.5;
r += rain(vec2(uv.x, y), 8., 6.) * 0.5;
r *= 1.5;
t.rgb += r * vec3(1., 0.25, 0.25); // red bias
gl_FragColor = t;
}