Morph targets

모프 타겟

Smoothly interpolating between two shapes with the same vertex count, so one mesh morphs into another.

Also known as: Blend shapesShape keysmorphTargetInfluences
···
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0c0c16);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 0, 4.6);

scene.add(new THREE.AmbientLight(0x556080, 1.2));
const dLight = new THREE.DirectionalLight(0xffffff, 1.1);
dLight.position.set(3, 4, 4);
scene.add(dLight);

const geo = new THREE.IcosahedronGeometry(1.3, 3);
const basePos = geo.attributes.position;
const spike = new Float32Array(basePos.count * 3);
const v = new THREE.Vector3();
for (let i = 0; i < basePos.count; i++) {
  v.fromBufferAttribute(basePos, i);
  const n = v.clone().normalize();
  const bump = 0.55 * (0.5 + 0.5 * Math.sin(i * 12.9898));
  v.addScaledVector(n, bump);
  spike[i * 3] = v.x; spike[i * 3 + 1] = v.y; spike[i * 3 + 2] = v.z;
}
geo.morphAttributes.position = [new THREE.Float32BufferAttribute(spike, 3)];

const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: 0x8f7bff, roughness: 0.4, flatShading: true }));
mesh.frustumCulled = false;
scene.add(mesh);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  mesh.morphTargetInfluences[0] = 0.5 + 0.5 * Math.sin(time * 1.2);
  mesh.rotation.y = time * 0.3;
  renderer.render(scene, camera);
});

Unlike skeletal animation, which moves bones, morph targets (blend shapes) blend vertex positions directly against other "target" position sets. They’re especially useful for deformations bones struggle with — facial expressions, a raised mouth corner, closed eyes.

The requirement: the base geometry and each morph target must have exactly the same vertex count and order. Put the target positions into geometry.morphAttributes.position as an array (before creating the Mesh!) and three.js automatically builds mesh.morphTargetInfluences. Each value (0–1) in that array says how much to blend toward that target.

The demo clones an icosahedron’s vertex array as-is, then pushes each vertex outward along its own normal by a different amount to build a "spiky sea urchin" target, and swings morphTargetInfluences[0] back and forth with a sine wave, so the shape drifts endlessly between a smooth sphere and the spiky form. Keeping flatShading on makes every facet’s changing angle read clearly during the morph.

When to use

Character facial expressions and lip-sync, or any UI/product animation that needs a smooth transition between two shapes.