Skip to main content
Filament provides first-class support for Android with Java/Kotlin bindings and Maven distribution. This guide covers setup, initialization, and rendering on Android.

Installation

Maven Dependencies

Add Filament libraries to your Android project using Maven Central:
build.gradle
repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.google.android.filament:filament-android:1.69.4'
}

Available Libraries

Filament provides several Android libraries in the com.google.android.filament group:
ArtifactDescription
filament-androidThe core Filament rendering engine
filament-android-debugDebug version with additional validation
gltfio-androidglTF 2.0 loader for Filament
filament-utils-androidKTX loading, Kotlin math, and camera utilities
filamat-androidRuntime material builder/compiler

Initialization

Always initialize Filament before using any APIs:
MainActivity.kt
class MainActivity : Activity() {
    companion object {
        init {
            Filament.init()
        }
    }
}
You must call Filament.init() before creating any Filament resources. This loads the JNI library needed by the API.

Basic Setup

1

Create the rendering surface

Set up a SurfaceView for rendering:
private lateinit var surfaceView: SurfaceView
private lateinit var uiHelper: UiHelper
private lateinit var choreographer: Choreographer

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    surfaceView = SurfaceView(this)
    setContentView(surfaceView)
    choreographer = Choreographer.getInstance()
    setupSurfaceView()
}
2

Configure UiHelper

Use UiHelper to manage the surface lifecycle:
private fun setupSurfaceView() {
    uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
    uiHelper.renderCallback = SurfaceCallback()
    
    // Optional: set a specific resolution
    // uiHelper.setDesiredSize(1280, 720)
    
    uiHelper.attachTo(surfaceView)
}
3

Create Engine and components

Initialize the Filament engine, renderer, and scene:
private lateinit var engine: Engine
private lateinit var renderer: Renderer
private lateinit var scene: Scene
private lateinit var view: View
private lateinit var camera: Camera

private fun setupFilament() {
    engine = Engine.Builder()
        .featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0)
        .build()
    renderer = engine.createRenderer()
    scene = engine.createScene()
    view = engine.createView()
    camera = engine.createCamera(engine.entityManager.create())
    
    view.camera = camera
    view.scene = scene
}

UiHelper and SwapChain Management

The UiHelper class simplifies SwapChain management for SurfaceView, TextureView, or SurfaceHolder:
inner class SurfaceCallback : UiHelper.RendererCallback {
    override fun onNativeWindowChanged(surface: Surface) {
        swapChain?.let { engine.destroySwapChain(it) }
        
        var flags = uiHelper.swapChainFlags
        if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
            if (SwapChain.isSRGBSwapChainSupported(engine)) {
                flags = flags or SwapChainFlags.CONFIG_SRGB_COLORSPACE
            }
        }
        
        swapChain = engine.createSwapChain(surface, flags)
        displayHelper.attach(renderer, surfaceView.display)
    }

    override fun onDetachedFromSurface() {
        displayHelper.detach()
        swapChain?.let {
            engine.destroySwapChain(it)
            engine.flushAndWait()
            swapChain = null
        }
    }

    override fun onResized(width: Int, height: Int) {
        view.viewport = Viewport(0, 0, width, height)
        
        val aspect = width.toDouble() / height.toDouble()
        camera.setProjection(
            Camera.Projection.PERSPECTIVE,
            45.0, aspect, 0.1, 100.0
        )
        
        FilamentHelper.synchronizePendingFrames(engine)
    }
}

Rendering Loop

Use Choreographer to schedule frames at vsync:
inner class FrameCallback : Choreographer.FrameCallback {
    override fun doFrame(frameTimeNanos: Long) {
        choreographer.postFrameCallback(this)
        
        if (uiHelper.isReadyToRender) {
            if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
                renderer.render(view)
                renderer.endFrame()
            }
        }
    }
}

override fun onResume() {
    super.onResume()
    choreographer.postFrameCallback(frameScheduler)
}

override fun onPause() {
    super.onPause()
    choreographer.removeFrameCallback(frameScheduler)
}

Creating Geometry

Example of creating a simple triangle mesh:
data class Vertex(val x: Float, val y: Float, val z: Float, val color: Int)

val vertexCount = 3
val vertexSize = 3 * 4 + 4 // 3 floats + 1 int

val vertexData = ByteBuffer.allocate(vertexCount * vertexSize)
    .order(ByteOrder.nativeOrder())
    .apply {
        put(Vertex(1.0f, 0.0f, 0.0f, 0xffff0000.toInt()))
        put(Vertex(-0.5f, 0.866f, 0.0f, 0xff00ff00.toInt()))
        put(Vertex(-0.5f, -0.866f, 0.0f, 0xff0000ff.toInt()))
    }
    .flip()

fun ByteBuffer.put(v: Vertex): ByteBuffer {
    putFloat(v.x)
    putFloat(v.y)
    putFloat(v.z)
    putInt(v.color)
    return this
}

Building Renderables

Create a renderable entity and add it to the scene:
val renderable = EntityManager.get().create()

RenderableManager.Builder(1)
    .boundingBox(Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.01f))
    .geometry(
        0,
        PrimitiveType.TRIANGLES,
        vertexBuffer,
        indexBuffer,
        0,
        3
    )
    .material(0, material.defaultInstance)
    .build(engine, renderable)

scene.addEntity(renderable)

Material Loading

Load precompiled materials from assets:
private fun loadMaterial() {
    val buffer = readUncompressedAsset("materials/baked_color.filamat")
    material = Material.Builder()
        .payload(buffer, buffer.remaining())
        .build(engine)
}

private fun readUncompressedAsset(assetName: String): ByteBuffer {
    assets.openFd(assetName).use { fd ->
        val input = fd.createInputStream()
        val dst = ByteBuffer.allocate(fd.length.toInt())
        val src = Channels.newChannel(input)
        src.read(dst)
        src.close()
        return dst.apply { rewind() }
    }
}

Cleanup

Always destroy resources in the correct order:
override fun onDestroy() {
    super.onDestroy()
    
    choreographer.removeFrameCallback(frameScheduler)
    uiHelper.detach()
    
    engine.destroyEntity(renderable)
    engine.destroyRenderer(renderer)
    engine.destroyVertexBuffer(vertexBuffer)
    engine.destroyIndexBuffer(indexBuffer)
    engine.destroyMaterial(material)
    engine.destroyView(view)
    engine.destroyScene(scene)
    engine.destroyCameraComponent(camera.entity)
    
    val entityManager = EntityManager.get()
    entityManager.destroy(renderable)
    entityManager.destroy(camera.entity)
    
    engine.destroy()
}

Backend Support

Filament on Android supports:
  • OpenGL ES 3.0+ - Wide device compatibility
  • Vulkan 1.0+ - Modern devices with better performance
The backend is selected automatically based on device capabilities.

Next Steps

glTF Loading

Load 3D models using gltfio-android

Materials

Create and customize materials

Lighting

Set up lights and IBL

Camera

Configure camera and projection

Build docs developers (and LLMs) love