Smooth Rotations in Three.js Using Quaternion Slerp
In 3D game development and animation, smoothly rotating objects from
one orientation to another is a common requirement. This article guides
you through using the Quaternion.slerp (Spherical Linear
Interpolation) method in Three.js to achieve seamless, natural-looking
rotational transitions. You will learn why quaternions are preferred
over Euler angles, how the slerp mathematical concept works in Three.js,
and how to implement it using two practical coding approaches.
Why Use Quaternions and Slerp?
When rotating 3D objects, developers often use Euler angles (X, Y, and Z rotations). However, Euler angles suffer from gimbal lock—a state where two rotational axes align, causing a loss of one degree of freedom and resulting in erratic, jerky movement.
Quaternions represent rotations using four-dimensional numbers (\(x, y, z, w\)). They completely avoid gimbal lock. Slerp (Spherical Linear Interpolation) is a method that interpolates between two quaternions along the shortest path on a 4D sphere at a constant speed, resulting in perfectly smooth rotation.
Method 1: Absolute Interpolation (From Start to End)
Use this method when you want to transition an object from a specific starting rotation to a specific target rotation over a defined period of time.
Three.js provides the slerpQuaternions(qa, qb, t) method
for this purpose, where t is a normalized value between
0 (start) and 1 (end).
Step-by-Step Implementation
- Define the Quaternions: Create a start quaternion, a target quaternion, and a third quaternion to hold the interpolated result.
- Track Time/Progress: Increment an interpolation factor (\(t\)) in your animation loop.
- Apply and Assign: Use
slerpQuaternionsto calculate the rotation at step \(t\) and apply it to your 3D mesh.
import * as THREE from 'three';
// 1. Create the mesh
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 2. Define rotations
const startRotation = new THREE.Quaternion(); // Default identity (no rotation)
const targetRotation = new THREE.Quaternion().setFromAxisAngle(
new THREE.Vector3(0, 1, 0), // Rotate around Y-axis
Math.PI / 2 // 90 degrees in radians
);
let t = 0; // Interpolation progress (0 to 1)
const duration = 2; // Duration of transition in seconds
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (t < 1) {
t += delta / duration; // Advance progress based on elapsed time
t = Math.min(t, 1); // Clamp t to a maximum of 1
// Interpolate and apply directly to the mesh
mesh.quaternion.slerpQuaternions(startRotation, targetRotation, t);
}
renderer.render(scene, camera);
}
animate();Method 2: Dynamic Tracking (Constant Easing)
Use this method when your target rotation is constantly changing (for example, a camera tracking a moving player, or a character turning to face the mouse cursor).
Instead of tracking absolute time, you call the .slerp()
method on the object’s current quaternion directly in the loop, using a
constant fractional step (damping factor).
Step-by-Step Implementation
- Calculate Target: Determine the desired target quaternion in each frame.
- Slerp Incrementally: Call
mesh.quaternion.slerp(target, alpha)wherealphais a small percentage (e.g.,0.05to0.1). This acts as a dampening factor, causing the rotation to smoothly “catch up” to the target.
const targetQuaternion = new THREE.Quaternion();
const easingFactor = 0.05; // Adjust between 0 and 1 for speed (lower is smoother)
// Event listener to change target rotation on click
window.addEventListener('click', () => {
// Generate a random rotation target
targetQuaternion.setFromEuler(new THREE.Euler(
Math.random() * Math.PI,
Math.random() * Math.PI,
0
));
});
function animate() {
requestAnimationFrame(animate);
// Smoothly rotate the mesh towards the target rotation
mesh.quaternion.slerp(targetQuaternion, easingFactor);
renderer.render(scene, camera);
}
animate();Key Differences Summary
| Use Case | Method to Use | Benefits |
|---|---|---|
| Fixed Timeline Transitions (e.g., opening a door, UI transitions) | slerpQuaternions(start, end, t) |
Linear, predictable timing; stops processing once \(t = 1\). |
| Real-time Tracking (e.g., character aiming, camera tracking) | current.slerp(target, step) |
Easy to implement; naturally slows down as it nears the target (organic deceleration). |