Skip to content

Tutorial: simplex-noise for Terrain/Resources

A quick story: you want infinite grasslands with scattered forests. Pure RNG looks blotchy; hand-made functions are hard to control. Noise gives you a stable, natural-looking value per coordinate—easy to tune, easy to reproduce via seed.


1. Install

bash
npm install simplex-noise

2. Minimal 2D heightmap

ts
import { createNoise2D } from "simplex-noise";
const noise2D = createNoise2D(); // provide a seeded RNG for reproducibility if needed

function heightAt(x: number, z: number, scale = 0.05, amp = 32, base = 40) {
  const n = noise2D(x * scale, z * scale); // [-1,1]
  return Math.floor(base + n * amp);
}

3. fBM (multi-octave)

ts
import { createNoise2D } from "simplex-noise";
const n2 = createNoise2D();

function fbm2D(x: number, z: number, octaves = 5, lac = 2.0, gain = 0.5, scale = 0.02) {
  let amp = 1, freq = 1, sum = 0, norm = 0;
  for (let o = 0; o < octaves; o++) {
    sum += n2(x * scale * freq, z * scale * freq) * amp;
    norm += amp; amp *= gain; freq *= lac;
  }
  return sum / norm; // [-1,1]
}

4. Domain warp (make it flow)

ts
import { createNoise2D } from "simplex-noise";
const n1 = createNoise2D();
const n2b = createNoise2D();

function domainWarp2D(x: number, y: number, scale = 0.02, warpAmp = 15, warpScale = 0.05) {
  const wx = n1(x * warpScale, y * warpScale) * warpAmp;
  const wy = n2b(x * warpScale, y * warpScale) * warpAmp;
  return n1((x + wx) * scale, (y + wy) * scale);
}

5. With voxels: carve simple terrain (XYZ 256×64×256)

ts
import { createNoise2D, createNoise3D } from "simplex-noise";
const n2v = createNoise2D();
const n3v = createNoise3D();

function heightAt(x: number, z: number) {
  let amp=1, freq=1, sum=0, norm=0; // fBM
  for (let o=0;o<5;o++){ sum+=n2v(x*0.02*freq, z*0.02*freq)*amp; norm+=amp; amp*=0.5; freq*=2; }
  return Math.floor(40 + (sum/norm)*32);
}

function caveDensity(x:number,y:number,z:number){
  const base = n3v(x*0.05,y*0.05,z*0.05);
  const detail = n3v(x*0.12,y*0.12,z*0.12)*0.5;
  return base*0.8 + detail - 0.2; // <0 → air tunnel
}

export function generateChunk(voxels: { setVoxel:(x:number,y:number,z:number,id:string)=>void }, chunkX:number, chunkZ:number, CHUNK=32, MAX_Y=64, WATER=42){
  for(let dz=0;dz<CHUNK;dz++) for(let dx=0;dx<CHUNK;dx++){
    const x = chunkX+dx, z = chunkZ+dz;
    const h = heightAt(x,z);
    for(let y=0;y<MAX_Y;y++){
      let id = "air";
      if (y < h-6) id = "stone"; else if (y < h-1) id = "dirt"; else if (y===h) id = y<WATER?"sand":"grass"; else if (y<=WATER) id="water";
      if (id!=="air" && y < h-1){ if (caveDensity(x,y,z) < 0) id = "air"; }
      voxels.setVoxel(x,y,z,id);
    }
  }
}

6. Tips

  • Use a seeded RNG for reproducible worlds.
  • Sample in world coordinates to avoid seams between chunks.
  • Mix low-frequency shapes with high-frequency detail.
  • Keep MAX_Y modest (e.g., 64) for performance in tests.

神岛实验室