Draws large crowds of animated, skinned models. A
BakedAnimations
bakes every animation of a skinned Model into joint matrices on the GPU once. Each
BakedAnimationInstance
is a Node3D with its own clip, time, and speed.
Baking
val fox =
resourcesVfs["models/Fox.glb"].readGltfModel(
GltfLoaderUnlitConfig(GltfModelConfig(skinned = true))
)
val baked = BakedAnimations(device, fox, fps = 30f)
baked.clips // every baked clip
baked.clip("Run") // a clip by name, or null
Release it when done. The source model must stay loaded since its meshes and materials are shared.
onRelease {
baked.release()
fox.release()
}
Instances
Animations advance when update(dt) is called, e.g. on a parent node.
val horde = Node3D()
repeat(1000) {
horde.addChild(
baked.createInstance().apply {
play("Run") // or play(clip, loop = true, restart = true)
speed = 1.2f
tint = Color.RED
position(it % 40 * 2.5f, 0f, it / 40 * 2.5f)
}
)
}
onUpdate { dt ->
horde.update(dt)
// ...
modelBatch.render(horde, camera, environment)
}
Use stop(), time, loop, and isFinished to control playback. Call remove() to stop drawing an instance for good.
Performance
All instances of one BakedAnimations are drawn in one instanced draw call per mesh primitive, are frustum culled, and have their frames interpolated on the GPU. 3000 animated foxes run at the 120 FPS vsync cap on the JVM.
There is no per-instance blending between clips and no procedural bones. Use a regular Model with a live skin for hero characters.
Skinned Model Copies
Copies of a skinned Model draw wherever they are placed. To pose an animation at a time without advancing it, use Animation.sample(time).
fox.animations[0].sample(0.5f)