If you filled your screen with completely random values, the result would look like television static.
Every pixel would be different from the one beside it.
Nature rarely looks like that.
Clouds change gradually.
Smoke flows smoothly.
Mountains rise slowly.
Water forms continuous waves.
To create effects like these, we need smooth transitions instead of sudden jumps.
This is where value noise comes in.
Value noise starts with random numbers.
Instead of displaying them directly, it blends neighboring values together.
The result looks much more natural.
Rather than seeing sharp blocks, you begin to see smooth hills and valleys.
This technique is one of the foundations of procedural graphics.
Imagine placing a grid across the screen.
Each corner of every cell receives one random number.
vec2 cell = floor(vUv * 8.0);
This tells us which grid cell we're currently inside.
Next we find where we are inside that cell.
vec2 local = fract(vUv * 8.0);
Unlike cell, these values always remain between 0.0 and 1.0.
They describe the position inside the current square.
Each corner receives its own random value.
float a = random(cell);
float b = random(cell + vec2(1.0,0.0));
float c = random(cell + vec2(0.0,1.0));
float d = random(cell + vec2(1.0,1.0));
Instead of one random number, we now have four.
Now blend across the bottom edge.
float bottom = mix(a, b, local.x);
Then blend across the top.
float top = mix(c, d, local.x);
Each blend changes smoothly from left to right.
Finally blend those two results.
float noise = mix(bottom, top, local.y);
Instead of four separate values, we've created one smooth value that changes gradually across the entire cell.
Linear blending works well, but the edges can still look mechanical.
A common improvement is to smooth the interpolation.
local = local * local *
(3.0 - 2.0 * local);
This formula slows the transition near the edges.
The result feels much softer.
You'll see this line in many procedural shaders.
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 vUv;
float random(vec2 st){
return fract(
sin(dot(st, vec2(12.9898,78.233)))
* 43758.5453123
);
}
void main(){
vec2 cell = floor(vUv * 8.0);
vec2 local = fract(vUv * 8.0);
local = local * local *
(3.0 - 2.0 * local);
float a = random(cell);
float b = random(cell + vec2(1.0,0.0));
float c = random(cell + vec2(0.0,1.0));
float d = random(cell + vec2(1.0,1.0));
float bottom = mix(a,b,local.x);
float top = mix(c,d,local.x);
float noise = mix(bottom,top,local.y);
gl_FragColor = vec4(vec3(noise),1.0);
}
Instead of harsh static, the screen now contains smooth flowing values.
Increase the grid.
vUv * 16.0
The details become smaller.
Decrease it.
vUv * 4.0
The noise becomes much larger and softer.
Noise doesn't have to stay grayscale.
vec3 color = mix(
vec3(0.1,0.2,0.6),
vec3(0.8,0.9,1.0),
noise
);
The result begins to resemble clouds or water.
Move through the noise field.
vec2 uv = vUv;
uv.x += uTime * 0.1;
Because the coordinates move continuously, the noise appears to flow.
This simple animation forms the basis of many natural effects.
Value noise appears in many procedural techniques.
Clouds.
Water.
Fog.
Fire.
Terrain.
Marble.
Wood.
Smoke.
Background textures.
Animated effects.
Although it looks simple, it serves as the starting point for much more advanced noise algorithms.
Change the grid size.
Animate the coordinates.
Color the noise.
Use the noise to change the brightness of a circle.
Use it to distort UV coordinates.
Observe how smooth interpolation completely changes the appearance.
Can you create these effects?
Moving clouds.
Flowing fog.
Ocean waves.
A marble texture.
A cloudy sky background.
Every one of these starts with smooth value noise.