Grainy Perlin Noise Shader for Web
Wow, it’s behind this text! For the longest time, the background of this website was a solid color, which I loved for its minimalism. But I’m addicted to motion, so I couldn’t resist adding something subtly animated. I’ve always been drawn to soft gradients with grainy texture. Taking some inspiration from Keito Yamada’s personal site , I realized I wanted to do something similar - a gentle, moving, grainy gradient.
My approach was fairly simplified in comparison:
Use Perlin noise to generate smooth, almost cloud-like patterns.
Add hash-based aglorithimn for a grainy, animated effect.
Use a light mode toggle to switch between light and dark gradients.
precision mediump float;
uniform vec3 iResolution; // viewport resolution (in pixels)
uniform float iTime; // shader playback time (in seconds)
uniform float iTimeDelta; // render time (in seconds)
uniform float iFrameRate; // shader frame rate
uniform int iFrame; // shader playback frame
uniform float iChannelTime[4]; // channel playback time (in seconds)
uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
uniform vec4 iDate; // (year, month, day, time in seconds)
uniform float iSampleRate; // sound sample rate (i.e., 44100)
uniform float uLightMode; // to keep track of light/dark mode
float hash(vec2 p) {
float h = dot(p, vec2(113.7, 271.9)); // hash seed
return -1.0 + 2.0 * fract(sin(h) * 38942.3794); // multiplier
}
vec2 perlinHash(vec2 p) {
p = p * mat2(123.4, 343.2, 199.2, 237.6); // matrix
p = -1.0 + 2.0 * fract(sin(p) * 37819.2345); // multiplier
return sin(p * 6.283 + iTime * 1.2);
}
float perlinNoise(vec2 p) {
vec2 cell = floor(p);
vec2 local = p - cell;
vec2 fade = local * local * (3.0 - 2.0 * local);
float a = dot(perlinHash(cell + vec2(0.0, 0.0)), local - vec2(0.0, 0.0));
float b = dot(perlinHash(cell + vec2(0.0, 1.0)), local - vec2(0.0, 1.0));
float c = dot(perlinHash(cell + vec2(1.0, 0.0)), local - vec2(1.0, 0.0));
float d = dot(perlinHash(cell + vec2(1.0, 1.0)), local - vec2(1.0, 1.0));
float xMix = mix(a, c, fade.x);
float yMix = mix(b, d, fade.x);
return mix(xMix, yMix, fade.y);
}
float layeredNoise(vec2 p) {
float sum = 0.0;
float amplitude = 1.0;
float totalAmplitude = 0.0;
p *= 3.5; // starting frequency
for (int i = 0; i < 4; i++) {
sum += amplitude * perlinNoise(p);
totalAmplitude += amplitude;
p *= 2.2;
amplitude *= 0.55;
}
return sum / totalAmplitude;
}
void main() {
vec2 uv = gl_FragCoord.xy / iResolution.xy;
uv *= vec2(iResolution.x / iResolution.y, 1.0);
vec2 center = vec2(0.2, 0.5);
float zoom = 0.11;
vec2 adjustedUV = (uv - center) * zoom + center;
float f = layeredNoise(adjustedUV);
f += 0.2 * hash(uv * 2.0);
if (uLightMode > 0.5) {
f = f * 0.75 + 0.85 * 1.1;
} else {
f = f * 0.53 + 0.0;
}
gl_FragColor = vec4(vec3(f), 1.0);
}
Figuring out how to incorporate the shader on my website was surprisingly more challenging. At first, I tried using the