TECH DETAILS

Demo Scene Tribute - Day 13

Advanced Level

Table of Contents

01 / Plasma Effect

The plasma effect is a classic demo scene visual created by combining multiple sine waves at different frequencies and phases. Each pixel's color is determined by summing these waves, creating organic, flowing patterns.

// Plasma uses ImageData for direct pixel manipulation const plasmaBuffer = ctx.createImageData(width, height); const data = plasmaBuffer.data; const t = time * 0.02; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const i = (y * width + x) * 4; // Combine multiple sine waves let v = Math.sin(x * 0.02 + t); // Horizontal wave v += Math.sin(y * 0.02 + t * 0.7); // Vertical wave v += Math.sin((x + y) * 0.015 + t * 0.5); // Diagonal wave v += Math.sin(Math.sqrt(x*x + y*y) * 0.02 + t); // Radial wave v = v / 4; // Normalize to -1..1 // Color cycling with phase offsets data[i] = Math.sin(v * Math.PI + t) * 127 + 128; // Red data[i + 1] = Math.sin(v * Math.PI + t + 2) * 127 + 128; // Green data[i + 2] = Math.sin(v * Math.PI + t + 4) * 127 + 128; // Blue data[i + 3] = 255; // Alpha } } ctx.putImageData(plasmaBuffer, 0, 0);

Why ImageData?

For per-pixel effects like plasma, using putImageData() is much faster than drawing thousands of individual rectangles. We manipulate the pixel buffer directly, then blast it to the canvas in one operation.

Demo Scene History

Plasma was one of the first "impossible" effects on home computers. The Amiga demoscene popularized it in the late 80s using the Copper chip for color cycling.

02 / Copper Bars

Copper bars simulate the Amiga's Copper co-processor, which could change colors mid-scanline. We create smooth horizontal gradient bars that wave up and down using sine functions.

// Multiple copper bars with sine wave motion const numBars = 12; for (let i = 0; i < numBars; i++) { // Each bar oscillates at a different phase const barY = height/2 + Math.sin(time * 0.03 + i * 0.5) * 150; const barHeight = 20 + Math.sin(time * 0.05 + i) * 10; // Copper gradient: dark -> bright -> dark (raster bar look) const gradient = ctx.createLinearGradient( 0, barY - barHeight/2, 0, barY + barHeight/2 ); const hue = (time * 2 + i * 30) % 360; gradient.addColorStop(0, `hsl(${hue}, 100%, 20%)`); gradient.addColorStop(0.3, `hsl(${hue}, 100%, 60%)`); gradient.addColorStop(0.5, `hsl(${hue}, 100%, 90%)`); // Hotspot gradient.addColorStop(0.7, `hsl(${hue}, 100%, 60%)`); gradient.addColorStop(1, `hsl(${hue}, 100%, 20%)`); ctx.fillStyle = gradient; ctx.fillRect(0, barY - barHeight/2, width, barHeight); }

The Raster Bar Effect

The symmetric gradient creates the classic "raster bar" look - dark edges with a bright center that simulates light reflecting off a 3D metallic bar.

03 / 3D Starfield Projection

The 3D starfield uses perspective projection to create the illusion of flying through space. Stars move toward the viewer, getting larger and brighter as they approach.

// Initialize stars in 3D space const stars = []; for (let i = 0; i < 300; i++) { stars.push({ x: (Math.random() - 0.5) * 2000, // World X y: (Math.random() - 0.5) * 2000, // World Y z: Math.random() * 1000 + 100 // Depth }); } // Update and project each star stars.forEach(star => { // Move toward viewer star.z -= 5; // Wrap around when too close if (star.z < 1) { star.z = 1000; star.x = (Math.random() - 0.5) * 2000; star.y = (Math.random() - 0.5) * 2000; } // Perspective projection: scale by 1/z const scale = 300 / star.z; const screenX = centerX + star.x * scale; const screenY = centerY + star.y * scale; // Size and brightness based on depth const brightness = (1000 - star.z) / 4; const size = 4 - star.z / 300; });

Motion Trails

The trail effect is achieved by drawing a gradient line from the star's current position toward where it came from. This creates the streaking effect seen in hyperspace sequences.

Perspective Math

The formula screenX = worldX / z is the core of all 3D projection. Dividing by Z makes distant objects appear smaller - the foundation of computer graphics.

