WebGPU · three.js · React Three Fiber

threejs-heightfield: Know where the ground is.

Bake any mesh or group into a heightfield once, then look up the height, measure the slope and ray-march the ground anywhere, with no per-object raycasts. A height function works the same way, so a modelled island and an endless procedural world look alike to everything built on top.

screenshot · heightfield.html open ↗
Grass covering a rolling hill mesh, every blade placed on its baked heightfield in the threejs-grass demo
4functions, one peer dependency
1024samples per side by default
12bisection steps per ray hit
NaNoff the terrain, never a guess
Getting started

Ask the ground anything.

Bake once, then every lookup is a few multiplies. threejs-grass and threejs-scatter do this for you when you hand them a terrain; use it directly to put your own things on the same ground.

three.jsthreejs-heightfield
import { Raycaster } from 'three/webgpu'
import { bakeHeightfield, createHeightSampler, raycastHeight,
  slopeAt } from 'threejs-heightfield'

const height = bakeHeightfield(island) // any mesh, baked once
const y = height(12, -40)              // NaN off the terrain

// keep trees off cliffs
if (slopeAt(height, 12, -40) < 0.6) tree.position.set(12, y, -40)

// brushes and picking without a triangle raycast
const ray = new Raycaster()
ray.setFromCamera(pointer, camera)
const { origin, direction } = ray.ray
const hit = raycastHeight(height, origin, direction)

// procedural worlds: functions pass straight through
const hills = createHeightSampler((x, z) => 6 * Math.sin(x / 50))
React Three Fiberthreejs-heightfield in React
import { useMemo } from 'react'
import { useGLTF } from '@react-three/drei'
import { bakeHeightfield, slopeAt } from 'threejs-heightfield'
import { Grass } from 'threejs-grass/react'

function Island() {
  const { scene } = useGLTF('/island.glb')
  const height = useMemo(() => bakeHeightfield(scene), [scene])
  const flat = spots.filter(([x, z]) =>
    slopeAt(height, x, z) < 0.4)
  return (
    <>
      <primitive object={scene} />
      <Grass terrain={height} />
      {flat.map(([x, z], i) => (
        <mesh key={i} geometry={rock}
          position={[x, height(x, z), z]} />
      ))}
    </>
  )
}
Install npm i threejs-heightfield three Full API reference → React components →
What's in the box

Small, and underneath everything.

Four functions and two types. The sandbox puts all four on a height function and on baked meshes side by side; threejs-grass and threejs-scatter sample their terrain through it, so these shots come from their demos.

01

Bake any mesh

Triangles in, heights out.

bakeHeightfield() rasterises every triangle under a mesh or group on the CPU, world transforms included, into a grid of about maxResolution samples along the longer side (1024 by default). Where surfaces overlap the highest one wins, so things land on the bridge, not under it.

Grass hugging a hill mesh beside a dirt path, each blade set on the baked heightfieldthreejs-grass demo
02

Smooth lookups

Bilinear inside, NaN outside.

The baked sampler blends the four nearest samples, so heights are continuous across the grid and objects don't step. Off the edge of the terrain it returns NaN instead of a made-up zero, and callers simply skip those points.

Tall grass following a hillside, with a dirt path cut across itthreejs-grass demo
03

Functions pass straight through

Endless worlds, same API.

createHeightSampler() takes a mesh, an (x, z) => y function or nothing at all (flat ground at y = 0) and always hands back a HeightFn. Procedural terrain, such as the height of a threejs-biomes world, is never baked, so it can go on forever.

Procedural mountains and winding rivers from a height function, seen from the airthreejs-biomes demo
04

Slope

Keep trees off cliffs.

slopeAt() measures rise over run by central differences, 25 cm either side by default. Grass uses it to skip ground steeper than maxSlope; use it the same way to keep trees off cliffs and buildings off hillsides.

Grass running up and over a rolling hill to the horizonthreejs-grass demo
05

Ray-march the ground

Brushes, picking, camera collision.

raycastHeight() marches a ray in half-metre steps, then bisects twelve times to the surface. It works on baked meshes and procedural terrain alike, so nothing needs a triangle raycast. The threejs-grass terrain painter runs on it.

threejs-grass painter
06

Made for React too

Nothing new to learn.

There is no component: bake in a useMemo and pass the sampler to <Grass>, <Scatter> or your own meshes. Those components also take a mesh ref as terrain and bake it for you.

The threejs-grass React Three Fiber demo, its grass sampled from a terrain mesh refReact Three Fiber demo

Stand on solid ground.

Every function is documented with examples in the API reference. Try every parameter in the sandbox, and see it at work in the grass and scatter demos.