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)
let scene = $state()
</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 very common to see especially 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))
}
// ...
})