Randomness — the seed of noise
Fake a repeatable random number from a coordinate with a hash, then cell-ify it.
Clouds, terrain, dithering, scattered grass — the organic look always starts with one thing: a random number you can compute from a coordinate, the same way every time. GLSL has no rand() function, so shaders fake it with a hash.
The famous one-liner is fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453). It looks like nonsense, and that is the point: sin() times a huge number, keep only the fractional part, and tiny changes in p scatter the output all over 0 to 1. Crucially it is DETERMINISTIC — the same p always returns the same value, so your texture does not flicker every frame. That is exactly what you want.
Feed the raw coordinate in and every pixel gets its own value: pure static. What makes random USEFUL is to floor() the coordinate into CELLS first. floor(st * 8.0) gives the same integer across a whole block of pixels, so each block gets one random value — a blocky grid. Smooth that grid out and you get noise (the next lesson); leave it blocky and you have random tiles, dither, or a starfield.
Your turn: make 10 random vertical stripes. Cell-ify just the x axis — floor(st.x * 10.0) — and hash it, so each of the 10 columns gets its own random grey (keep the y term a constant like 0.0 so the whole column shares one value).
Make 10 random vertical stripes.
The random() hash is provided. Cell-ify the x axis: v = random(vec2(floor(st.x * 10.0), 0.0)). Flooring x into 10 columns makes each column share one random value.
Why floor the coordinate (e.g. floor(st * 8.0)) before hashing it?