SkinningBuffer holds bone transformation data for skeletal animation (vertex skinning). It is a structured UBO wrapper that stores bone matrices efficiently.
Overview
Skinning (also called skeletal animation) deforms mesh vertices based on bone transformations. Each vertex is influenced by one or more bones with associated weights. The SkinningBuffer stores the bone transformation matrices that are used during vertex processing.
Creating a SkinningBuffer
Use the Builder pattern to create a SkinningBuffer:
#include <filament/Engine.h>
#include <filament/SkinningBuffer.h>
using namespace filament;
Engine* engine = Engine::create();
SkinningBuffer* sb = SkinningBuffer::Builder()
.boneCount(256)
.initialize(true)
.build(*engine);
Builder Methods
boneCount
Sets the number of bones the buffer can hold.
Builder& boneCount(uint32_t boneCount) noexcept;
Due to GLSL limitations, the SkinningBuffer size is automatically rounded up to a multiple of 256 bones. This can cause memory overhead, which can be mitigated by sharing a single SkinningBuffer across multiple renderables.
initialize
Initializes the buffer with identity bone transforms.
Builder& initialize(bool initialize = true) noexcept;
When true, all bones are set to identity matrices. This is useful to ensure a known initial state.
name
Associates a debug name with the buffer.
Builder& name(utils::StaticString const& name) noexcept;
The name appears in error messages for debugging purposes.
build
Creates the SkinningBuffer.
SkinningBuffer* build(Engine& engine);
Updating Bone Data
SkinningBuffer provides two overloads of setBones() for updating bone transformations:
setBones (Bone structures)
Updates bone transforms using RenderableManager::Bone structures.
void setBones(Engine& engine,
RenderableManager::Bone const* transforms,
size_t count,
size_t offset = 0);
Parameters:
engine - Reference to the Filament engine
transforms - Array of Bone structures (quaternion + translation)
count - Number of bones to update
offset - Starting bone index in the buffer (not byte offset)
RenderableManager::Bone structure:
struct Bone {
math::quatf unitQuaternion = {1.f, 0.f, 0.f, 0.f};
math::float3 translation = {0.f, 0.f, 0.f};
float reserved = 0;
};
setBones (Matrices)
Updates bone transforms using 4x4 matrices.
void setBones(Engine& engine,
math::mat4f const* transforms,
size_t count,
size_t offset = 0);
Parameters:
engine - Reference to the Filament engine
transforms - Array of 4x4 transformation matrices
count - Number of bones to update
offset - Starting bone index in the buffer
Example from helloskinningbuffer sample:
using namespace filament::math;
// Define bone transforms
mat4f transforms[] = {
mat4f(1.f),
mat4f::translation(float3(1, 0, 0)),
mat4f::translation(float3(1, 1, 0)),
mat4f::translation(float3(0, 1, 0)),
mat4f::translation(float3(-1, 1, 0)),
mat4f::translation(float3(-1, 0, 0)),
mat4f::translation(float3(-1, -1, 0)),
mat4f::translation(float3(0, -1, 0)),
mat4f::translation(float3(1, -1, 0))
};
// Create SkinningBuffer
SkinningBuffer* sb = SkinningBuffer::Builder()
.boneCount(9)
.initialize()
.build(*engine);
// Upload bone data
sb->setBones(*engine, transforms, 9, 0);
Querying Buffer Size
getBoneCount
Returns the number of bones the buffer can hold.
size_t getBoneCount() const noexcept;
This returns the actual allocated size (rounded to multiple of 256), which may be larger than the requested size.
Attaching to Renderables
Attach a SkinningBuffer to a renderable using RenderableManager::Builder:
using namespace filament;
// Create vertex buffer with bone data
VertexBuffer* vb = VertexBuffer::Builder()
.vertexCount(vertexCount)
.bufferCount(3)
.attribute(VertexAttribute::POSITION, 0,
VertexBuffer::AttributeType::FLOAT3, 0, 12)
.attribute(VertexAttribute::BONE_INDICES, 1,
VertexBuffer::AttributeType::USHORT4, 0, 8)
.attribute(VertexAttribute::BONE_WEIGHTS, 2,
VertexBuffer::AttributeType::FLOAT4, 0, 16)
.build(*engine);
// Create SkinningBuffer
SkinningBuffer* sb = SkinningBuffer::Builder()
.boneCount(256)
.initialize()
.build(*engine);
// Create renderable with skinning
RenderableManager::Builder(1)
.boundingBox({{-1, -1, -1}, {1, 1, 1}})
.material(0, materialInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
vb, ib, 0, indexCount)
.enableSkinningBuffers(true) // Enable skinning buffer mode
.skinning(sb, 256, 0) // Attach buffer, count, offset
.build(*engine, renderable);
Key Points:
- enableSkinningBuffers(true) - Enables the SkinningBuffer mode (instead of inline bone data)
- skinning(sb, count, offset) - Attaches the buffer and specifies which bones to use
sb - The SkinningBuffer
count - Number of bones this renderable uses
offset - Starting bone index in the buffer
Sharing Buffers Between Renderables
A single SkinningBuffer can be shared by multiple renderables to reduce memory overhead:
// Create a shared buffer with space for multiple skeletons
SkinningBuffer* sharedBuffer = SkinningBuffer::Builder()
.boneCount(512) // Space for 2 skeletons of 256 bones each
.initialize()
.build(*engine);
// First renderable uses bones 0-255
RenderableManager::Builder(1)
// ... setup geometry ...
.enableSkinningBuffers(true)
.skinning(sharedBuffer, 256, 0) // offset = 0
.build(*engine, renderable1);
// Second renderable uses bones 256-511
RenderableManager::Builder(1)
// ... setup geometry ...
.enableSkinningBuffers(true)
.skinning(sharedBuffer, 256, 256) // offset = 256
.build(*engine, renderable2);
Animation Example
Updating bone transforms each frame for animation:
// In animation loop
FilamentApp::get().animate([&](Engine* engine, View* view, double now) {
auto& rm = engine->getRenderableManager();
// Calculate animated bone transforms
float t = static_cast<float>(sin(now));
mat4f boneTransforms[] = {
mat4f::translation(float3(t, 0, 0)),
mat4f::translation(float3(-1, t, 0)),
mat4f(1.f)
};
// Update first bone
skinningBuffer->setBones(*engine, &boneTransforms[0], 1, 0);
// Update bones 1-2
skinningBuffer->setBones(*engine, &boneTransforms[1], 2, 1);
});
Vertex Buffer Setup
Vertices must include bone indices and weights:
struct VertexWithBones {
math::float3 position;
math::ushort4 joints; // Bone indices (4 bones per vertex)
math::float4 weights; // Bone weights (sum should equal 1.0)
};
// Define vertex data
VertexWithBones vertices[] = {
{{1.0f, 0.0f, 0.0f}, {0, 1, 0, 0}, {0.7f, 0.3f, 0.0f, 0.0f}},
// ... more vertices ...
};
// Create vertex buffer
VertexBuffer* vb = VertexBuffer::Builder()
.vertexCount(3)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0,
VertexBuffer::AttributeType::FLOAT3, 0, sizeof(VertexWithBones))
.attribute(VertexAttribute::BONE_INDICES, 0,
VertexBuffer::AttributeType::USHORT4, 12, sizeof(VertexWithBones))
.attribute(VertexAttribute::BONE_WEIGHTS, 0,
VertexBuffer::AttributeType::FLOAT4, 20, sizeof(VertexWithBones))
.build(*engine);
vb->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(vertices, sizeof(vertices), nullptr));
Inline vs. Buffer Mode
There are two ways to provide bone data:
Inline Mode (enableSkinningBuffers = false)
// Bones stored directly in renderable
mat4f bones[] = { /* ... */ };
RenderableManager::Builder(1)
// ... geometry ...
.skinning(4, bones) // Inline bones
.enableSkinningBuffers(false) // Default
.build(*engine, renderable);
Buffer Mode (enableSkinningBuffers = true)
// Bones stored in SkinningBuffer (can be shared)
SkinningBuffer* sb = /* ... */;
RenderableManager::Builder(1)
// ... geometry ...
.skinning(sb, 256, 0) // Use buffer
.enableSkinningBuffers(true) // Required for buffer mode
.build(*engine, renderable);
Buffer mode advantages:
- Share bone data across multiple renderables
- More efficient memory usage for multiple meshes with the same skeleton
- Better for complex characters with many sub-meshes
Cleanup
Destroy the SkinningBuffer when no longer needed:
engine->destroy(skinningBuffer);
Ensure all renderables using the buffer are destroyed first, or updated to not reference it.