Skip to content

Spring bones

Hair, tails, scarves and cords move after the body does: they lag it, swing, and settle. Spring bones add that to a skeleton without an animator authoring it. A SpringBone on the root of a chain makes every entity below it a spring: the animation still says where each bone would be, and the chain trails that pose by inertia, falls with gravity, and is pulled back toward it. The root itself is not simulated; it is what the animation or the body drives.

It is core, not an optional module, and it runs in C++ in the engine, so it costs the same on every target, including ones with no JIT.

Put a SpringBone on the top bone of a hair strand (its parent bone is the body; its children are the strand). Everything below it, down every branch, is simulated.

Field What it does
enabled Off leaves the chain exactly as the animation poses it.
stiffness How hard each bone is pulled back toward its animated pose, 0 to 1 a frame at 60 Hz. 0 hangs free. Default 0.1.
drag How much of its speed a bone loses each frame, 0 to 1. Higher settles sooner; the default (0.5) is damped enough not to ring.
gravity World units a second squared (world y is up). The default is {0, -980, 0}: a metre a hundred units.
wind A steady wind on the chain, world units a second squared. Zero is still air.
windGust How far gusts swing the wind’s strength, 0 to 1. 0 is steady; 1 takes it from calm to twice its strength. Each bone meets a gust a moment after the one above, so the strand ripples.
windFrequency Gusts a second (default 0.5).
endLength A leaf bone has no child to aim at; this gives it a tip that long. 0 leaves leaf bones unsimulated.
radius The thickness of the strand, kept clear of the colliders.
weight How much of the simulation is used, 0 to 1 (default 1). 0 is the animation alone.
colliders Entities with a SpringBoneCollider the chain is kept out of.

A SpringBoneCollider is a sphere (the default, radius 10), or a capsule along its entity’s own Y axis (height 20, the distance between its end centres), placed by the entity’s transform (and its offset). Put them on the head and shoulders bones so hair does not pass through the body.

Set a model’s Spring Bones import setting to From File Data and Names and the importer builds the chains from what the file already says, so nobody sets up forty strands by hand. It looks, in this order, at:

  1. VRMC_springBone, the VRM 1.0 extension: springs, colliders and collider groups with their numbers. A spring becomes a SpringBone on its first joint (that bone is held, the ones below it move), and its stiffness (per second in VRM) and gravity power are converted to the engine’s per-frame and acceleration units. Springs that name fewer than two joints, or that would move bones they do not name, are reported in the import warnings.
  2. A node’s own properties, extras.estella.springBone (stiffness, drag, gravity, radius, endLength) on a chain root and extras.estella.springCollider (shape, radius, height, offset) on a collider node, which is how a Blender custom property reaches the engine with exact sizes. These win over VRM for the node they are on.
  3. Names, for whatever those leave unsaid:
Name Becomes
Hair_<region>_<chain>_00 (with bones below it) a SpringBone on that bone; the rest of the strand is whatever hangs below it
SBC_Sphere_<name>_r<cm> a sphere SpringBoneCollider, radius in centimetres
SBC_Capsule_<name>_r<cm>_h<cm> a capsule, radius and the distance between its two end centres, along the node’s local +Y

The region (Ponytail, Temple_L, Fringe, Crest, Crown) picks a starting stiffness and drag: a fringe is held close, a ponytail swings. Every chain is kept out of every collider the model has. Sizes are in the model’s metres and follow the import scale, and gravity is Earth’s in those metres. A capsule with no length, or a chain root with nothing below it, is skipped. A name is a convention, so look at what the import made in Details and adjust it there.

Details groups the chain’s fields into Motion (stiffness, drag, gravity), Chain (end length, radius, weight) and Collision (colliders), and says on its first line how many bones the chain has below its entity and how many end tips, or, in the warning colour, that it has no child bones and so simulates nothing.

Select a chain’s root and the viewport draws it, and the colliders it keeps out of, in the Spring bones entry of the Gizmo menu: the bones as the animation poses them as amber lines, a ring at each joint the width of the strand’s radius (a dot where it is 0), a tip where endLength gives one, and each collider as a wireframe sphere or capsule in the collider green. Select a SpringBoneCollider and its own shape is drawn, with six square handles to size it: on a sphere all six set the radius, on a capsule the four round its middle set the radius and the two on its ends set the height (the distance between the centres of its end spheres). The handles on the far side of the shape are faded. A drag is one undo step, shows the value beside the pointer, and the strands follow it live while Preview FX is on. Nothing is drawn for what is not selected, so a character’s hair does not fill the viewport.

View → Preview FX (on in a fresh editor, the same switch particles and trails use) runs the chain in the edit viewport: drag the head, or change a field, and the hair swings. With it on, the simulated chain is drawn solid over the animated pose, dashed, so the difference you are tuning is the difference you see. Turning it off ends the simulation and gives the bones back as the scene has them. The simulation is the editor’s own: it never writes into the scene file.

The animation runs first and writes the bones’ local rotations. Then, for each chain:

  1. The pose the animation gave is worked out down the chain from the root, using the animation’s own rotations. (A bone this system turned last frame and that nothing animated this frame is read as the animation had it, not as it was left, so a chain never springs toward its own last result.)
  2. The chain is stepped toward that pose: each bone keeps its velocity less drag, falls with gravity, is pulled toward its pose, is put back at its length from its parent (parents first, so a bone cannot stretch), and is pushed out of the colliders. The time is split into steps of at most a 60 Hz frame.
  3. Each bone is turned to aim at where its child has got to. Only rotations are written; a bone’s position is never changed, so it cannot leave its parent, and skinning needs nothing else.

A jump of the root by more than a few times the chain’s length, or a stall of half a second, puts the chain back at its pose instead of whipping it. Switching the component off, or removing it, gives the bones back as the animation had them. SpringBoneRes (app.getResource(SpringBoneRes)) has reset(root) for a teleport you want to mark yourself.

The solver is a few arithmetic operations per bone. Measured in the editor’s play realm, in the wasm engine: three hundred chains of six bones (2100 nodes) cost 0.28 ms an update. The test is springbone in the editor checks; test_springbone is the C++ unit test of the solver and the system.

  • A bone with several children aims at its first child; the others follow by being children, and aim at their own.
  • Scale is taken from the bones’ composed scale; non-uniform scale down a chain is not modelled.
  • Collision is bone against sphere or capsule: no collision between strands, no self-collision, and no collision with the world’s physics shapes.
  • The wind is a field on each chain, not a force in the scene: there is no scene-wide wind source to set once for every chain. A collider’s offset is typed in Details, or set by moving its entity; the handles size it, they do not place it. Chains are made from names; the importer does not look at the shape of the mesh, so a strand with no Hair_ bone gets no motion.