Understanding the Concepts
Shaders are often taught as random magic. Here, we break them down into their component logic: the GPU model, the math of light, and the art of procedural complexity.
What Shaders Really Are
The first thing that makes shader learning feel messy is that most resources mix together three different layers of knowledge without saying so: the GPU model itself, the math and image-making tricks, and the artistic taste/judgment layer.
The Three Layers
- Platform layer: What WebGL2 / GLSL ES 3.00 actually lets you do.
- Rendering technique layer: Rasterization, ray marching, post-processing, bloom, fog, etc.
- Material and beauty layer: Color, composition, lighting response, detail hierarchy.
The True Mental Model
WebGL2 is not "a shader art engine," and it is not "a ray tracer." It is a browser facing GPU API built around feeding geometry through a vertex stage and then shading fragments.
WebGL is fundamentally a rasterization system that draws points, lines, and triangles. Anything else is something you build with your own shader code.
The Four Biggest Shader Worlds
The field becomes much easier once you group almost everything into four “worlds.”
- Procedural 2D image shaders: UVs, gradients, shapes, masks, and noise. (The "Book of Shaders" world).
- Rasterized 3D shading: Meshes, normals, lighting, shadow maps, and PBR. (The "LearnOpenGL" world).
- Ray marching / SDF scenes: Implicit scene descriptions, constructive solid geometry. (The "Demoscene" world).
- Post-processing / beauty passes: HDR, bloom, tone mapping, and final color shaping.
The Technique Catalogue
The cleanest way to stop shader knowledge feeling scattered is to organize techniques by what problem they solve visually.
1. Shape generation
How you create recognizable structure. Clarity comes from simple fields first, not from noise. If the large scale form is weak, adding detail only makes the image busier.
- UV space construction: Normalize coordinates, center them, and build masks from distance comparisons.
- Signed Distance Functions (SDF): A function that returns how far a point is from a surface. Ideal for crisp shapes, outlines, and smooth union blending.
2. Detail generation
Once the major forms exist, the next problem is richness. Use noise for natural imperfection.
- fBm (Fractal Brownian Motion): Combines multiple octaves of noise to create the impression of richness across scales.
- Domain Warping: Distorting coordinates before evaluating another function. Turns plain patterns into fluid, folded, or turbulent ones.
3. Lighting response
Convincing surface behavior often matters more than raw geometric complexity.
- Fresnel: Grazing angle brightening. This is one of the highest value cheats in all of rendering to make edges feel "premium."
- PBR Principles: Using albedo, metallic, and roughness values to create consistent material behavior across lighting conditions.
4. Depth and space cues
A scene looks flat when it lacks depth cues. Use fog, ambient occlusion (AO), and shadows to ground your objects in space.
The Lesson Taxonomy
The best way to build shader knowledge is through a knowledge ladder where each lesson unlocks a new image making power.
Coordinate Thinking
Everything starts with the idea that a shader is a function of position. A canonical setup converts pixels into a mathematical canvas:
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec2 p = uv * 2.0 - 1.0;
p.x *= u_resolution.x / u_resolution.y;
Mask Algebra
Turning continuous values into controlled masks is a core skill. smoothstep is your best friend
here—it prevents brittle digital edges and allows for softened transitions.
float band = smoothstep(a, b, x) - smoothstep(c, d, x);
float pulse = smoothstep(w, 0.0, abs(x - center));
Multi scale structure
If you want to understand why some shaders feel "dead" and some feel "rich," the answer is usually scale hierarchy. Multi scale structure beats single scale detail. Layering noise frequencies (fBm) creates believable complexity across scales.
The Function Bank
This is your grab bag of reusable formulas that keep showing up across shader art and real time rendering.
Remapping & Shaping
// Remap range [a,b] to [c,d]
float remap(float x, float a, float b, float c, float d) {
return c + (x - a) * (d - c) / (b - a);
}
// Cubic Hermite easing (Smoothstep)
float smooth(float t) {
return t * t * (3.0 - 2.0 * t);
}
2D Distance Primitives
float sdCircle(vec2 p, float r) {
return length(p) - r;
}
float sdBox(vec2 p, vec2 b) {
vec2 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
}
Procedural Detail Helpers
// Standard 2D Hash
float hash21(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
// Palette generator
vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
return a + b * cos(6.28318 * (c * t + d));
}
Beauty & Complexity Playbook
Beautiful shaders are not math-heavy. They are decision-heavy. Complexity is not "more math," it is layered intention.
The 7 Pillars: Form, Value Control, Material Response, Depth Cues, Detail Hierarchy, Motion, and Image Shaping.
The "Path Tracing" Illusion
You don't need real path tracing to get the "wow" factor. You just need the signals of path tracing:
- Soft energy rolloff instead of harsh linear gradients.
- Fresnel edges for that premium glossy feel.
- Ambient Occlusion to ground objects with dark creases.
- Bloom to imply intense energy and energy transport.
Shader Recipes
Putting it all together into full visual ideas. Every premium shader follows a similar stack: Form > Detail > Light > Depth > Color > Post > Stability.
Recipe: The Glossy Chrome Sphere
// 1. Scene
float d = length(p) - 1.0;
// 2. Normal
vec3 n = calcNormal(p);
// 3. Specular highlight
float spec = pow(max(dot(n, h), 0.0), 64.0);
// 4. Fresnel & Reflection
float fres = pow(1.0 - max(dot(n, v), 0.0), 5.0);
vec3 col = base + spec + fres * envReflection;
Recipe: Domain Warped Liquid
// Warp the coordinates first
vec2 q = vec2(fbm(p + vec2(0.0)), fbm(p + vec2(5.2)));
// Then evaluate noise at those warped coordinates
float warpedNoise = fbm(p + 4.0 * q);
// Map to a beautiful palette
vec3 col = palette(warpedNoise, a, b, c, d);