Skip to content

Lerp

Video Lecture

Section Video Links
Lerp : Part 1 Lerp Lerp
Lerp : Part 2 Lerp Lerp

Description

Animating object transforms and material properties using interpolation (Lerp).

Lerping is a very minimal and simple technique used to quickly transition a property from one state to another.

In this lesson, I use the lerp technique to modify the cameras position. I also modify many objects positions, rotations and colors depending on whether that object was clicked or the pointer is hovering over it.

I also quickly demonstrate the Drei Center helper, using Array.map to generate a series of objects in the JSX and introduce the React Three Fiber useThree hook.

./src/App.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Canvas, useThree, useFrame } from '@react-three/fiber'
import { Vector3 } from 'three'
import { Stats, Environment, Center } from '@react-three/drei'
import Button from './Button'

const vec = new Vector3()

function Rig() {
  return useFrame(({ camera, pointer }) => {
    vec.set(pointer.x * 2, pointer.y * 2, camera.position.z)
    camera.position.lerp(vec, 0.025)
    camera.lookAt(0, 0, 0)
  })
}

export default function App() {
  return (
    <Canvas camera={{ position: [0, 0, 5] }}>
      <Environment preset="forest" background />
      <Center>
        {[...Array(5).keys()].map((x) =>
          [...Array(3).keys()].map((y) => (
            <Button key={x + y * 5} position={[x * 2.5, y * 2.5, 0]} />
          ))
        )}
      </Center>
      <Rig />
      <Stats />
    </Canvas>
  )
}

./src/Button.jsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { useRef, useState, useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import { MathUtils } from 'three'
import { Color } from 'three'

const black = new Color('black')
export default function Button(props) {
  const ref = useRef()
  const [hovered, setHovered] = useState(false)
  const [selected, setSelected] = useState(false)
  const colorTo = useMemo(
    () => new Color(Math.floor(Math.random() * 16777216)),
    []
  )

  useFrame(() => {
    ref.current.rotation.x = hovered
      ? MathUtils.lerp(ref.current.rotation.x, -Math.PI * 2, 0.025)
      : MathUtils.lerp(ref.current.rotation.x, 0, 0.025)

    ref.current.position.z = selected
      ? MathUtils.lerp(ref.current.position.z, 0, 0.025)
      : MathUtils.lerp(ref.current.position.z, -3, 0.025)

    ref.current.material.color.lerp(selected ? colorTo : black, 0.025)
  })

  return (
    <mesh
      {...props}
      ref={ref}
      onPointerDown={() => {
        setSelected(!selected)
      }}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
    >
      <icosahedronGeometry />
      <meshPhysicalMaterial
        roughness={0}
        metalness={0}
        thickness={3.12}
        ior={1.74}
        transmission={1.0}
      />
    </mesh>
  )
}

Lerp with Epsilon

One problem with Lerp, that may not ever be a problem for you, is that the destination/to of the lerp may never arrive, although it will be very close, and may appear to be correct. Lerping takes the current value, and the destination/to value, and moves it closer depending on the alpha value. The resulting difference will be a floating point number getting closer and closer to 0, but never actually reaching 0.

You can use this lerping function below, instead of MathUtils.lerp when you need your lerp to finish on the exact destination/to value. See the detail of the function, and you can see that it considers a minimum difference of .001. If the difference between the current value, and the destination/to value is less than .001, then it will return the desired destination/to value instead of the calculated value. The 0.001 is the Epsilon value that my function is using. You can replace it with a different minimum if you desire.

function lerp(from, to, speed) {
  const r = (1 - speed) * from + speed * to
  return Math.abs(from - to) < 0.001 ? to : r
}

You then replace any usage of MathUtils.lerp

ref.current.position.z = selected
  ? MathUtils.lerp(ref.current.position.z, 0, 0.025)
  : MathUtils.lerp(ref.current.position.z, -3, 0.025)

with the custom lerp.

ref.current.position.z = selected
  ? lerp(ref.current.position.z, 0, 0.025)
  : lerp(ref.current.position.z, -3, 0.025)

Working Example

<>
Drei Center github.com/pmndrs/drei
Array.prototype.map MDN
useThree docs.pmnd.rs
MathUtils.lerp threejs.org
Vector3.lerp threejs.org sbcode.net
Color.lerp threejs.org
Maath/easing damp github.com/pmndrs/maath

GitHub Branch

git clone https://github.com/Sean-Bradley/React-Three-Fiber-Boilerplate.git
cd React-Three-Fiber-Boilerplate
git checkout lerp
npm install
npm start

Comments