Writing GLSL Shaders for the Web
What Are Shaders?
Shaders are small programs that run on the GPU. Unlike CPU code that executes sequentially, shaders process thousands of pixels or vertices in parallel — a fundamentally different way of thinking about computation.
There are two main types in the WebGL pipeline:
- Vertex shaders — process each vertex of your geometry, setting its final screen position
- Fragment shaders — process each pixel, determining its final color
The Mental Model
When writing a fragment shader, imagine you're writing code that runs for every pixel simultaneously. You don't loop over pixels — the GPU does that for you. Your job is to answer one question:
Given this pixel's coordinates, what color should it be?
void main() {
vec2 uv = gl_FragCoord.xy / iResolution.xy;
gl_FragColor = vec4(uv.x, uv.y, 0.5, 1.0);
}
Domain Warping
One of the most powerful techniques in shader art is domain warping — feeding the output of one noise function as the input to another. This creates organic, flowing patterns that look alive.
for (float i = 1.0; i < 8.0; i++) {
uv.x += 0.5 / i * cos(i * 2.5 * uv.y + uTime);
uv.y += 0.5 / i * cos(i * 1.5 * uv.x + uTime);
}
Each iteration adds a new layer of distortion at a different frequency. The result is a mesmerizing flow field that evolves over time.
Color from Chaos
The final color comes from feeding the warped coordinates into sin() and abs() functions:
float pattern = abs(sin(centered.y - centered.x));
vec3 color = mix(deepBlue, midBlue, smoothstep(0.1, 0.6, pattern));
The abs(sin(...)) creates repeating bands, and smoothstep softens the transitions. Layer multiple patterns at different scales, and you get rich, complex visuals from just a few lines of math.
Why This Matters
Shaders aren't just for demos. They power modern UI effects, data visualizations, and image processing. Understanding them opens up a world of real-time graphics that JavaScript alone can't touch.