#3d-model #flame #parametric #avatar #graphics #head-model

oxigaf-flame

FLAME parametric head model — LBS, normal maps, mesh sampling

1 unstable release

0.1.0 Feb 24, 2026

#2947 in Algorithms


Used in 5 crates

Apache-2.0

200KB
3.5K SLoC

oxigaf-flame

FLAME parametric 3D head model implementation in Pure Rust.

Overview

This crate implements the FLAME (Faces Learned with an Articulated Model and Expressions) parametric 3D head model in pure Rust, with no dependencies on Python or C/C++ libraries.

FLAME is a statistical 3D head model that represents shape, expression, and pose variations using a Linear Blend Skinning (LBS) framework. It's widely used in computer vision and graphics for facial animation, avatar creation, and 3D reconstruction.

v0.1.0 — what's included:

  • Linear Blend Skinning (LBS) forward pass with SIMD/parallel acceleration
  • CPU software rasterizer for normal map generation
  • Mesh surface sampling for Gaussian initialization
  • Safetensors I/O — load/save FLAME models in .safetensors format (io_safetensors.rs)
  • FlameSequence — video frame processing with LRU caching and temporal interpolation (sequence.rs)
  • 124 tests (all passing)

Installation

[dependencies]
oxigaf-flame = "0.1"

Features

Feature Description Performance Gain
default Standard CPU implementation Baseline
simd SIMD-accelerated operations (requires nightly Rust) 2-4× faster
parallel Parallel batch processing with rayon Near-linear with cores
full Enable both simd and parallel Combined benefits

Feature Details

  • simd: Enables SIMD acceleration for:

    • Rodrigues rotation computation (axis-angle to rotation matrix)
    • Blend shape evaluation (weighted sum of deformations)
    • Normal map rendering (vectorized pixel operations)
    • Requires nightly Rust with portable_simd feature
  • parallel: Enables parallel processing for:

    • forward_batch_par() — parallel mesh generation
    • compute_normals_batch_par() — parallel normal computation
    • Scales with CPU core count

Example Usage

# Standard CPU implementation
oxigaf-flame = { version = "0.1" }

# With parallel processing
oxigaf-flame = { version = "0.1", features = ["parallel"] }

# Maximum performance (requires nightly)
oxigaf-flame = { version = "0.1", features = ["full"] }

Usage

Basic FLAME Forward Pass

use oxigaf_flame::{FlameModel, FlameParams};

fn main() -> Result<(), oxigaf_flame::FlameError> {
    // Load FLAME model from directory containing .npy files
    let model = FlameModel::load("path/to/flame/model")?;

    // Create neutral parameters (zero shape, expression, pose)
    let params = FlameParams::neutral();

    // Run forward pass to get posed mesh
    let mesh = model.forward(&params);

    println!("Generated mesh with {} vertices", mesh.vertices.len());
    println!("Mesh has {} faces", mesh.faces.len());

    Ok(())
}

Customize Shape and Expression

use oxigaf_flame::{FlameModel, FlameParamsBuilder};
use nalgebra as na;

fn main() -> Result<(), oxigaf_flame::FlameError> {
    let model = FlameModel::load("path/to/flame/model")?;

    // Use builder pattern to customize parameters
    let params = FlameParamsBuilder::new()
        .shape(na::DVector::from_vec(vec![0.5, -0.3, 0.2, /* ... */]))
        .expression(na::DVector::from_vec(vec![0.8, 0.0, 0.4, /* ... */]))
        .jaw_pose([0.1, 0.0, 0.0])  // Open jaw slightly
        .build();

    let mesh = model.forward(&params);

    // Access mesh data
    for vertex in mesh.vertices.iter().take(5) {
        println!("Vertex: ({:.3}, {:.3}, {:.3})", vertex[0], vertex[1], vertex[2]);
    }

    Ok(())
}

Safetensors I/O (v0.1.0)

use oxigaf_flame::{load_flame_model_safetensors, save_flame_model_safetensors};
use std::path::Path;

fn main() -> Result<(), oxigaf_flame::FlameError> {
    // Load FLAME model from safetensors
    let model = load_flame_model_safetensors(Path::new("flame_model.safetensors"))?;

    // Save to safetensors (preserves metadata)
    save_flame_model_safetensors(&model, Path::new("output.safetensors"))?;

    Ok(())
}

Video Sequence Processing (v0.1.0)

use oxigaf_flame::FlameSequence;
use std::path::Path;

