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.
Therefore we want to utilise 2D noise, and this example uses the function createNoise2D
from the package simplex-noise
.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { World } from '@threlte/rapier'
import Scene from './Scene.svelte'
import { Pane, Checkbox } from 'svelte-tweakpane-ui'
import { showCollider, autoRotate } from './state'
</script>
<Pane
title=""
position="fixed"
>
<Checkbox
label="Show Collider"
bind:value={$showCollider}
/>
<Checkbox
label="AutoRotate"
bind:value={$autoRotate}
/>
</Pane>
<div>
<Canvas>
<World>
<Scene />
</World>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { PlaneGeometry } from 'three'
import { DEG2RAD } from 'three/src/math/MathUtils.js'
import { createNoise2D } from 'simplex-noise'
import { T } from '@threlte/core'
import { OrbitControls } from '@threlte/extras'
import { AutoColliders, Debug } from '@threlte/rapier'
import { showCollider, autoRotate } from './state'
const geometry = new PlaneGeometry(10, 10, 100, 100)
const noise = createNoise2D()
const vertices = geometry.getAttribute('position').array
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i]
const y = vertices[i + 1]
// @ts-ignore
vertices[i + 2] = noise(x / 4, y / 4)
}
// needed for lighting
geometry.computeVertexNormals()
</script>
<Debug visible={$showCollider} />
<T.PerspectiveCamera
makeDefault
position.y={5}
position.z={10}
lookAt.y={2}
>
<OrbitControls
autoRotate={$autoRotate}
enableZoom={false}
maxPolarAngle={DEG2RAD * 80}
/>
</T.PerspectiveCamera>
<T.DirectionalLight position={[3, 10, 10]} />
<T.HemisphereLight intensity={0.2} />
<AutoColliders shape="trimesh">
<T.Mesh
{geometry}
rotation.x={DEG2RAD * -90}
>
<T.MeshStandardMaterial />
</T.Mesh>
</AutoColliders>
import { writable } from 'svelte/store'
export let showCollider = writable(false)
export let autoRotate = writable(true)
How is this done?
In Scene.svelte:
- Create noise map
const noise = createNoise2D()
- Create plane geometry
const geometry = new PlaneGeometry(10, 10, 100, 100)
Reminder that planes are on their side by default!
i.e. they extend in the x and y directions, and the z value of each vertex is 0
-
Extract the “position” (vertices) array from the PlaneGeometry
const geometry = geometry.getAttribute('position').array
-
Loop over the vertices, setting each
z
value, using our noise map
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i]
const y = vertices[i + 1]
// @ts-ignore
vertices[i + 2] = noise(x / 4, y / 4)
}
Why i+=3
?
The position array is a flat array of vertices, in the recurring format “x y z x y z…” So if we want to set each vertex’s z value from it’s x and y, we need to loop in triplets.
- Attach the plane geometry to a mesh, and rotate
<T.Mesh
{geometry}
rotation.x={DEG2RAD * -90}
>
This example intentionally used only part of the noise map (the middle 25%), in order to generate gentler terrain.
To “see” the noise map in full, change noise(x/4, y/4)
to noise(x, y)
.
You’ll see that the terrain is much more hilly!