setup()
setup() runs once and is the right place to do expensive one-time work - like pre-rendering a gradient into a graphics buffer and filling arrays with randomized objects - so the draw() loop can stay fast and focused purely on animation.
🔬 Each band gets random noise offsets so they never move in sync. What happens visually if you set xoff, yoff, and zoff all to 0 instead of random(1000) for every band - do the bands start looking identical?
for (let i = 0; i < numAuroraBands; i++) {
auroraBands.push({
xoff: random(1000), // Noise offset for horizontal movement
yoff: random(1000), // Noise offset for vertical movement
zoff: random(1000), // Noise offset for shimmering depth
colorIndex: i % auroraColors.length, // Cycle through defined colors
amplitude: random(0.5, 1.5), // How much the band stretches vertically
opacityScale: random(0.2, 0.8), // Base opacity for the band
hueOffset: random(-20, 20) // Slight hue variation for each band
});
}
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB, 360, 100, 100, 100); // Hue, Saturation, Brightness, Alpha
noStroke(); // No outlines for shapes
// --- Create Background Gradient (deep navy to black) ---
// This is rendered once into a p5.Graphics object to save performance.
backgroundGraphics = createGraphics(width, height);
backgroundGraphics.colorMode(HSB, 360, 100, 100, 100);
backgroundGraphics.noStroke();
for (let y = 0; y < height; y++) {
let inter = map(y, 0, height, 0, 1);
// Deep navy at the top, almost black at the bottom
let c1 = color(220, 80, 20); // Deep navy (hue, saturation, brightness)
let c2 = color(220, 80, 0); // Almost black
let c = lerpColor(c1, c2, inter);
backgroundGraphics.fill(c);
backgroundGraphics.rect(0, y, width, 1); // Draw a 1-pixel high rectangle
}
// --- Initialize Stars ---
for (let i = 0; i < numStars; i++) {
stars.push({
x: random(width),
y: random(height * 0.7), // Stars mostly in the upper 70% of the canvas
size: random(1, 3), // Random size for stars
brightness: random(50, 100), // Base brightness
twinkleSpeed: random(0.01, 0.05) // Speed at which the star twinkles
});
}
// --- Define Aurora Color Palette (HSB values) ---
// These colors will be used for the different aurora bands.
auroraColors = [
color(120, 100, 80), // Vibrant Green
color(150, 80, 100), // Lighter, more saturated Green
color(180, 100, 80), // Teal
color(210, 80, 100), // Lighter, more saturated Teal
color(270, 100, 80), // Purple
color(300, 80, 100), // Lighter, more saturated Purple
color(330, 100, 80), // Pink
color(360, 80, 100) // Lighter, more saturated Pink (wraps around to red)
];
// --- Initialize Aurora Bands ---
// Each band has unique noise offsets and properties to create variation.
for (let i = 0; i < numAuroraBands; i++) {
auroraBands.push({
xoff: random(1000), // Noise offset for horizontal movement
yoff: random(1000), // Noise offset for vertical movement
zoff: random(1000), // Noise offset for shimmering depth
colorIndex: i % auroraColors.length, // Cycle through defined colors
amplitude: random(0.5, 1.5), // How much the band stretches vertically
opacityScale: random(0.2, 0.8), // Base opacity for the band
hueOffset: random(-20, 20) // Slight hue variation for each band
});
}
}
Line-by-line explanation (10 lines)
🔧 Subcomponents:
for (let y = 0; y < height; y++) {
Draws one thin horizontal rectangle per row, blending from deep navy at the top to near-black at the bottom, baked into an offscreen buffer once for performance.
for (let i = 0; i < numStars; i++) {
Creates numStars star objects with random position, size, brightness, and twinkle speed.
for (let i = 0; i < numAuroraBands; i++) {
Creates each aurora band's unique noise offsets and visual properties so every band moves and looks slightly different.
createCanvas(windowWidth, windowHeight);- Makes the canvas fill the entire browser window.
colorMode(HSB, 360, 100, 100, 100);- Switches from the default RGB color model to Hue-Saturation-Brightness, which makes it much easier to shift colors (like aurora hues) smoothly by just changing one number.
backgroundGraphics = createGraphics(width, height);- Creates a separate offscreen canvas (a p5.Graphics object) so the gradient can be drawn once here instead of every frame in draw(), which would be wasteful.
let c1 = color(220, 80, 20); // Deep navy (hue, saturation, brightness)- Defines the sky color at the top of the gradient using HSB values (hue 220 is blue, low brightness keeps it dark).
let c = lerpColor(c1, c2, inter);- Blends smoothly between the top color and bottom color based on how far down the canvas this row is (inter goes from 0 to 1).
backgroundGraphics.rect(0, y, width, 1); // Draw a 1-pixel high rectangle- Draws one row of the gradient at a time onto the offscreen buffer, building the full gradient line by line.
y: random(height * 0.7), // Stars mostly in the upper 70% of the canvas- Keeps stars out of the bottom 30% of the screen so they don't appear inside the tree silhouette area.
auroraColors = [- Defines the fixed palette of colors (green, teal, purple, pink) that aurora bands will cycle through.
colorIndex: i % auroraColors.length, // Cycle through defined colors- Uses the modulo operator so if there are more bands than colors, the palette simply repeats.
amplitude: random(0.5, 1.5), // How much the band stretches vertically- Gives each band a random vertical stretch factor so some ribbons of light are taller and more dramatic than others.