Cool gradient

This sketch fills your entire screen with a continuously morphing field of psychedelic waves and ripples. Using WebGL shaders, it animates colorful patterns that flow and shift in real time, creating an immersive, organic visual experience that responds to your window size.

🧪 Try This!

Experiment with the code by making these changes:

  1. Slow down the waves — Increase the divisor in the time uniform to make waves drift more lazily and peacefully.
  2. Speed up the animation — Decrease the divisor to make waves flow frantically and chaotically.
  3. Add tighter ripples — Increase the frequency multiplier on the first wave to create more bands of color across the screen.
  4. Make it more psychedelic — Increase the distortion strength to warp and twist the pattern more dramatically.
  5. Change your credit text — Replace 'by corbun' with your own name or message in the bottom-left corner.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a mesmerizing fullscreen display of animated, colorful waves that ripple and shift continuously across your screen. The visual effect is powered by WebGL shaders—small graphics programs that run on your GPU to calculate the color of every pixel in real time. Unlike drawing shapes one by one, shaders compute the entire pattern in parallel, making it possible to animate a flowing, organic design smoothly and responsively.

The code is organized into three main pieces: a vertex shader that positions a single flat quad to cover the entire screen, a fragment shader that calculates the psychedelic color pattern using sine and cosine waves, and p5.js glue code in setup() and draw() that compiles the shaders, feeds them the current time, and handles window resizing. By studying it you will learn how to escape the limits of traditional p5.js shape drawing and harness the GPU for real-time generative art.

⚙️ How It Works

  1. When the sketch loads, preload() fetches a font from the internet, and setup() creates a fullscreen WebGL canvas and compiles the vertex and fragment shaders from their source strings.
  2. Every frame, draw() activates the shader and feeds it two uniforms: the current time in milliseconds divided by 250 (to control animation speed) and the canvas resolution.
  3. The shader then runs once for every pixel on screen. It takes the pixel's UV coordinates (a 0-to-1 position across the canvas) and distorts them slightly using sine and cosine functions of the current time.
  4. Using the distorted coordinates, it calculates three separate wave patterns at different frequencies and phases, then blends them together to create a complex, flowing shape.
  5. Finally, it maps that blended pattern through sine and cosine functions again to generate the red, green, and blue color values, creating colors that pulse and shift as time changes.
  6. The text 'by corbun' is drawn on top using p5.js text functions (without the shader), and the design automatically scales when you resize your window.

🎓 Concepts You'll Learn

WebGL shadersFragment shadersTrigonometric functions (sine, cosine)UV coordinatesReal-time animationGPU computationUniform variablesColor space mixing

📝 Code Breakdown

preload()

preload() is called once before setup() and is the right place to load fonts, images, and other files from the internet. It ensures everything is ready before your sketch begins drawing.

function preload() {
  uiFont = loadFont(
    'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff'
  );
}
Line-by-line explanation (1 lines)
uiFont = loadFont('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff');
Loads a font file from a CDN (content delivery network) and stores it in the uiFont variable so it can be used for text later. preload() guarantees this finishes before setup() runs.

setup()

setup() runs once when the sketch starts. Here we create the WebGL canvas and compile the shaders so draw() can use them. The third argument to createCanvas() chooses the rendering engine: WEBGL enables GPU shaders, P2D uses hardware 2D acceleration, and omitting it uses standard 2D rendering.

