Overview
A procedural 3D terrain generator built in Processing for a Computer Graphics course, then pushed past the brief to render textures and project full images onto the moving surface. A flat 2D grid is rotated on the X axis and given Z values from a noise function — the result reads as rolling terrain because the noise is organic, not random.
How it works
The mesh is a regular grid of vertices. The interesting choice is what
goes into each Z. Java's pseudo-random Math.random() produces white
noise — uncorrelated values that look like static, not landscape.
Perlin noise interpolates between gradients on a lattice, so nearby
points get nearby heights and the surface stays continuous as it scrolls.
The 2D variant used here has the shape:
Octaves stack scaled copies of the noise to add detail without losing the low-frequency hills. Time is folded into the inputs so the whole field moves; the camera doesn't translate, the noise does.
Colour is mapped from height: low Z values get cooler tones, peaks get warmer ones. Lighting is Processing's built-in directional light applied per vertex.
Stack and shape
Processing is the right tool for this exact project — the loop is
setup() / draw(), the 3D primitives are one function call, and the
G4P GUI builder hands you sliders for every parameter without writing
a layout. The same project in a "real" engine (three.js, Bevy, raw
WebGL) would be a week of boilerplate before the first triangle
appears. The trade-off is that performance ceilings are low: recording
mode captures frames synchronously inside draw() and the framerate
visibly drops while it runs.
The fun extension was treating the terrain as a screen rather than a landscape — projecting a single image across the grid and letting the noise deform it. The Blender flag-in-the-wind animation was the reference; the Processing version is cruder but the idea carries.
What I'd change
This is what I'd do differently if I picked the project up again, not a feature wishlist.
- Fix the mesh-type bug.
QUADSandTRIANGLESmodes draw incorrectly because vertex order doesn't match whatbeginShape()expects per primitive. The fix is per-mode index arithmetic, not a rewrite. - Interpolate texture colour by Z. The README notes this in passing and it's the obvious next step — blend the texture sample with a height-based colour ramp so peaks read as snow, valleys as grass, without painting a custom texture.
- Move recording off the render thread. Frame capture inside
draw()halves the framerate during recording; an async writer with a ring buffer would let the scene run at full speed and dump frames as the disk catches up. - Re-host outside Processing. Most of what's interesting here — the noise, the mesh, the height-to-colour mapping — translates directly to a WebGL shader. A browser-hosted version would be the natural way to show this off without asking a recruiter to install Java 17.
Repo
Source on GitHub. MIT. The core idea came from Daniel Shiffman's Coding Train terrain video; texture mode and whole-image mode are extensions on top.
