Command Palette

Search for a command to run...

Hectal
Course outline
Topic 9.4Intermediate ~21 min · step 4 of 6

Morph Targets and Skinning

In one line

Morph targets blend a mesh between stored shapes. Skinning bends a mesh with a skeleton of bones. You animate both, and see what goes wrong when a skinned mesh is cloned the wrong way.

0/6 · 0%

Think of it like this

A clay ball can be pressed into a cube or stretched into an egg. A morph target is a photo of the clay in each shape, and you slide between photos. A skinned mesh is a glove puppet: bend the finger inside and the cloth follows.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

morph target
A second (or third...) set of vertex positions for the same mesh, blended in with an influence from 0 to 1.
influence
How much of a morph target is applied. 0 none, 1 all.
bone
An Object3D in a tree used to bend a skinned mesh. Moving a bone moves the vertices that are weighted to it.
skeleton
The list of bones of one SkinnedMesh, plus their rest pose.
skinning weights
For each vertex, how strongly each of (up to four) bones pulls it.

Step by step

01Make two targets

The first example builds a sphere, then two extra position sets: the same points pushed out to a cube, and the points stretched tall. ball.updateMorphTargets() creates the influences, [0, 0].

02Animate the influences

A clip with two tracks raises cube to 1 and tall to 0.5 at 1 s and back to 0 at 2 s. At 1 s the log reads 1.00, 0.50.

03The late-target trap

A second mesh gets its target after it was made: its morphTargetInfluences is undefined, until updateMorphTargets() makes [0].

04Three foxes

The second example loads the Fox, then clones it two ways. It animates only the SkeletonUtils clone and checks both the bones and the picture.

Try it yourself

  1. 1

    Overshoot

    In the morph example change the cube track values [0, 1, 0] to [0, 2, 0]. At 1 s the log prints 2.00, 0.50: influences are not clamped, so the shape goes past the cube.

  2. 2

    Hide the original

    In the fox example add original.visible = false; after the loop that adds the foxes. A fox is still drawn on the left. It is the middle one, drawn at the original's place: its mesh uses the original's bones, and hiding the original does not stop those bones updating.

Code & diagrams

Morph targets: a ball that becomes a cube New tab

Watch the sphere flatten into a cube and stretch. Each target is a full copy of the 561 vertex positions.

idle

Console

Run the scene to see its console.

Expected console output

morph targets: 2 | influences: [ 0, 0 ]
at 0 s: influences 0.00, 0.00
at 1 s: influences 1.00, 0.50
at 2 s: influences 0.00, 0.00
targets added too late: influences = undefined
after updateMorphTargets(): [ 0 ]
Three foxes: the wrong clone and the right one New tab

Left: the original. Middle (x = 0): `clone()`. Right: `SkeletonUtils.clone()`, the only one that runs. The middle fox is not drawn where it was placed. Fox credits: Topic 9.3.

idle

Console

Run the scene to see its console.

Expected console output

clone() shares the original's bones:            true
SkeletonUtils.clone has bones of its own:        true
tail bone z, original / clone() / SkeletonUtils: -0.054 -0.054 0.258
is a fox drawn at...
  x = -1.2 original             yes
  x =    0 clone()              no (background)
  x =  1.2 SkeletonUtils.clone  yes

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Clone it the usual way

In the fox example change SkeletonUtils.clone(original) to original.clone().

terminal
$ Run
── what you'll see ──
SkeletonUtils.clone has bones of its own: false
tail bone z, original / clone() / SkeletonUtils: -0.054 -0.054 -0.054
is a fox drawn at...
x = -1.2 original yes
x = 0 clone() no (background)
x = 1.2 SkeletonUtils.clone no (background)
No error. Only one fox is on screen and it does not move.

Myth vs fact

Myth

Skinning moves the mesh's vertices on the CPU.

Fact

The CPU moves the bones. The GPU moves each vertex using its bone indexes and weights, in the vertex shader.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Bones are ordinary Object3D nodes, so you can attach things to them: bone.add(hat) makes the hat follow the fox's head.

  • ▸

    glTF allows 4 bone influences per vertex in JOINTS_0 and WEIGHTS_0. More than that needs extra sets, which three.js does not read.

  • ▸

    A hand-built target, as in the example, holds absolute positions. Targets that come from a glTF file hold offsets from the base shape, and the loader sets geometry.morphTargetsRelative for you.

Remember this

  1. 1

    The problem this solves: the tools so far move a whole object (Topic 9.1) or turn parts that are separate meshes (the wheels). A face smiling or a fox running changes the shape of one mesh. Moving every vertex by hand would be slow and hard to author.

  2. 2

    Morph targets store extra sets of vertex positions on the geometry (geometry.morphAttributes.position). The mesh holds one number per target, mesh.morphTargetInfluences. The drawn vertex is the base plus each influence times its offset: 0 is the base shape, 1 is the full target, and 0.5 is halfway. Values above 1 push past the target, as the example's tryIt shows. three.js stores them in a texture, so a mesh can have many.

  3. 3

    Naming. mesh.morphTargetDictionary maps names to indexes. glTF files carry them (a face has smile, blink). With names a track reads ball.morphTargetInfluences[cube]. Without the dictionary, use the index.

  4. 4

    The trap: morphTargetInfluences is created by the Mesh constructor only if the geometry already has morph attributes. Add them afterwards and the array is undefined: call mesh.updateMorphTargets().

  5. 5

    Skinning. A SkinnedMesh has a Skeleton: a list of Bone objects, which are ordinary Object3D nodes arranged in a tree (hip, spine, neck, tail). Each vertex stores up to four bone indexes and four weights (skinIndex, skinWeight), and is drawn at the weighted mix of those bones' movements. Turn a bone and the skin near it follows. The animation clips from Topic 9.3 move the bones, not the vertices.

  6. 6

    Cloning. object.clone() copies the object tree, but a cloned SkinnedMesh still points at the *original's* skeleton. The clone is drawn wherever the original's bones are, ignoring its own position, and animating the clone's copied bones changes nothing you can see. Use SkeletonUtils.clone(object) from three/addons/utils/SkeletonUtils.js: it copies the bones and binds the new mesh to them.

  7. 7

    Cost. Both techniques run on the GPU, but morph targets cost memory for each target and skinning costs a matrix per bone per mesh. The animation itself runs on the CPU in the mixer.

Explain it without notes

01

When would you use a morph target and when a skeleton?

02

Why does a skinned mesh made with object.clone() appear in the wrong place?

Practice

01

Make three independent foxes that play different clips.

02

A mesh has morph targets added after it was created. Make morphTargetInfluences available.

Done when you can

  • Say what a morph target and a bone each change

  • Animate morph target influences with a clip

  • Clone a skinned model with SkeletonUtils.clone

  • Recognise a mesh that uses its original's skeleton