Home / Features / Models and animation
glTF 2.0 is the only model and animation format. Not the preferred one — the only one. That decision removes an abstraction layer, an import pipeline and a class of bugs where two formats disagree about bind poses.
Model places a GLB in the world using the engine's uniform
placement convention: position is the world position of the anchor, and width, height and
depth are the target bounds.
var robot = new Model
{
Name = "Assets/3DGodotRobot.glb",
PosX = -2f, PosY = 1f, PosZ = 6f,
Width = 1f, Height = 1f, Depth = 0.5f,
Rotation = MathF.PI / 2f
};
AddControl(robot);
Width, Height and
Depth are target sizes in world units, not multipliers.
Setting Width = 1f makes the model one unit wide whatever
the file said. And Rotation is in radians around
Y — writing 90 gives you 90 radians, which
normalises to about 117 degrees and looks almost right, which is the worst kind of
bug.
Placement, anchors and the camera covers the anchor
maths, including AnchorWorldOffset for the case where you want
to pin the model's own origin rather than the centre of its bounds.
Skinning, morph targets and named clips all come from the file. Clips are addressed by name, and the names are the ones the artist used.
// After Load has completed:
foreach (var name in robot.GetAnimationNames())
app.AddLog(LogType.Info, name);
robot.PlayAnimation("Walk");
// Or cycle, which is what a debug key usually wants:
robot.SwitchToNextAnimation();
// Advance the clock yourself; Time is the animation clock.
robot.Update(time);
| Member | Notes |
|---|---|
GetAnimationNames() | Clip names, available once the asset has loaded. |
PlayAnimation(string) | Switches clip. Returns the name that was actually selected. |
GetCurrentAnimationName() | What is playing now. |
SwitchToNextAnimation() | Advances through the clip list, wrapping. |
Time | The animation clock, in seconds. |
SetModel(name, forceReload) | Swaps the underlying GLB, keeping placement. |
Switching a clip is a cut, not a crossfade. There is no state machine, no blend tree and no additive layering. If a character needs to walk and aim at the same time, that is work the engine does not do for you today.
An InstancedModel is declared once and bound to a list of
instance transforms. Skinning and morph targets are per instance, so twenty characters can
each play a different clip from one draw. This is the pattern the reference application uses
for robots, birds, rocks and the beach.
robotField = new InstancedModel { ModelName = "Assets/3DGodotRobot.glb" };
AddControl(robotField);
for (var i = 0; i < 10; i++)
{
var person = new Person
{
PosX = -2f, PosY = 1f, PosZ = 6f + i * 3f,
Width = 1f, Height = 1f, Depth = 0.5f,
Animation = animations[i]
};
robotField.Instances.Add(person);
}
Person derives from
MeshInstanceTransform, so the object in your list
is the instance. Editing its fields takes effect on the next frame with no copy
step, and inserting or removing in the middle of the list preserves order.
Field on MeshInstanceTransform | Notes |
|---|---|
PosX/Y/Z, Width, Height, Depth, Rotation | Same semantics as on a single model, relative to the shared template bounds. |
AnimationClip | Clip index for this copy. AnimationClipCount and AnimationNames on the parent tell you the range. |
AnimationSpeed | Playback rate. Varying it slightly per copy is what stops a crowd looking mechanical. |
AnimationTimeOffset | Phase offset, so copies do not step in unison. |
Enable | Skips the copy without removing it from the list. |
Selected, Highlight | Per-instance picking state, so an individual copy can be outlined. |
ID, Name | Identity, useful when the list is rebuilt from data. |
Not everything comes from a file. Mesh3D takes a list of
Surface objects — vertices, indices and a material
— and behaves like any other control from there. The sea, the beach tiles and the cube
fields in the reference application are built this way.
| Type | Use |
|---|---|
Mesh3D | Procedural geometry with a quaternion Rotation, a ColorTint, and ExcludeFromAo for surfaces that should not receive occlusion. |
InstancedMesh3D | The same, instanced. No per-instance animation — procedural geometry has no clips. |
GLTFAnimationPlayer | Clip evaluation, if you want to drive a skeleton yourself. |
GLTFTools | Pulling individual meshes out of one GLB at runtime. |
PickMesh | The CPU-side geometry used for surface-accurate picking. |
Frustum culling and light-space shadow culling both run on the CPU against bounding volumes.
The subtlety is that a skinned mesh can move outside its own bind-pose bounds, so the bounds
used for culling are grown by AnimatedBoundsScale, 1.5 by
default.
| Member | Which bounds |
|---|---|
LocalBounds | Inflated. This is what culling tests, and it is deliberately conservative. |
LocalBoundsRaw | Not inflated. This is what picking tests, because a pick should not hit empty air. |
LocalSize | Extent of the raw bounds, which is what you divide by to compute a target scale. |
GetWorldBounds() | Bounds after the world matrix, for your own spatial queries. |
CullingEnabled | Per-control opt-out. Useful for anything that legitimately extends past its bounds. |
An animation that swings a limb further than 50 per cent past the bind pose will
pop at the frustum edge. Raise AnimatedBoundsScale or clear
CullingEnabled on that control; both are cheap compared with
the alternative of computing exact skinned bounds every frame.
A glTF file can carry KHR_lights_punctual lights, and
Model surfaces them as
ImportedPunctualLights, appending them into the frame's
lighting through AppendWorldLights.
Two things to remember: the frame budget is eight lights total, so an asset carrying twelve
of them will crowd out your own; and glTF intensities are photometric, which is why
Model.LightIntensityScale and
RenderQuality.KhrLightIntensityScale exist. See
Lighting and shadows.