Skip to content
Back to projects

Personal project

  • ThreeJS
  • Javascript
  • Creative Coding

3D Pipes started as a way for me to learn WebGL and Three.js.

I wanted something small enough that I could actually finish it, but interesting enough to force me to work with real 3D problems. I eventually landed on something I remembered immediately from childhood: the classic Windows 3D Pipes screensaver.

The idea

The idea was simple. Generate pipes that wander randomly through a 3D space, turn in different directions, and slowly fill the screen.

What started as a Three.js experiment eventually became an interactive generator, a performance playground, and the project that pushed me to start my YouTube channel.

Making a random walk look like a pipe

The random walk algorithm itself was the easy part.

At every step, a pipe chooses a direction, moves forward, and occasionally turns. The harder part was turning that sequence of positions into something that actually looked like one continuous pipe.

Every new segment depends on where the pipe came from and where it is going next. A straight section is simple, but as soon as the pipe turns I need to figure out the correct orientation for the corner connecting those two directions.

A pipe might come from the left and turn upward, come from above and turn toward the camera, or take any of the other valid combinations in 3D space. The geometry itself was not the difficult part. The challenge was correctly reasoning about orientation at every step.

It became a surprisingly useful exercise in thinking about 3D coordinates, directions, rotations, and how objects relate to each other in space.

Turning it into a playground

Once the basic generator worked, I did not want it to just be an animation you watched. I wanted people to be able to mess with it.

So I added controls for the parts of the generation that had the biggest effect on the result. You can change the size of the boundary the pipes are allowed to move inside, control how many pipes are generated, and adjust their turn randomness - essentially how eager a pipe is to change direction instead of continuing straight.

Small changes can produce very different scenes. A large boundary with a few pipes feels sparse, while a smaller space with many pipes quickly turns into a dense mess of geometry. Increasing turn randomness creates tangled structures, while reducing it produces much longer straight runs.

That turned the project from a screensaver recreation into something closer to a small procedural 3D toy.

Making random generations shareable

There was one problem with procedural generation: if somebody found a generation they really liked, refreshing the page would normally make it disappear forever.

So I replaced normal randomness with seeded random number generation.

Given the same seed and the same settings, the generator produces the same result every time.

That gave me a very simple sharing system. Instead of storing or uploading an entire 3D scene, I only needed to share the seed and configuration needed to recreate it.

I liked this solution because it made sharing almost free. The generation itself does not need to be saved anywhere; it can simply be generated again.

Learning to make it fast

The first version focused on making the idea work. Once I started pushing the number of pipes and segments higher, performance became another interesting problem.

A scene made from hundreds or thousands of individual Three.js objects can get expensive very quickly. There is CPU work to manage all those objects and GPU work to draw them.

I explored techniques such as InstancedMesh, where many copies of the same geometry can be rendered much more efficiently, along with techniques like offscreen culling so the renderer does not spend time drawing geometry that cannot currently be seen.

This was one of my favorite parts of the project because optimization stopped being an abstract idea. I could make a change, push the generator harder, and immediately see the difference.

The project went from teaching me how to put objects into a Three.js scene to making me think about what actually happens when a browser has to render thousands of them.

This pursuit of performance eventually turned into another video, which you can check out here:

The project that started my YouTube channel

I also wanted to document the entire process.

Rather than writing a normal project post, I decided to turn the build into a YouTube video: what I was trying to recreate, the problems I ran into, how the generation worked, and how the final version came together.

That video became the start of my YouTube channel.

I later made another video entirely around optimizing the project, which gave me a reason to go deeper into Three.js performance instead of stopping once the first version looked good.

It is similar to why I built Labyrinth in the first place: if I can explain what I made and why it works, I probably understand it much better.

What I took away from it

Three.js is incredibly powerful, but this project also made it obvious how little of it I have explored so far.

Watching the project evolve from a few boxes moving through a scene into something that genuinely reminded me of the 3D Pipes screensaver I remembered from childhood was incredibly satisfying.

Along the way I got hands-on experience with 3D coordinates, rotations, procedural generation, deterministic randomness, rendering performance, instancing, and culling.

3D Pipes gave me enough understanding to build something I had wanted to recreate for years, while also showing me just how much further there is to go with Three.js.

But the biggest takeaway was probably simpler than any of those.

3D programming is ridiculously fun.

Explore more projects