Terrain Generator.

A procedural 3D terrain generator written in Processing — Perlin noise drives a moving mesh that doubles as a screen for textures and whole images.

2022
Terrain Generator — cover

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:

z(x, y) = \sum_{i=0}^{n-1} a^{i}\,\mathrm{perlin}\!\left(2^{i} x,\; 2^{i} y\right)

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.

Same mesh, two noise sources. Perlin first, then Java's pseudo-random — the second is unusable as a landscape.

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.

Texture mode. Processing's texture() does UV-mapping per vertex; the mesh becomes a moving terrain skin.

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.

WholeImage mode — one image stretched across the full grid, rippling with the noise field underneath.

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. QUADS and TRIANGLES modes draw incorrectly because vertex order doesn't match what beginShape() 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.