Periodic functions
Sine, triangle, sawtooth and square waves, and the three knobs every wave has, frequency, amplitude and phase.
How it works
A periodic function repeats: after one period its output starts over. Fed with time, it makes things pulse, bob, blink and orbit forever. Fed with a coordinate, it makes stripes, rings and ripples. The same few waves cover both, and they all answer to the same three controls.
sin and cos
sin(x) swings smoothly between -1 and 1 and repeats every 2π (about 6.283). cos(x) is the same wave a quarter
period earlier, so vec2(cos(a), sin(a)) walks around a circle as a grows. The usual form scales the input so that one
unit means one full cycle:
const float TAU = 6.2831853; // 2π: one full cycle
float wave = sin(TAU * (freq * x + phase)) * amp; // -amp..amp
float pulse = sin(TAU * time) * 0.5 + 0.5; // 0..1, once per second
- Frequency is how many cycles fit in one unit of
x. With time asx, it is cycles per second (hertz); with a UVA 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.FundamentalUV coordinates
A 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.
uv = gl_FragCoord.xy / resolutionSourcescoordinate, cycles across the surface.- gl_FragCoord (opens in a new tab)Reference
- UV mapping (opens in a new tab)Reference
- Amplitude is how far the wave swings either side of its center. Add an offset to move the center.
- Phase shifts the wave along
x, in fractions of a cycle. A phase of 0.25 starts a sine at its peak.
* 0.5 + 0.5 moves a -1..1 wave into 0..1, the range colors and masks want.
Waves that travel
Put space and time in the same wave and it moves. sin(TAU * (4.0 * uv.x - time)) has four crests across the surface
and slides them toward +x at a quarter of the surface per second, one cycle per second. Using + time sends them the
other way; using distance from a point instead of uv.x makes rings that spread out, the start of every ripple.
Saw, triangle, square
fract from the fract-and-mod page is already a wave, the sawtooth: it rises from 0 to 1 and drops back. The other
classic shapes are built from it:
float saw = fract(x); // ramp up, drop: /|/|/|
float triangle = 1.0 - abs(fract(x) * 2.0 - 1.0); // up and down: /\/\/\, 0..1
float square = step(0.5, fract(x)); // off half, on half
float pulseW = step(1.0 - width, fract(x)); // on for a fraction "width" of each period
Each takes the same frequency and phase as a sine: fract(freq * x + phase). A triangle is the cheapest smooth-looking
back-and-forth, a square blinks, and a saw is a repeating progress, the shape to feed into an easing curve or a palette
when something should play again and again.
Shaping a wave
Waves combine with the curves from the shaping-curves page. pow(sin(...) * 0.5 + 0.5, 8.0) turns a sine into short
peaks with long rests between them, a heartbeat. smoothstep(0.4, 0.6, triangle) is a square wave whose edges ramp
instead of jumping. Adding sines of unrelated frequencies, such as 1.0, 2.3 and 3.7, gives motion that takes a long
time to repeat and looks organic, the usual trick for flickering flames, swaying grass and idle bobbing.
Two cautions
Hard-edged waves alias. A square or saw drawn across space has an instant jump that a pixel can land on either side of,
and fine stripes turn into crawling noise; soften the edge to about one pixel wide, which the derivatives page
automates. And large arguments lose accuracy: GPUs compute sin well near 0 but often poorly at thousands of radians.
When x can grow large, such as time times a high frequency, wrap it first: sin(TAU * fract(freq * time)) only
ever asks for angles between 0 and 2π. The wrapped value is still only as precise as time itself, which the time page
covers.