# How to use easing functions to improve lerp/slerp

**URL:** <https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766>\
**Category:** Questions\
**Tags:** animation-loop, animation, math\
**Created:** [July 26, 2022, 6:29pm UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766 "2022-07-26T18:29:23Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![r0bert0](https://avatars.discourse-cdn.com/v4/letter/r/8e7dd6/32.png) [@r0bert0](https://discourse.threejs.org/u/r0bert0)\
**Post date:** [July 26, 2022, 6:29pm UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/1 "2022-07-26T18:29:23Z")

</div>

Hey,

I am animating a gltf skeleton dynamically and interpolating between position, rotation, scale for each bone.

Using lerp and slerp works, but it still looks very sudden and unnatural, so I have been trying to use easing functions to improve the quality of interpolation a bit.

For example I am using .lerpVectors(a, b, t) like so. The value t is calculated using my render loop: I start at 0 and do t += 0.01 until t \>= 1.

Now to make use of easing functions I was trying to pass t through [https://threejs.org/docs/#api/en/math/MathUtils.smootherstep](https://smootherstep) like so Math.Utils(t, 0, 1), but I am not seeing any difference in the speed of interpolation. I am also unsure whether updating t with t += 0.01 in my render loop is correct. I have read that one should scale this by deltaTime, but then my values go from 0 to 1 too quickly.

Thanks for any advice on how to lerp in a smoother way.

---

<div class="post-metadata">

**Author:** ![tfoller](https://avatars.discourse-cdn.com/v4/letter/t/3e96dc/32.png) [@tfoller](https://discourse.threejs.org/u/tfoller)\
**Post date:** [July 26, 2022, 11:48pm UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/2 "2022-07-26T23:48:36Z")

</div>

The next simplest after lerp is probably a quadratic filter with ease:

```javascript
f = -ease * t * t + (1 + ease) * t); // ease [-1, 1]

```

it creates motions like this:

> **[Edit fiddle - JSFiddle - Code Playground](https://jsfiddle.net/tfoller/hqzpsL0w/66/)**
>
> Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.

---

<div class="post-metadata">

**Author:** ![r0bert0](https://avatars.discourse-cdn.com/v4/letter/r/8e7dd6/32.png) [@r0bert0](https://discourse.threejs.org/u/r0bert0)\
**Post date:** [July 27, 2022, 4:07am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/3 "2022-07-27T04:07:53Z")

</div>

Could you explain a bit more the reasoning behind this quadratic filter? What is ‘ease’ in this context?

---

<div class="post-metadata">

**Author:** ![tfoller](https://avatars.discourse-cdn.com/v4/letter/t/3e96dc/32.png) [@tfoller](https://discourse.threejs.org/u/tfoller)\
**Post date:** [July 27, 2022, 4:25am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/4 "2022-07-27T04:25:20Z")

</div>

Most of these functions just map input to output using some non-linear function, **ease** is just a parameter of the filter function that allows you to create different curves, in this case something that either slows down at the end or at the beginning. With **ease = 0** you get a straight line, so it’s lerp.

**ease \> 0** boosts the input, making something to change faster than lerp, **ease \< 0** suppresses it

![filter](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/5/f/5fd770f65fa8da2a43ec88dc3623c2641ae035a3.png)

---

<div class="post-metadata">

**Author:** ![r0bert0](https://avatars.discourse-cdn.com/v4/letter/r/8e7dd6/32.png) [@r0bert0](https://discourse.threejs.org/u/r0bert0)\
**Post date:** [July 27, 2022, 4:55am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/5 "2022-07-27T04:55:33Z")

</div>

Ok, that clears it up thank you. So this is basically 3 different easing functions in one dependent on the parameter ‘ease’? Wouldn’t passing the ‘alpha’ parameter of [lerpVectors](https://threejs.org/docs/#api/en/math/Vector3.lerpVectors) through a manually defined easing function e.g. smoothstep, before calling .lerpVectors(a1, a2, smoothstep(t)) achieve similar results?

---

<div class="post-metadata">

**Author:** ![tfoller](https://avatars.discourse-cdn.com/v4/letter/t/3e96dc/32.png) [@tfoller](https://discourse.threejs.org/u/tfoller)\
**Post date:** [July 27, 2022, 5:16am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/6 "2022-07-27T05:16:54Z")

</div>

> [@r0bert0](#):
>
> So this is basically 3 different easing functions in one dependent on the parameter ‘ease’?

Yes.

> [@r0bert0](#):
>
> Wouldn’t passing the ‘alpha’ parameter of [lerpVectors](https://threejs.org/docs/#api/en/math/Vector3.lerpVectors) through a manually defined easing function…

Lerp doesn’t add anything to the result, it maps values one to one, if you already use quadratic filter or smoothstep, then just use that result directly but technically yes.

---

<div class="post-metadata">

**Author:** ![tfoller](https://avatars.discourse-cdn.com/v4/letter/t/3e96dc/32.png) [@tfoller](https://discourse.threejs.org/u/tfoller)\
**Post date:** [July 27, 2022, 6:10am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/7 "2022-07-27T06:10:34Z")

</div>

Added another sphere that uses smoothstep as the input for lerp:

> **[Edit fiddle - JSFiddle - Code Playground](https://jsfiddle.net/tfoller/35v01h2o/28/)**
>
> Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.

---

<div class="post-metadata">

**Author:** ![r0bert0](https://avatars.discourse-cdn.com/v4/letter/r/8e7dd6/32.png) [@r0bert0](https://discourse.threejs.org/u/r0bert0)\
**Post date:** [July 27, 2022, 7:23am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/8 "2022-07-27T07:23:08Z")

</div>

Cool thanks, I have a few approaches in mind now.

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [August 23, 2022, 6:52pm UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/9 "2022-08-23T18:52:14Z")

</div>

The JSFiddles do not work anymore. ☹  
I wanted to add them to the [collection](https://discourse.threejs.org/t/collection-of-examples-from-discourse-threejs-org/4315).

---

<div class="post-metadata">

**Author:** ![tfoller](https://avatars.discourse-cdn.com/v4/letter/t/3e96dc/32.png) [@tfoller](https://discourse.threejs.org/u/tfoller)\
**Post date:** [August 23, 2022, 8:32pm UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/10 "2022-08-23T20:32:58Z")

</div>

@hofk Not sure what happened but I found this one:

> **[Edit fiddle - JSFiddle - Code Playground](https://jsfiddle.net/tfoller/b78hmzqk)**
>
> Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [August 24, 2022, 9:35am UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/11 "2022-08-24T09:35:43Z")

</div>

Ok. Added this exanple. 🙂

---

<div class="post-metadata">

**Author:** ![Gangula](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/gangula/32/17731_2.png) [@Gangula](https://discourse.threejs.org/u/Gangula)\
**Post date:** [January 10, 2025, 3:35pm UTC](https://discourse.threejs.org/t/how-to-use-easing-functions-to-improve-lerp-slerp/40766/12 "2025-01-10T15:35:31Z")

</div>

adding the code the above fiddle here for future:

```js
import * as CONTROLS from 'https://alikim.com/_v1_jsm/controls.js'

import * as THREE from 'https://unpkg.com/three/build/three.module.js'

import * as MathUtils from 'https://unpkg.com/three/src/math/MathUtils.js'

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const hlp = new THREE.AxesHelper(3);

const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 16, 16),
new THREE.MeshBasicMaterial({color:0x800080}),
);

const sphere1 = sphere.clone();
const sphere2 = sphere.clone();
const sphere3 = sphere.clone();

scene.background = new THREE.Color(0x002200);
scene.add(hlp).add(sphere).add(sphere1).add(sphere2).add(sphere3);

camera.position.set(5, 3, 5);
camera.lookAt(0, 0, 0);

const duration = 3000;
const idur = 1 / duration;

let t0 = performance.now();

const tick = (ease, now) => {
  const dif = now - t0;
  // quadratic filter
  const s = dif ? Math.min(1, dif * idur) : 0;
  const f = dif ? Math.min(1, -ease * s * s + (1 + ease) * s) : 0;
  return f;
};

const v1 = new THREE.Vector3(0, 0, 3);
const v2 = new THREE.Vector3(0, 1, 3);
const smoothstep = now => { 
	const dif = now - t0;
  const t = dif * idur;
	return new THREE.Vector3().lerpVectors(v1, v2, MathUtils.smoothstep(t, 0, 1));
};

const render = (f0, f1, f2, v) => {
  sphere.position.set(0, 2 * f0, 0);
  sphere1.position.set(0, 2 * f1, 1);
  sphere2.position.set(0, 2 * f2, 2);
  sphere3.position.set(v.x, 2 * v.y, v.z);
  renderer.render(scene, camera);
};

const state = {
  play: true
};

const maybeRender = () => {
  if (!state.play) render()
};

const framerate = 100;
const fpsint = 1000 / framerate;
let elapsed, now, then;

let tid = 0;
const animate = newtime => {

  if (!state.play) return;
  requestAnimationFrame(animate);

  if (!newtime) then = newtime = window.performance.now();

  now = newtime;
  elapsed = now - then;

  if (!fpsint || elapsed > fpsint) {

    then = now - (elapsed % fpsint);

    const [fy0, fy1, fy2, fy3] = [
    tick(0, now), 
    tick(-1, now), 
    tick(1, now), 
    smoothstep(now)
    ];
    
    if(fy0 == 1 && fy1 == 1 && fy2 == 1 && fy3.equals(v2) && !tid) { 
      tid = setTimeout(()=>{t0 = now; tid = 0; }, 1000);
    }

    render(fy0, fy1, fy2, fy3);
  }
};

CONTROLS.create({
  cont: renderer.domElement,
  cam: camera,
  type: 'Orbital',
  overlay: true,
  lookAt: [0, 0, 0],
  callback: maybeRender
});

animate();

```
