terrain-with-3d-noise
Noise is often used in graphics and game development to create “smooth randomness”.
Three.js has a SimplexNoise addon that can be used for this purpose. In the example below, it is used to generate a smooth random surface.
<script lang="ts">
import Scene from './Scene.svelte'
import { Canvas } from '@threlte/core'
import { Checkbox, Pane, Slider } from 'svelte-tweakpane-ui'
let autoRotate = $state(true)
let flatness = $state(4)
</script>
<Pane
title="3D noise terrain"
position="fixed"
>
<Checkbox
label="Auto-rotate Camera"
bind:value={autoRotate}
/>
<Slider
bind:value={flatness}
label="flatness"
min={1}
max={10}
step={1}
/>
</Pane>
<div>
<Canvas>
<Scene
{autoRotate}
{flatness}
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { Environment, OrbitControls } from '@threlte/extras'
import { DoubleSide, PlaneGeometry } from 'three'
import { SimplexNoise } from 'three/examples/jsm/Addons.js'
import { T } from '@threlte/core'
let { autoRotate = false, flatness = 4 }: { autoRotate?: boolean; flatness?: number } = $props()
const geometry = new PlaneGeometry(10, 10, 100, 100)
const positions = geometry.getAttribute('position')
const noise = new SimplexNoise()
$effect(() => {
for (let i = 0; i < positions.count; i += 1) {
const x = positions.getX(i) / flatness
const y = positions.getY(i) / flatness
positions.setZ(i, noise.noise(x, y))
}
positions.needsUpdate = true
// needed for lighting
geometry.computeVertexNormals()
})
</script>
<T.PerspectiveCamera
makeDefault
position={10}
>
<OrbitControls
{autoRotate}
autoRotateSpeed={0.5}
/>
</T.PerspectiveCamera>
<Environment url="/textures/equirectangular/hdr/shanghai_riverside_1k.hdr" />
<T.Mesh
{geometry}
rotation.x={-1 * 0.5 * Math.PI}
>
<T.MeshStandardMaterial side={DoubleSide} />
</T.Mesh>
Setting the Height of Each Vertex
After the geometry is created, the z-value of each vertex’s position is set with a value generated from the noise function.
const noise = new SimplexNoise()
for (let i = 0; i < positions.count; i += 1) {
const x = positions.getX(i) / flatness
const y = positions.getY(i) / flatness
positions.setZ(i, noise.noise(x, y))
}
The flatness variable scales down the x and y values that are passed to the noise function. A higher flatness value corresponds to smaller changes between noise values thus a flatter surface.
When updating attributes of a geometry after the first render, you may have to set
attribute.needsUpdate to true. It may also be necessary to recalculate the geometry’s vertex
normals using geometry.computeVertexNormals().
Rotating the geometry
One important thing to note is that the plane geometry is created in the xy-plane. This is why the z-value is treated as the height of the vertex and the geometry is rotated 90 degrees. The Z-up coordinate system is common to see in such areas as structural design and 3D-printing.
Deterministic noise values
SimplexNoise.noise is deterministic. In other words, when given the same x and y, the output is always the same. If you want to produce different results, you can offset the x and y inputs by some amount.
const noise = new SimplexNoise()
$effect(() => {
const randomOffset = Math.random()
for (let i = 0; i < positions.count; i += 1) {
const x = positions.getX(i) / flatness + randomOffset
const y = positions.getY(i) / flatness + randomOffset
positions.setZ(i, noise.noise(x, y))
}
// ...
})