function setup() {
  // Fullscreen canvas, WEBGL so we can use shaders
  createCanvas(windowWidth, windowHeight, WEBGL);
  noStroke();
  rectMode(CENTER);

  // Create shader from source strings
  patternShader = createShader(vertSrc, fragSrc);

  // Use the loaded font for UI text
  textFont(uiFont);
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation WebGL Canvas Setup createCanvas(windowWidth, windowHeight, WEBGL);

Creates a fullscreen canvas that supports WebGL shaders instead of regular 2D drawing

calculation Shader Compilation patternShader = createShader(vertSrc, fragSrc);

Compiles the vertex and fragment shader code into a GPU program that will calculate colors for every pixel

createCanvas(windowWidth, windowHeight, WEBGL);
Creates a canvas as wide and tall as the browser window, and enables WEBGL mode so shaders can run on the GPU
noStroke();
Disables outlines around shapes—we only want filled colors
rectMode(CENTER);
Makes rect() draw from the center point outward instead of from the top-left, which is standard for WEBGL coordinates
patternShader = createShader(vertSrc, fragSrc);
Takes the vertex shader (vertSrc) and fragment shader (fragSrc) code and compiles them into a GPU program stored in patternShader
textFont(uiFont);
Tells p5.js to use the font we loaded in preload() for any text we draw

draw()

draw() runs every frame (60 times per second by default). Here we activate the shader, feed it the current time and canvas size, draw a rectangle for the shader to color, then turn off the shader to draw text on top without the effect. The shader does the heavy lifting—it runs in parallel on the GPU to compute a color for every single pixel.

function draw() {
  // Use the shader
  shader(patternShader);

  // Time in seconds – DIVIDE BY 250 TO SPEED UP COLOR SHIFT (was 1000)
  patternShader.setUniform('time', millis() / 250.0);
  // Current resolution
  patternShader.setUniform('resolution', [width, height]);

  // Draw a single quad that covers the entire screen
  rect(0, 0, width, height);

  // Draw UI text on top (no shader)
  resetShader();
  push();
  // Convert WEBGL center coordinates to top-left style
  translate(-width / 2, -height / 2);

  fill(255);
  textAlign(LEFT, BOTTOM);
  textSize(16);
  // Bottom-left corner with small margin
  text('by corbun', 10, height - 10);
  pop();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation Shader Activation shader(patternShader);

Activates the compiled shader so all subsequent drawing commands use it

calculation Uniform Configuration patternShader.setUniform('time', millis() / 250.0); patternShader.setUniform('resolution', [width, height]);

Sends the current time and canvas size to the shader as uniform variables it can read

calculation Pattern Rectangle rect(0, 0, width, height);

Draws a rectangle covering the entire canvas; the shader runs on every pixel of this rectangle

conditional UI Text Rendering resetShader(); push(); translate(-width / 2, -height / 2); fill(255); textAlign(LEFT, BOTTOM); textSize(16); text('by corbun', 10, height - 10);

Disables the shader, adjusts coordinates, and draws white text in the bottom-left corner without the pattern effect

shader(patternShader);
Activates the shader we compiled in setup() so it will run for all subsequent drawing
patternShader.setUniform('time', millis() / 250.0);
Sends the elapsed time in milliseconds, divided by 250, to the shader as a uniform named 'time'. Dividing by 250 controls animation speed—lower numbers make it faster, higher numbers slow it down
patternShader.setUniform('resolution', [width, height]);
Sends the canvas width and height to the shader so it knows the screen dimensions (though this example doesn't currently use it)
rect(0, 0, width, height);
Draws a rectangle centered at (0,0) with size equal to the canvas—this is where the shader runs and fills every pixel with the animated pattern
resetShader();
Turns off the shader so subsequent drawing uses regular p5.js rendering instead
translate(-width / 2, -height / 2);
Adjusts coordinates from WEBGL's center-based system back to the standard top-left system so text appears in the right place
text('by corbun', 10, height - 10);
Draws the text 'by corbun' 10 pixels from the left edge and 10 pixels from the bottom in white

windowResized()

windowResized() is called automatically whenever the browser window is resized. We use it to scale the canvas to the new size and update the shader with the new dimensions. This ensures the pattern stretches smoothly across the entire window at any size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  if (patternShader) {
    patternShader.setUniform('resolution', [width, height]);
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

conditional Conditional Shader Update if (patternShader) { patternShader.setUniform('resolution', [width, height]); }

Only updates the shader's resolution uniform if the shader exists, preventing errors during startup

resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the browser window's new width and height
if (patternShader) {
Checks that patternShader has been created (not null) before trying to use it
patternShader.setUniform('resolution', [width, height]);
Sends the new canvas dimensions to the shader so it stays aware of the current screen size

📦 Key Variables

patternShader object

Holds the compiled WebGL shader program created from vertSrc and fragSrc. Used to calculate the animated pattern.

let patternShader;
uiFont object

Stores the font loaded from the internet in preload(), used to render the 'by corbun' text

let uiFont;
vertSrc string

The vertex shader source code as a string. It positions the geometry and passes UV coordinates to the fragment shader.

const vertSrc = `precision mediump float; ...`;
fragSrc string

The fragment shader source code as a string. It calculates the psychedelic color pattern for every pixel using sine/cosine waves and time.

const fragSrc = `precision mediump float; ...`;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE preload() and setup()

Loading a font from a CDN every time the sketch runs adds unnecessary latency and dependency on external servers.

💡 For a production sketch, embed the font as a data URL, use a system font by omitting textFont(), or serve the font from your own server. Alternatively, use a lightweight web-safe font like Arial or system-ui.

STYLE Fragment shader

The resolution uniform is set but never used in the shader calculations, making it dead code.

💡 Either remove the unused uniform or use it to aspect-correct the pattern, e.g., uv.x *= resolution.x / resolution.y to maintain consistent wave spacing on non-square screens.

FEATURE draw()

The pattern is fully deterministic based only on time; there is no interactivity with mouse or keyboard input.

💡 Add mouseX and mouseY as uniforms to the shader to let users influence the pattern by moving their cursor, or add keyboard controls to change animation speed on the fly.

BUG draw() text rendering

If the window is resized to be very small, the text may overlap the pattern or become unreadable.

💡 Add a semi-transparent background behind the text (e.g., rect(0, height - 30, 150, 30); fill(0, 150);) or scale textSize() based on window size to ensure legibility at all scales.

🔄 Code Flow

Code flow showing preload, setup, draw, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-creation[canvas-creation] canvas-creation --> shader-compilation[shader-compilation] shader-compilation --> draw[draw loop] draw --> shader-activation[shader-activation] shader-activation --> uniform-setup[uniform-setup] uniform-setup --> quad-draw[quad-draw] quad-draw --> text-overlay[text-overlay] draw --> draw setup --> windowresized[windowresized] windowresized --> canvas-resize[canvas-resize] canvas-resize --> windowresized click setup href "#fn-setup" click draw href "#fn-draw" click windowresized href "#fn-windowresized" click canvas-creation href "#sub-canvas-creation" click shader-compilation href "#sub-shader-compilation" click shader-activation href "#sub-shader-activation" click uniform-setup href "#sub-uniform-setup" click quad-draw href "#sub-quad-draw" click text-overlay href "#sub-text-overlay" click canvas-resize href "#sub-canvas-resize"

❓ Frequently Asked Questions

What visual effects does the 'Cool gradient' sketch create?

The 'Cool gradient' sketch generates a full-screen display of flowing, colorful waves that continuously morph and ripple, creating a vibrant psychedelic effect.

Is there any user interaction available in the 'Cool gradient' sketch?

The sketch is primarily a visual experience without user interaction; it automatically animates and fills the screen with dynamic color patterns.

What creative coding concepts are showcased in this p5.js sketch?

This sketch demonstrates the use of WEBGL shaders for real-time graphics, including techniques for creating organic motion and colorful patterns through mathematical functions.

Preview

Cool gradient - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Cool gradient - Code flow showing preload, setup, draw, windowresized
Code Flow Diagram