Glint
A pure-Dart 3D engine for Flutter, built on Flutter GPU.
Website · pub.dev · API docs · See it running · FAQ
Glint renders real glTF/GLB models — textures, multi-material meshes, PBR
lighting, HDRI reflections, animations — as ordinary Flutter widgets. No
platform views, no WebViews, no native code. Overlay Flutter UI on your
scene, restyle materials with setState, pin widgets to points on a model,
and run a real-time game loop, all from Dart.
Scene3D(
scene: ProductShowroom(), // declarative scene description
onModelTap: (hit) => restyle(hit), // CPU-accurate tap picking
labels: [
Label3D( // a real Flutter widget, pinned to
anchor: Vector3(.96, 1.34, 0), // a point on the model's surface
child: PriceTag(),
),
],
)
Watch Duck Dash run at 60 fps on iPhone → An endless runner built entirely on the engine below — imported models, animation, fog, a chase camera, and a Flutter HUD.
What's inside
| Capability | Detail |
|---|---|
| glTF 2.0 loading | Binary .glb from assets or network; scene hierarchy, node transforms, 16/32-bit indices, normals (parsed or generated) |
| Materials | Per-primitive material batches: base-color textures + factors, metallic, roughness; embedded PNG/JPEG textures (auto-capped at 1K for mobile memory) |
| Lighting | GGX metallic-roughness PBR, directional key light, HDRI image-based lighting (.hdr decoder + irradiance/specular prefiltering built in), distance fog |
| Animation | glTF node animation clips: translation/rotation/scale channels, LINEAR and STEP sampling, quaternion slerp, looping |
| Interaction | Orbit/pan/zoom gestures, scroll-aware mode for scrollable pages, tap picking via CPU raycasting (Möller–Trumbore) |
| Widgets in 3D | Label3D: project any widget onto a model anchor with fade/hide occlusion policies |
| Game loop | GlintGameView: ticker-driven frames, unlimited model instances with per-node transforms, free look-at camera, translucent blob shadows, frustum culling |
| Diagnostics | Live FPS / frame time / draw call / triangle overlay; typed, actionable errors for every asset failure |
Status: v0.1, feature-complete for the first release and validated at 60 fps with ~160 draw calls per frame on a physical iPhone and on macOS desktop. Like Flutter GPU itself, Glint is early — expect rough edges, and see Outside v0.1 for what isn't here yet.
Getting started
1. Install
flutter pub add glint_engine
2. Enable Flutter GPU (one-time, per app)
Glint renders through Flutter GPU, which ships behind a flag. Bake it into your app so no run flags are ever needed:
iOS and macOS — add to ios/Runner/Info.plist and
macos/Runner/Info.plist:
<key>FLTEnableFlutterGPU</key>
<true/>
<!-- macOS only, Impeller is already the default on iOS: -->
<key>FLTEnableImpeller</key>
<true/>
Android — add inside <application> in
android/app/src/main/AndroidManifest.xml:
<meta-data
android:name="io.flutter.embedding.android.EnableFlutterGPU"
android:value="true" />
(For a quick trial without editing manifests:
flutter run --enable-impeller --enable-flutter-gpu.)
3. Render a model
import 'package:glint_engine/glint_engine.dart';
GlintGpuFirstLight(
model: Model.asset('assets/product.glb'), // or Model.network(url)
environmentAsset: 'assets/studio.hdr', // optional HDRI
fallback: const Text('Flutter GPU unavailable'), // shown when disabled
)
4. Or describe a scene declaratively
class Showroom extends Scene {
const Showroom({this.finish});
final Material3D? finish;
@override
List<Light3D> get lights => const [
DirectionalLight(direction: Vector3(.55, -1, -.65), intensity: .87),
EnvironmentLight(asset: 'assets/studio.hdr'),
];
@override
List<Node3D> get children => [
Node3D(
model: const Model.asset('assets/product.glb'),
material: finish, // null keeps the model's own material
),
];
}
// Restyling is just state:
Scene3D(scene: Showroom(finish: selected))
5. Build a game
GlintGameView(
models: const {
'hero': Model.asset('assets/hero.glb'),
'crate': Model.asset('assets/crate.glb'), // animated GLBs just work
},
fogColor: sky, fogDistance: 95,
onFrame: (dt) {
world.step(dt); // your simulation
return GlintGameFrame(
camera: GlintGameCamera(position: eye, target: focus),
instances: [
for (final thing in world.things)
GlintGameInstance(
model: thing.kind,
transform: thing.transform,
animationTime: world.clock, // loops the model's clip
),
],
);
},
)
The example app ships a complete endless runner (Duck Dash) built this way.
Examples
example/ is a launcher with four demos, each a compact reference:
- Duck Dash — endless runner: imported multi-material models, animation playback, fog, blob shadows, swipe input, Flutter HUD.
- Product configurator — a scrollable product page with scroll-aware orbiting, live material swatches, tap-to-restyle, anchored labels.
- Model viewer — the minimal one-widget integration.
- Anchored labels —
Label3Docclusion policies in isolation.
git clone https://github.com/kiddo4/glint.git
cd glint/example
flutter run # manifests already carry the GPU flags
Gesture routing in scrollable pages
Inside a ListView, pass gestureMode: GlintGestureMode.scrollAware:
one-finger horizontal drags orbit, one-finger vertical drags keep scrolling
the page, two-finger and trackpad pinches orbit, pan, and zoom.
FAQ
What platforms does this package support?
| Platform | Status |
|---|---|
| iOS | 🟢 Validated — 60 fps on device |
| macOS | 🟢 Validated |
| Android | 🟡 Physical device only — see below |
| Web / Windows / Linux | ⚪ Not yet |
Glint follows wherever Flutter GPU runs. On iOS, Impeller is already Flutter's default renderer. On macOS it's opt-in (the manifest key above enables it). On Android, physical devices with Vulkan work well; emulators do not — most lack a real Vulkan driver, so Impeller falls back to its OpenGLES backend, which Flutter GPU does not support yet, and the app aborts natively rather than failing gracefully. There is no Dart-level way to detect this today, so: test on a physical Android device. Web, Windows, and Linux aren't supported because Flutter GPU isn't available there yet.
Isn't this a reinvention of flutter_scene?
Not the same bet. flutter_scene
is a general-purpose scene graph; Glint is a product-experience engine
built widget-first: scenes are declarative Flutter state (Scene3D,
restyled with setState), Flutter widgets anchor to points on a model with
real occlusion, gestures cooperate with scrollable pages, and it drives a
real-time game loop with a Flutter HUD over it. Owning the whole
pipeline — glTF parser, PBR + IBL shaders, picking, animation — is
deliberate: every millisecond of the frame is understood, not inherited.
Why do I have to touch platform manifests at all?
Flutter GPU ships behind a flag while it's in preview, and Info.plist/ AndroidManifest have no cross-package way to merge settings into a host app (unlike Android, where a plugin's manifest entries merge automatically — something worth doing for Glint once it's a proper Flutter plugin). Until then it's a one-time, two-line setup per app, documented above.
Troubleshooting
- "Flutter GPU requires the Impeller rendering backend" — the manifest keys from step 2 are missing, or you ran without the flags.
- Native crash on Android:
render_pass_gles.cc ... Check failed— see the platform FAQ above; run on a physical Vulkan-capable device. - "Missing GLB magic header" — the file isn't a binary glTF (
.glb). If it lives in Git LFS, rungit lfs pull; a 134-byte "file" is an LFS pointer, not a model. - Model renders but looks wrong — Glint v0.1 reads base-color textures and factors; normal/ORM maps, Draco compression, and skeletal skinning are not supported yet.
- Blurry textures on imported assets — embedded textures are decoded at a maximum of 1024px per side to protect mobile memory.
Outside v0.1
Skeletal skinning, morph targets, point/spot lights, shadow mapping, KTX, custom shaders, physics, audio, Web/Windows/Linux. Open an issue if one of these is blocking you — real projects are what decide what comes next.
License
MIT. The Duck model is the Khronos glTF sample asset; see assets/models/ATTRIBUTION.md.