Recreating the classic Windows 3D Pipes screensaver in modern browsers is easier than you might think, thanks to Three.js and modern WebGL capabilities.
Why Recreate a 30-Year-Old Screensaver?
The 3D Pipes screensaver, which debuted with Windows NT in 1992 and became iconic in Windows 95/98, represents a perfect intersection of nostalgia and technical challenge. It's complex enough to be interesting, but simple enough to understand. Plus, who didn't spend hours watching those colorful pipes wind their way across the screen?
Bringing this classic to the web browser demonstrates how far web technologies have come. What once required native Windows APIs and DirectX can now run smoothly in any modern browser, on any platform, without plugins.
The Building Blocks: Three.js Fundamentals
Three.js is a JavaScript library that makes WebGL accessible to mere mortals. Instead of wrestling with shader programs and vertex buffers, you can create 3D scenes with intuitive JavaScript code.
At its core, every Three.js application needs three essential components:
- Scene: The 3D space where objects live
- Camera: Your viewpoint into the scene
- Renderer: The engine that draws everything to the screen
Setting Up Your Scene
import * as THREE from 'three';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Creating the Pipes: Procedural Generation
The magic of the 3D Pipes screensaver lies in its procedural generation. Each pipe segment must decide: go straight, turn left, turn right, go up, or go down. The trick is making these choices feel random while avoiding self-intersection.
Pipe Geometry
Pipes are essentially cylinders with joints (elbows and tees). Three.js makes creating these primitives straightforward:
// Create a straight pipe segment
const pipeGeometry = new THREE.CylinderGeometry(
0.2, // radius top
0.2, // radius bottom
1.0, // height
12, // radial segments
1 // height segments
);
// Create an elbow joint (90-degree bend)
const elbowGeometry = new THREE.TorusGeometry(
0.2, // radius
0.08, // tube diameter
16, // radial segments
100, // tubular segments
Math.PI / 2 // arc (90 degrees)
);
Materials and Colors
The original screensaver used bold, saturated colors. Three.js's MeshPhongMaterial gives us nice shiny surfaces that catch the light:
const colors = [
0xff0000, // Red
0x00ff00, // Green
0x0000ff, // Blue
0xffff00, // Yellow
0xff00ff, // Magenta
0x00ffff // Cyan
];
const material = new THREE.MeshPhongMaterial({
color: colors[Math.floor(Math.random() * colors.length)],
shininess: 100,
specular: 0x444444
});
The Algorithm: Growing the Pipes
Each pipe grows one segment at a time. The algorithm needs to track the current position, direction, and ensure pipes don't grow outside the visible area or intersect themselves.
class PipeGrower {
constructor(scene) {
this.position = new THREE.Vector3(0, 0, 0);
this.direction = new THREE.Vector3(1, 0, 0);
this.color = this.randomColor();
this.scene = scene;
}
grow() {
// Add a straight segment in current direction
this.addSegment();
// Randomly decide to turn
if (Math.random() < 0.3) {
this.turn();
}
// Move forward
this.position.add(this.direction.clone().multiplyScalar(1.0));
// Check boundaries
if (this.isOutOfBounds()) {
this.reset();
}
}
turn() {
const turns = this.getPossibleTurns();
const newDirection = turns[Math.floor(Math.random() * turns.length)];
this.addElbow(this.direction, newDirection);
this.direction = newDirection;
}
}
Adding the Details: Lighting and Animation
Good lighting makes the difference between "meh" and "wow". The original screensaver had that characteristic glossy look that we can replicate:
// Ambient light for overall brightness const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); // Directional light for highlights const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 10); scene.add(directionalLight); // Point light that follows the camera const pointLight = new THREE.PointLight(0xffffff, 0.5); camera.add(pointLight); scene.add(camera);
Camera Movement
To recreate that dreamy, floating feeling of the original, we slowly rotate the camera around the scene:
function animate(time) {
requestAnimationFrame(animate);
// Slowly rotate camera
camera.position.x = Math.cos(time * 0.0001) * 5;
camera.position.y = Math.sin(time * 0.0002) * 3;
camera.lookAt(scene.position);
// Grow pipes
pipes.forEach(pipe => pipe.grow());
renderer.render(scene, camera);
}
animate(0);
Performance Optimization
As pipes grow, you'll accumulate thousands of segments. Without optimization, your framerate will tank. Here are key strategies:
- Geometry Merging: Combine multiple pipe segments into single meshes to reduce draw calls
- Frustum Culling: Let Three.js automatically skip rendering objects outside the camera view
- Level of Detail: Use simpler geometry for distant pipes
- Segment Limits: Remove old segments when pipes grow too long
// Limit total segments
if (this.segments.length > 100) {
const oldSegment = this.segments.shift();
this.scene.remove(oldSegment);
oldSegment.geometry.dispose();
oldSegment.material.dispose();
}
Adding Polish: The Finishing Touches
Small details make a big difference:
- Joint Caps: Add spheres at pipe ends for a cleaner look
- Varied Speeds: Make different pipes grow at different rates
- Teapots: Yes, the original could place 3D teapots as joint connectors. Load an OBJ model for authentic nostalgia
- Mixed Joint Types: Alternate between elbows, tees, and spheres
Beyond the Basics: Modern Enhancements
Since we're using modern web technologies, why not add some features the original never had?
- Post-Processing: Add bloom effects for extra glow
- Real-time Controls: Let users adjust speed, colors, and pipe count
- Sound: Add subtle ambient audio that reacts to pipe generation
- Mobile Support: Touch controls for rotation and zoom
- VR Mode: Experience the pipes in virtual reality with WebXR
The Complete Pipeline
Here's the basic structure to get you started:
- Set up Three.js scene, camera, and renderer
- Create geometry templates for pipes and joints
- Implement the pipe growth algorithm
- Add lighting for that classic glossy look
- Implement camera rotation/movement
- Add performance optimizations
- Polish with details and effects
Lessons Learned
Recreating retro graphics teaches important lessons about procedural generation, 3D mathematics, and performance optimization. The constraints of the original (limited colors, simple geometry) actually make it more interesting to recreate - sometimes limitations breed creativity.
More importantly, projects like this bridge the past and present. They show that "old" doesn't mean "obsolete" - classic algorithms and designs still have value, both educational and aesthetic.
"The best part about recreating classic screensavers isn't the code - it's the moment when someone sees it and says 'Oh my god, I remember this!' That connection to the past, that shared cultural memory, that's what makes retro computing projects worthwhile."
Try It Yourself
The complete source code for this 3D Pipes implementation is available on our homepage. Open your browser's developer console while watching the pipes - you might spot some hidden features and Easter eggs we've included as a tribute to the original.
Whether you're a nostalgic Windows 95 veteran or a young developer curious about retro computing, rebuilding classics like this is both fun and educational. So fire up your code editor, import Three.js, and let's get those pipes flowing!