Skip to content
Fundamental · TimeBeginner

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 as x, it is cycles per second (hertz); with a UV coordinate, cycles across the surface.
  • 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.

Further reading