Your first scene
This page builds a complete, runnable Flutter app that renders a 3D cube with an orbiting camera. It uses only the engine’s built-in geometry and material, so there is no asset to load yet.
This is the scene you are about to build, running live in your browser.
If you have not added the package, start with Installation.
The complete app
Section titled “The complete app”Copy this into lib/main.dart and run it. It is a whole Flutter app in one file.
import 'dart:math';
import 'package:flutter/material.dart';import 'package:flutter_scene/scene.dart';import 'package:vector_math/vector_math.dart' as vm;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { const MyApp({super.key});
@override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold(body: FirstScene()), ); }}
class FirstScene extends StatefulWidget { const FirstScene({super.key});
@override State<FirstScene> createState() => _FirstSceneState();}
class _FirstSceneState extends State<FirstScene> { // Constructing a Scene starts loading the engine's shared resources. final Scene scene = Scene();
@override void initState() { super.initState();
// A cube mesh, made from built-in geometry and an unlit material. final mesh = Mesh( CuboidGeometry(vm.Vector3(1, 1, 1), debugColors: true), UnlitMaterial(), ); scene.add(Node(mesh: mesh)); }
@override Widget build(BuildContext context) { return SceneView( scene, // The camera orbits the origin once per second. cameraBuilder: (elapsed) { final t = elapsed.inMicroseconds / 1e6; return PerspectiveCamera( position: vm.Vector3(sin(t) * 5, 2, cos(t) * 5), target: vm.Vector3(0, 0, 0), ); }, ); }}Run it with the flags from Installation.
flutter run --enable-flutter-gpu --enable-impeller # nativeflutter run -d chrome # webYou should see a multicolored cube turning as the camera circles it.
What is happening
Section titled “What is happening”- A
Sceneholds the scene graph and does the rendering. Constructing one also kicks off loading the engine’s shared resources in the background (see Warming up below). - A
Meshpairs aGeometry(the shape) with aMaterial(the shading). Here that is a unitCuboidGeometryand anUnlitMaterial. ThedebugColorsflag tints each face so the cube is easy to read without any lighting set up. - A
Nodeplaces the mesh in the scene graph, andscene.addattaches it under the scene root. SceneViewis the bridge to Flutter. It owns the per-frame ticker and the render call. Each frame it callscameraBuilderwith the elapsed time, so returning a camera computed fromelapsedanimates the orbit with no manual timer.
Warming up
Section titled “Warming up”The first time the engine renders, it loads a few shared resources, such as shaders and a lookup texture. Until that finishes, SceneView skips frames and shows nothing. You do not have to manage this. Build your scene whenever you like and the content appears on its own once the engine is ready, usually within a frame or two.
If you would rather show your own placeholder during that brief warm-up, await Scene.initializeStaticResources() and swap in the scene when it completes.
@overrideWidget build(BuildContext context) { return FutureBuilder<void>( future: Scene.initializeStaticResources(), builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { // Your placeholder while the engine warms up. return const Center(child: CircularProgressIndicator()); } return SceneView(scene, cameraBuilder: _orbit); }, );}This is optional. Without it the scene still renders correctly; you just see whatever is behind the view during the warm-up rather than a placeholder.
Going further: add your own motion
Section titled “Going further: add your own motion”SceneView already animates the camera. To animate the cube itself, attach a Component. A component is behavior bound to a node, with an update hook the engine calls every frame.
class SpinComponent extends Component { SpinComponent(this.radiansPerSecond);
final double radiansPerSecond;
@override void update(double deltaSeconds) { node.localTransform = node.localTransform * vm.Matrix4.rotationY(radiansPerSecond * deltaSeconds); }}Attach it when you build the node.
final node = Node(mesh: mesh)..addComponent(SpinComponent(1.5));scene.add(node);The cube now spins on its own, independent of the camera.
- Core concepts explains the scene graph, cameras, and the render loop in more detail.
- Assets and loading covers importing real glTF models instead of built-in shapes.
- Materials and Lighting and environment cover lit, physically based shading.