Skip to main content

Animation Samples

Filament provides comprehensive support for various animation techniques. These samples demonstrate skeletal skinning, morph target animations, and dynamic vertex transformations.

Basic Transform Animation

The animation.cpp sample demonstrates simple vertex buffer animation with rotation transforms.

Key Features

  • Real-time vertex buffer updates
  • Transform animations using rotation matrices
  • Orthographic camera projection
  • Basic renderable setup

Code Example

FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
    void* verts = malloc(36);
    TRIANGLE_VERTICES[0].position.y = sin(now * 4);
    memcpy(verts, TRIANGLE_VERTICES, 36);
    vb->setBufferAt(*engine, 0, VertexBuffer::BufferDescriptor(verts, 36,
            (VertexBuffer::BufferDescriptor::Callback) free));

    auto& tcm = engine->getTransformManager();
    tcm.setTransform(tcm.getInstance(app.renderable),
            filament::math::mat4f::rotation(now, filament::math::float3{ 0, 0, 1 }));
});
Source: samples/animation.cpp:108-150 This sample shows how to animate geometry by updating vertex positions each frame and applying rotation transforms to create a spinning triangle with animated vertices.

Skeletal Skinning

The helloskinning.cpp sample demonstrates GPU-accelerated skeletal animation using bone weights and joint indices.

Key Features

  • Bone-based vertex deformation
  • Joint indices and weights attributes
  • Real-time bone transform updates
  • Multiple primitives with shared skinning data

Vertex Structure

struct VertexWithBones {
    float2 position;
    uint32_t color;
    filament::math::ushort4 joints;  // Bone indices
    filament::math::float4 weights;  // Bone weights
};
Source: samples/helloskinning.cpp:61-66

Setting Up Skinning

RenderableManager::Builder(2)
    .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
    .material(0, app.mat->getDefaultInstance())
    .material(1, app.mat->getDefaultInstance())
    .geometry(0, RenderableManager::PrimitiveType::TRIANGLE_STRIP, app.vb, app.ib, 0, 4)
    .geometry(1, RenderableManager::PrimitiveType::TRIANGLES, app.vb2, app.ib, 0, 3)
    .skinning(4, transforms)  // 4 bone transforms
    .enableSkinningBuffers(false)
    .build(*engine, app.renderable);
Source: samples/helloskinning.cpp:179-190

Animating Bones

float tr = (float)(sin(now));
mat4f trans[] = {
    filament::math::mat4f::translation(filament::math::float3{tr, 0, 0}),
    filament::math::mat4f::translation(filament::math::float3{-1, tr, 0}),
    filament::math::mat4f(1.f)
};
rm.setBones(rm.getInstance(app.renderable), trans, 3, 0);
Source: samples/helloskinning.cpp:221-225 The bone transforms are updated each frame to create smooth skeletal animation. Each vertex is influenced by its assigned bones based on the weight values.

Morph Target Animation

The hellomorphing.cpp sample showcases blend shape (morph target) animation for facial expressions and shape deformations.

Key Features

  • Multiple morph targets per primitive
  • Position and tangent morphing
  • Blend weight animation
  • MorphTargetBuffer management

Creating Morph Targets

app.mt1 = MorphTargetBuffer::Builder()
    .withPositions(true)
    .withTangents(true)
    .vertexCount(9 * 2)
    .count(3)  // 3 morph targets
    .build(*engine);

// Set position data for each morph target
app.mt1->setPositionsAt(*engine, 0, targets_pos1, 3, 0);
app.mt1->setPositionsAt(*engine, 1, targets_pos1+3, 3, 0);
app.mt1->setPositionsAt(*engine, 2, targets_pos1+6, 3, 0);
Source: samples/hellomorphing.cpp:171-183

Configuring Morphing

RenderableManager::Builder(2)
    .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
    .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3)
    .geometry(1, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3)
    .morphing(app.mt1)
    .morphing(0, 0, 0)  // Primitive 0, offset 0
    .morphing(0, 1, 9)  // Primitive 1, offset 9
    .build(*engine, app.renderable);
Source: samples/hellomorphing.cpp:194-206

Animating Blend Weights

float z = (float)(sin(now) / 2.f + 0.5f);
float weights[] = {1 - z, z/2, z/2};
rm.setMorphWeights(rm.getInstance(app.renderable), weights, 3, 0);
Source: samples/hellomorphing.cpp:236-239 Morph weights control the blend between the base mesh and each target shape. Weights are typically normalized to sum to 1.0 for natural blending.

Advanced Skinning Techniques

For production use, Filament also provides:
  • SkinningBuffer: Hardware-accelerated skinning with buffer objects (helloskinningbuffer.cpp)
  • UV Morphing: Morph texture coordinates for animated materials (hellouvmorphing.cpp)
  • Large Bone Counts: Support for complex rigs with many bones (helloskinningbuffer_morebones.cpp)

Best Practices

  1. Vertex Attributes: Always include BONE_INDICES and BONE_WEIGHTS for skinned meshes
  2. Bounding Boxes: Update bounding boxes when using dynamic animations to ensure proper culling
  3. Performance: Use SkinningBuffer for better performance with many animated characters
  4. Morph Targets: Limit the number of active morph targets to 4-8 for optimal performance
  5. Transform Updates: Batch bone transforms when possible to reduce API calls
  • gltf_viewer.cpp - Full glTF animation support including keyframe interpolation
  • skinningtest.cpp - Advanced skinning test cases
  • animation.cpp - Basic transform and vertex animations

Build docs developers (and LLMs) love