04 / 3D Rotating Cube

The rotating cube demonstrates 3D rotation matrices and perspective projection. We define 8 vertices and 12 edges, then rotate and project them each frame.

// Cube vertices (normalized -1 to 1) const vertices = [ [-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1], [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1] ]; // Rotation angles (different speeds for each axis) const angleX = time * 0.02; const angleY = time * 0.03; const angleZ = time * 0.015; // Transform each vertex vertices.map(v => { let [x, y, z] = v; // Rotate around X axis let temp = y; y = y * Math.cos(angleX) - z * Math.sin(angleX); z = temp * Math.sin(angleX) + z * Math.cos(angleX); // Rotate around Y axis temp = x; x = x * Math.cos(angleY) + z * Math.sin(angleY); z = -temp * Math.sin(angleY) + z * Math.cos(angleY); // Rotate around Z axis temp = x; x = x * Math.cos(angleZ) - y * Math.sin(angleZ); y = temp * Math.sin(angleZ) + y * Math.cos(angleZ); // Perspective projection const fov = 4; const scale = fov / (z + 3); return { x: centerX + x * 100 * scale, y: centerY + y * 100 * scale }; });

Rotation Matrix Breakdown

Each rotation uses the 2D rotation formula applied to two axes at a time. X rotation affects Y and Z, Y rotation affects X and Z, and Z rotation affects X and Y.

05 / Sine Wave Scroller

The sine scroller is an iconic demo effect where text waves up and down while scrolling horizontally. Each character is positioned independently based on its X position and time.

const scrollText = "WELCOME TO THE DEMO SCENE..."; let scrollOffset = 0; // Increases each frame const charWidth = 32; // Calculate visible character range const visibleChars = Math.ceil(width / charWidth) + 2; const startChar = Math.floor(scrollOffset / charWidth); for (let i = 0; i < visibleChars; i++) { const charIndex = (startChar + i) % scrollText.length; const char = scrollText[charIndex]; // X position with smooth sub-pixel scrolling const screenX = i * charWidth - (scrollOffset % charWidth); // Y position from sine wave const sineOffset = Math.sin((screenX + time * 3) * 0.02) * 60; const screenY = height / 2 + sineOffset; // Rainbow color per character const hue = (time * 2 + screenX * 0.5) % 360; ctx.fillStyle = `hsl(${hue}, 100%, 70%)`; ctx.fillText(char, screenX, screenY); } scrollOffset += 3; // Scroll speed

The Wave Formula

The magic is in sin((screenX + time) * frequency) * amplitude. The screenX component makes the wave travel across the text, while time makes it animate.

Smooth Scrolling

Using scrollOffset % charWidth gives us sub-pixel precision for smooth scrolling without visible "jumping" between characters.

06 / Tunnel Effect

The tunnel creates a hypnotic infinite corridor effect by drawing concentric rings that shrink toward a center point, with warping and rotation applied.

const centerX = width / 2; const centerY = height / 2; const t = time * 0.03; // Draw rings from outside to inside for (let ring = 50; ring > 0; ring--) { const depth = ring / 50; // 0 = closest, 1 = farthest const radius = 300 * depth; // Tunnel swaying motion const offsetX = Math.sin(t + depth * 2) * 50 * (1 - depth); const offsetY = Math.cos(t * 0.7 + depth * 2) * 50 * (1 - depth); // Color based on depth and time const hue = (time * 3 + ring * 5) % 360; // Draw warped circular ring ctx.beginPath(); for (let angle = 0; angle < Math.PI * 2; angle += 0.1) { // Warping factor makes it non-circular const warp = 1 + Math.sin(angle * 4 + t) * 0.2 * (1 - depth); const px = centerX + offsetX + Math.cos(angle + t * depth) * radius * warp; const py = centerY + offsetY + Math.sin(angle + t * depth) * radius * warp; if (angle === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py); } ctx.closePath(); ctx.stroke(); }

Creating Depth

The key to the tunnel illusion is that inner rings (closer to viewer) have more warping and offset motion, while outer rings (farther away) are more stable and circular.

Demo Scene Legacy

These effects were revolutionary in the 80s-90s when groups like Future Crew, Fairlight, and Sanity pushed hardware to its limits. Today we recreate them with modern web tech!