fn main() -> Result<(), oxigaf_flame::FlameError> {
    // Load video sequence with LRU caching
    let mut sequence = FlameSequence::from_json(Path::new("sequence.json"))?;

    println!("Sequence has {} frames", sequence.num_frames());

    // Access frames with automatic caching
    let frame_42 = sequence.get_frame(42)?;

    // Interpolate between frames
    let interpolated = sequence.interpolate(42.5)?;
    let _ = (frame_42, interpolated);

    Ok(())
}

Render Normal Maps

use oxigaf_flame::{FlameModel, FlameParams, NormalMapRenderer, Camera};
use nalgebra as na;

fn main() -> Result<(), oxigaf_flame::FlameError> {
    let model = FlameModel::load("path/to/flame/model")?;
    let params = FlameParams::neutral();
    let mesh = model.forward(&params);

    // Set up camera
    let camera = Camera {
        position: na::Point3::new(0.0, 0.0, 2.0),
        target: na::Point3::new(0.0, 0.0, 0.0),
        up: na::Vector3::new(0.0, 1.0, 0.0),
        fov_y: std::f32::consts::FRAC_PI_4,
        aspect: 1.0,
        near: 0.1,
        far: 100.0,
    };

    // Render normal map
    let renderer = NormalMapRenderer::new(512, 512);
    let normal_map = renderer.render(&mesh, &camera)?;

    // Save to file
    normal_map.save("normal_map.png").map_err(|e| {
        oxigaf_flame::FlameError::Io(format!("Failed to save image: {}", e))
    })?;

    Ok(())
}

Sample Mesh Surface for Gaussian Initialization

use oxigaf_flame::{FlameModel, FlameParams, sample_mesh_surface};

fn main() -> Result<(), oxigaf_flame::FlameError> {
    let model = FlameModel::load("path/to/flame/model")?;
    let params = FlameParams::neutral();
    let mesh = model.forward(&params);

    // Sample 10,000 points uniformly on the mesh surface
    let num_points = 10000;
    let surface_points = sample_mesh_surface(&mesh, num_points)?;

    println!("Sampled {} points", surface_points.len());

    for point in surface_points.iter().take(5) {
        println!(
            "Position: ({:.3}, {:.3}, {:.3}), Normal: ({:.3}, {:.3}, {:.3})",
            point.position[0], point.position[1], point.position[2],
            point.normal[0], point.normal[1], point.normal[2]
        );
    }

    Ok(())
}

Batch Processing with Parallel Feature

use oxigaf_flame::{FlameModel, FlameParams};

#[cfg(feature = "parallel")]
fn main() -> Result<(), oxigaf_flame::FlameError> {
    let model = FlameModel::load("path/to/flame/model")?;

    // Create batch of parameters
    let params_batch: Vec<FlameParams> = (0..100)
        .map(|_| FlameParams::neutral())
        .collect();

    // Process batch in parallel (automatically uses rayon)
    let meshes = model.forward_batch_par(&params_batch)?;

    println!("Generated {} meshes in parallel", meshes.len());

    Ok(())
}

#[cfg(not(feature = "parallel"))]
fn main() {
    println!("This example requires the 'parallel' feature");
}

FLAME Parameters

The FLAME model is controlled by:

  • Shape parameters (β): Control identity-specific features (typically 100-300 coefficients)

    • Examples: face width, nose size, overall head shape
  • Expression parameters (ψ): Control facial expressions (typically 50-100 coefficients)

    • Examples: smile, frown, raised eyebrows, mouth open
  • Pose parameters (θ): Control joint rotations (5 joints × 3 = 15 values)

    • Root rotation (global head orientation)
    • Neck rotation
    • Jaw rotation (open/close mouth)
    • Left eye rotation
    • Right eye rotation
  • Translation: Global 3D translation applied after posing

Coordinate System

FLAME uses a right-handed coordinate system:

  • +X: Right (from the subject's perspective)
  • +Y: Up
  • +Z: Forward (out of the face)

Rotations are specified as axis-angle vectors and converted to rotation matrices using Rodrigues' formula.

Performance

The LBS forward pass is optimized for real-time performance:

  • ~1-2ms for standard FLAME mesh (5023 vertices) on modern CPUs
  • 3-4× faster with simd feature (nightly Rust required)
  • Near-linear scaling with parallel feature on multi-core CPUs

Run benchmarks with:

cargo bench -p oxigaf-flame

Statistics

  • Tests: 124 (all passing)
  • Benchmark files: 5 (flame_bench, lbs_forward, rodrigues, normal_map, simd_ops)
  • Key source files: model.rs, sequence.rs, normal_map.rs, io_safetensors.rs, io.rs

Documentation

License

Licensed under the Apache License, Version 2.0 (LICENSE)

Dependencies

~29MB
~453K SLoC