threlte logo

Terrain with 3D noise

The key to creating “random” yet smooth terrain is using “noise”.

To create a terrain map, we want to be able to input the x and y coordinate , and return a height value. To get the height we want to utilise 2D noise. There’s a SimplexNoise class in the Three.js addons that produces noise values from an x and y inputs.

<script lang="ts">
  import Scene from './Scene.svelte'
  import { Canvas } from '@threlte/core'
  import { Checkbox, Pane } from 'svelte-tweakpane-ui'
  import { World } from '@threlte/rapier'

  let autoRotate = $state(true)
</script>

<Pane
  title="3D noise terrain"
  position="fixed"
>
  <Checkbox
    label="Auto Rotate Camera"
    bind:value={autoRotate}
  />
</Pane>

<div>
  <Canvas>
    <World>
      <Scene {autoRotate} />
    </World>
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { DEG2RAD } from 'three/src/math/MathUtils.js'
  import { OrbitControls } from '@threlte/extras'
  import { PlaneGeometry } from 'three'
  import { SimplexNoise } from 'three/examples/jsm/Addons.js'
  import { T } from '@threlte/core'

  let { autoRotate = false }: { autoRotate?: boolean } = $props()

  const geometry = new PlaneGeometry(10, 10, 100, 100)

  const vertices = geometry.getAttribute('position')

  const flatness = 4

  const noise = new SimplexNoise()

  for (let i = 0; i < vertices.count; i += 1) {
    const x = vertices.getX(i)
    const y = vertices.getY(i)
    vertices.setZ(i, noise.noise(x / flatness, y / flatness))
  }
  // needed for lighting
  geometry.computeVertexNormals()
</script>

<T.PerspectiveCamera
  makeDefault
  position.y={5}
  position.z={10}
  lookAt.y={2}
>
  <OrbitControls
    {autoRotate}
    maxPolarAngle={DEG2RAD * 80}
  />
</T.PerspectiveCamera>

<T.DirectionalLight position={[3, 10, 10]} />
<T.HemisphereLight intensity={0.2} />

<T.Mesh
  {geometry}
  rotation.x={DEG2RAD * -90}
>
  <T.MeshStandardMaterial />
</T.Mesh>

Modifying the Height of Each Vertex

Changing the height of each vertex is accomplished by looping over the vertices of the geometry and setting the z-value of each vertex to a value generated by the noise function. The height of each value is the z component because the plane is created in the xy-plane. Z-values are modified and the plane is rotated to lay flat. Z-up is a very common coordinate system especially in structural design and 3D-printing.

const noise = new SimplexNoise()

for (let i = 0; i < vertices.count; i += 1) {
  const x = vertices.getX(i)
  const y = vertices.getY(i)
  vertices.setZ(i, noise.noise(x / 4, y / 4))
}