Shaders are tiny programs that run on the GPU to turn inputs (like UV coordinates, time, and resolution) into pixels. This page is a practical, beginner-friendly reference for anyone using ShaderGif (or learning GLSL generally) who wants to understand what a shader is doing and how to build one step by step.
What a shader actually is
In real-time graphics, a “shader” is a program executed in parallel across many GPU cores. For ShaderGif-style work you’ll mostly write a fragment shader (sometimes called a pixel shader): it runs once per pixel and returns a final colour for that pixel. The result is an image that updates every frame, which is why shaders are so good for procedural patterns and animation.
There are other shader stages (most notably the vertex shader), but if you’re working with full-screen effects and generative visuals, the fragment shader is usually where the interesting stuff happens.
The fragment shader mental model
Most beginner confusion comes from expecting a shader to “draw shapes” like a normal CPU program. A fragment shader doesn’t draw lines; it answers a question for each pixel: “What colour should this pixel be right now?” If you can define a function that maps (position, time, parameters) to colour, you can make almost anything.
Core inputs you’ll see all the time
- UV / pixel position – where this fragment is on the canvas.
- time – a continuously increasing value used to animate.
- resolution / ratio – the output size and aspect ratio.
- uniforms – user-controlled parameters (sliders, toggles, palette picks, etc.).
Coordinates: pixels, UVs, and aspect ratio
Shader coordinate systems vary by environment, but the two most common are:
- Pixel coordinates: (0..width, 0..height)
- Normalised coordinates (UV): (0..1, 0..1)
A helpful pattern is to convert your coordinates into a centred “world space” where (0,0) is the middle of the screen. That makes rotations and radial effects simpler:
// Example: normalised centred coordinates
// uv is assumed to be in 0..1 range
vec2 p = uv * 2.0 - 1.0; // now roughly -1..1
p.x *= ratio; // preserve circles on non-square canvases
Why the ratio multiply? If your canvas is wide, a circle computed in uncorrected UV space will look like an ellipse. Scaling X (or Y, depending on your convention) compensates for the aspect ratio so distances behave more consistently.
Colour basics: vec3, vec4, and “linear thinking”
Colours in GLSL are usually vec3 (RGB) or vec4 (RGBA). Values are commonly in the 0..1 range. This encourages “linear thinking” about colour: you blend, clamp, remap, and mix values as numbers. A few essentials:
vec3(0.0)is black,vec3(1.0)is white.mix(a, b, t)blends betweenaandbwith factort.clamp(x, 0.0, 1.0)keeps values in a safe range.smoothstep(edge0, edge1, x)makes soft transitions (great for anti-aliased edges).
Here’s a simple animated gradient you can adapt into almost any shader:
// p is centred coordinates, time is a uniform
float wave = 0.5 + 0.5 * sin(p.x * 3.0 + time);
vec3 colA = vec3(0.10, 0.40, 0.90);
vec3 colB = vec3(0.95, 0.80, 0.20);
vec3 col = mix(colA, colB, wave);
Drawing shapes without “drawing”: distance fields
A common shader technique is to compute a distance field: a value that represents how far the current pixel is from an implicit shape. You then turn that distance into colour and edges using step or smoothstep.
Circle: the classic first shape
// p is centred coordinates, radius in the same space
float d = length(p); // distance from centre
float radius = 0.4;
float inside = 1.0 - step(radius, d); // 1 inside circle, 0 outside
That gives a hard-edged circle. For nicer edges, use a small feather width and smoothstep:
float aa = 0.002; // anti-alias width (tweak)
float edge = smoothstep(radius, radius - aa, d);
Distance fields scale well: once you can compute distance-to-shape, you can build outlines, glows, soft shadows, and layered compositions by remapping that distance.
Animation: time, periodic motion, and stable loops
To animate, you typically plug time into a periodic function like sin or cos, or use it to rotate/translate coordinates. A good beginner approach is to animate the input space (the coordinates) rather than directly animating the colour. That usually produces more coherent motion.
Rotate the coordinate space
mat2 rot(float a){
float c = cos(a), s = sin(a);
return mat2(c, -s, s, c);
}
p = rot(time * 0.7) * p;
Rotating the space rotates everything “drawn” in that space. You can also translate, scale, or warp coordinates for effects like ripples, swirl, or fisheye distortion.
Make loops that actually loop
If you’re rendering GIFs, loops matter. A practical trick is to drive motion with an angle that completes a full cycle over a chosen duration:
float duration = 3.0; // seconds per loop
float t = fract(time / duration); // 0..1 loop phase
float a = t * 6.28318530718; // 0..2PI
float wobble = sin(a); // perfectly looping
When your animation is based on t and uses periodic functions, the first and last frame match cleanly (assuming your render captures an integer number of frames across duration).
Noise and texture-like detail
Procedural “texture” in shaders often comes from noise. True noise implementations can be lengthy, but the concept is simple: generate a stable pseudo-random value that changes smoothly over space, then layer it (often in multiple octaves) to produce richer detail (sometimes called fractal noise or fBm).
When starting out, focus less on the “perfect” noise function and more on what you do with it:
- Use noise to perturb coordinates (warp).
- Use noise to vary thresholds (organic edges).
- Use noise to modulate colour (grain, clouds, marble).
If you want a structured, gentle introduction to how fragment shaders think, the early chapters of The Book of Shaders (Chapter 1: “What is a shader?”) are a solid companion to this page.
Precision, performance, and common gotchas
Shaders are fast because they’re massively parallel, but you can still make them slow. A few rules of thumb:
- Prefer simple math over heavy loops. Loops can be fine, but keep them bounded and purposeful.
- Avoid branching when you can express the same logic with
mix,step, and smooth transitions. - Be careful with high-frequency detail. Very sharp patterns can shimmer or alias, especially in GIF output.
- Watch precision. If you see banding or unstable edges, adjust your anti-alias widths and remap values more gently.
Debugging tip: when something looks wrong, temporarily output intermediate values as colour. For example, visualise a distance field like this:
// Visualise a scalar as greyscale
outColor = vec4(vec3(d), 1.0);
If you can see the underlying fields (distance, masks, noise, UVs), you can usually find the bug quickly.
A minimal “building block” pattern
Most shaders can be thought of as a pipeline of steps:
- Get coordinates (UV/pixel) and correct aspect ratio.
- Transform space (centre, rotate, warp).
- Compute fields (distance, noise, masks).
- Map fields to colour (palette, gradients, mixes).
- Post-process (vignette, bloom-like glow, grain).
Keeping your shader structured like this makes it easier to iterate, and it helps you reuse parts across different pieces.
Internal link suggestions
- GLSL tips and snippets – handy patterns for shapes, blending, and utility functions.
- Shader performance – practical ways to keep renders fast and stable.
- Colour palettes in shaders – palette techniques for consistent, pleasing colour.
- Licensing – how licensing works for generated GIFs and shader code on the site.
- Using curl – quick diagnostics for endpoints and checking responses while developing.
