Skinned meshes
Open the live demo · Read the source · View on GitHub
A skinned mesh is an ordinary mesh whose vertices are also told which joints
move them, and by how much. The engine keeps two things: a Skeleton, which
turns a chain of scene nodes into the matrices the device reads, and
SkinBlend, a CPU copy of the same arithmetic. This page builds a two-joint
banner and bends it with the Bend slider.
Step 1: Two joints, one skeleton #
A joint is a plain SceneNode. The tip is a child of the base, offset along
the banner's length, so rotating the tip bends everything past it rather than
the whole banner. Skeleton needs the inverse of each joint's bind-pose world
matrix, taken right after the hierarchy is built and before anything moves it.
_base = SceneNode(name: 'base')..setPosition(0.0, 0.0, -_depth / 2);
_tip = SceneNode(name: 'tip')..setPosition(0.0, 0.0, _depth);
_base.add(_tip);
final Matrix4 inverseBindBase = Matrix4.copy(_base.worldMatrix)..invert();
final Matrix4 inverseBindTip = Matrix4.copy(_tip.worldMatrix)..invert();
_skeleton = Skeleton(
joints: <SceneNode>[_base, _tip],
inverseBindMatrices: <Matrix4>[inverseBindBase, inverseBindTip],
name: 'flagpole',
);
Step 2: Paint the weights #
A mesh built by PlaneShape carries no joints or weights, so the first step is
converting it to the layout that has them. Every vertex then gets an index
pair, [0, 1], and a weight pair that blends smoothly from all base near one
end to all tip near the other: t is how far along the banner the vertex sits.
_cpuMesh = const PlaneShape(
width: 0.7,
depth: _depth,
depthSegments: 8,
).build().convertedTo(VertexLayout.skinned);
_paintWeights(_cpuMesh);
Step 3: Attach the skeleton to the mesh #
A MeshNode draws with its skeleton set, and nothing else marks it as
skinned. The renderer reads the skeleton's matrices once a frame and uploads
them; the CPU-side _cpuMesh this page keeps is only for SkinBlend to check
its own claim afterwards, not for drawing.
final Material fabric = Material(
name: 'banner',
baseColor: Vector4(0.82, 0.24, 0.2, 1.0),
roughness: 0.7,
doubleSided: true,
);
final MeshNode banner = MeshNode(
DeviceMesh.upload(context.device, _cpuMesh),
fabric,
name: 'banner',
)..skeleton = _skeleton;
Step 4: Move a joint #
Bending the banner is one line: turn the tip joint. The base never moves, so the near half of the banner stays put and the far half swings with the tip.
_tip.setRotation(
Quaternion.axisAngle(Vector3(1.0, 0.0, 0.0), bend * _maxAngle),
);
Drag Bend and watch the far half of the banner swing while the near half stays
planted. SkinBlend recomputes the same bend on the CPU every time the page's
own check runs, and the two are held to agreeing with each other.