最近一个第三方实现的 bdero/flutter_scene 惊艳到我了,比如下面这个类似「我的世界」的游戏效果,就是作者用 Flutter Scene 做的,全部基于 Flutter GPU/Impeller 内容实现,没有引入任何其他外部引擎,没有任何 PlatfromView :

Flutter Scene 整个实现里,都是自己管理场景图、材质、动画、光照、阴影、RenderGraph、GPU 资源、绘制排序、空间裁剪和 glTF 导入 ,底层依赖原生的 flutter_gpu ,同时支持 Flutter 全平台,除了 Web 平台用了一套 API 兼容的 WebGL2 后端,而且支持 Custom embedders ,也就是甚至可以支持鸿蒙。

从实现上看它已经远超官方的任何 GPU Demo 效果,比如项目有 PBR、IBL、阴影、SSAO、SSR、景深、后处理、动画混合、LOD、实例化、RenderTexture、3D Gaussian Splatting、物理、3D 场景中的 Flutter Widget、甚至支持了场景编辑器和 MCP Agent 操作接口。

简单看的话呀,整个项目可以分成六层:
| 层级 | 主要模块 | 作用 |
|---|---|---|
| Flutter 展示层 | SceneView、Semantics、Pointer、WidgetTexture |
帧循环、Flutter 合成、输入、无障碍 |
| 场景逻辑层 | Scene、Node、Component、Camera |
场景图、行为、动画、物理生命周期 |
| 渲染数据层 | RenderScene、RenderItem、BVH |
扁平绘制列表和空间索引 |
| 渲染调度层 | RenderGraph、各种 RenderPass |
阴影、深度、SSAO、SSR、PBR、后处理 |
| 绘制编码层 | SceneEncoder、Material、Geometry |
Pipeline、排序、绑定、Draw Call |
| GPU 后端层 | Flutter GPU/Impeller、WebGL2 shim | 真正提交 GPU 命令 |
除此之外还有三套外围系统支持:
- glTF、
.fsceneb、.fmat资产构建系统 - Rapier、Box3D 物理后端
- Editor Core、编辑器 UI 和 MCP Agent 接口

而且不只有
flutter_scene,项目还包含flutter_scene_rapier、flutter_scene_box3d、编辑器、MCP、和独立编辑器 App,完善程度是真的让人意外。
而且使用方式也很方便,通过 SceneView 入口就可以接入 , SceneView 内部拥有一个 Flutter Ticker,但每帧不会重建整个 Widget 子树,而是通过一个 ChangeNotifier 只触发绘制层重绘:
- 驱动每帧 tick
- 构造或取得 Camera
- 调用
Scene.render - 管理加载占位和 Pipeline 预热
- 处理场景里的 Widget 输入
- 将 3D 节点投影进 Flutter Semantics 树
也就是 SceneView 不是 PlatformView,也不是 Flutter 上方盖一层原生 Surface, SceneView 的结果实际是会参与 Flutter 的 Canvas 合成,可以和普通 Flutter UI 叠加、裁剪和布局。
scss
final scene = Scene();
class SpinComponent extends Component {
SpinComponent(this.speed);
final double speed;
@override
void update(double deltaSeconds) {
node.localTransform *=
vm.Matrix4.rotationY(speed * deltaSeconds);
}
}
void buildScene() {
final mesh = Mesh(
CuboidGeometry(vm.Vector3.all(1)),
PhysicallyBasedMaterial(),
);
scene.add(
Node(mesh: mesh)..addComponent(SpinComponent(1.2)),
);
}
Widget buildView() {
return SceneView(
scene,
camera: PerspectiveCamera(
position: vm.Vector3(0, 2, -5),
target: vm.Vector3.zero(),
),
warmUp: true,
);
}
然后对于每个 RenderView 都会获得自己的离屏纹理 ,纹理尺寸按根据实际情况缩放成功后,再合成到 Flutter Canvas,3D 场景完成 GPU 渲染后,纹理通过 asImage() 转成 Flutter 可绘制图像,再由 Canvas.drawImageRect 合成到目标区域。
所以他有个很明显的超级优势,它天然可以支持:
- Flutter 布局中的局部 3D 区域
- 画中画
- 分屏多 Camera
- 不同 View 使用不同 LayerMask
- RenderTexture
- 3D 和 2D UI 混合
当然,相对的成本就是每个屏幕 View 都有独立离屏目标和 RenderGraph,多个 Camera 不能一次绘制自动复用,不同 View 不共享帧内 Render Target。
然后项目里的场景图主要靠 Node 和 Component,其中 Node 只负责层级和变换 ,Node 是典型的保留式场景图节点,包含:
- 本地变换和缓存的世界变换
- 父子层级
- Mesh
- Component
- 可见性
- Render Layer
- Raycast 开关
- 阴影静态标记
- LOD、动画、Skin 等附加数据
变换采用脏标记机制,修改父节点时,子 tree 的世界变换会被标记失效,然后真正访问或渲染时才重新计算,世界 AABB 也有单独缓存,同时向祖先传播失效。
简单来说,比如你直接原地修改 Matrix4,引擎不知道矩阵变了,必须手工调用 markTransformDirty():
ini
node.localTransform.translate(...);
node.markTransformDirty();
然后 Component 是行为和能力插件 ,Component 类似轻量 ECS 中的组件,但需要依附在传统 Node ,有完整生命周期:
sql
onAttach
↓
onMount
↓
onLoad(异步)
↓
fixedUpdate / update
↓
onUnmount
↓
onDetach
比如
onLoad没结束前,update不会执行,物理系统还可以用fixedUpdate运行固定时间步。
目前引擎内置组件包括:
- Mesh、Instanced Mesh
- Camera
- Directional、Point、Spot Light
- LOD
- Semantics
- WidgetComponent
- SplatComponent
- Environment Volume
- 各类物理组件
这个设计的优势在于, Flutter 开发者不用接受完整 ECS 心智模型,自定义行为只需要继承
Component。
当然,整个项目最最最重要的是,场景树不会直接拿来绘制,Node Graph 和 RenderScene 是两套结构 。
Node 用来表达业务层级,但 GPU 绘制时不适合每帧递归整棵树,所以 MeshComponent 挂载到场景时,会把每个可绘制 Primitive 注册成一个 RenderItem。
也是很贴合 Flutter 多颗 Tree 的设计。
而 RenderItem 主要保存:
- Geometry 和 Material
- 当前世界变换
- 可见性
- Layer
- 世界 AABB
- LOD
- Instance Transform
- 对象对应的局部光源列表
- 阴影和高亮状态
渲染 Pass 遍历的是扁平的
RenderScene.items。
然后就是 BVH 用于裁剪和灯光分配,有边界的 RenderItem 会放进 BVH,没有边界或关闭裁剪的对象进入 always-visible 列表。
静态结构变化会重建 BVH,仅仅是对象移动时可以走 refit,而不是完整 rebuild。
BVH 不只用于视锥裁剪,还会用在局部灯光分配,而 Point 和 Spot Light 每帧通过空间结构找到可能受影响的对象,然后给每个 RenderItem 生成灯光索引切片,Fragment Shader 不需要循环整个场景的灯光。
所以本质上是一个带 Cluster 思路但粒度在对象级的 Forward Renderer,不是传统 Deferred Renderer。
整个完整渲染流程大概如下:

更详细的渲染流程会是如下所示,每帧大体走下面这条路径:

这里这些 Pass 不是每帧全部运行,会根据实际情况按能力动态启用,例如:
- SSAO、SSR、景深或自定义 Pass 请求深度时,才生成 Depth Prepass
- 材质声明
scene_color时,才把主 Scene Pass 拆成不透明和透明两个阶段 - 没有阴影灯就不创建 Shadow Pass
- 没启用后处理就没有额外成本
另外这里面的 RenderGraph 并不是复杂的图编译器,我感觉这个名字很有误解,更准确地说应该是:
- 一个有顺序的 Pass 列表
- 一个 Pass 间共享数据的 Blackboard
- 一个跨帧复用纹理的 TransientTexturePool
Pass 按插入顺序执行,不做自动依赖分析、自动重排和无效 Pass 裁剪,每个 Pass 还会创建自己的 CommandBuffer。
这种实现的好处是简单、可调试、可以适合现在还在演进的 Flutter GPU API,不过缺点也和明显,如果 Pass 越来越多,资源复用和调度效率会落后于成熟引擎的 RenderGraph。
然后在编码上,项目 SceneEncoder 将可见对象分成两类:
- 不透明对象:先按 Render Pipeline 分组,减少 Shader、Vertex Layout 等状态切换,然后同 Pipeline 内再按距离从近到远绘制,利用深度测试尽早杀掉后面的 Fragment
- 透明对象:按 Camera 距离从远到近绘制,使用 Premultiplied Alpha 混合,透明的 Instanced Mesh 会被拆成每个 Instance 一个排序记录
然后整个 Pipeline 使用进程级缓存,Key 是:
Vertex Shader + Fragment Shader + Vertex Layout
项目还针对 Pipeline 创建、Uniform 临时内存和重复绑定做了大量优化,比如:
一个 100 Tile 的流式场景在 Metal 上,平均帧构建时间 5.7ms。
然后 Flutter Scene 支持两种导入模式:
1、运行时导入
ini
final model = await Node.fromGlbAsset('assets/model.glb');
scene.add(model);
2、外部获得 bytes:
ini
final model = await Node.fromGlbBytes(bytes);
然后在构建期预处理,通过 Build Hook 将 glTF 转成 .fsceneb:
less
buildScenes(
buildInput: input,
buildOutput: output,
inputFilePaths: ['assets_src/model.glb'],
);
也就是实际读取的是构建时生成的
.fsceneb,避免在终端设备上重新解析 glTF,同时它还支持 Scene Subtree 流式加载和 Prefab 复用
然后对于材质系统,内置材质主要有:
PhysicallyBasedMaterialUnlitMaterialSpriteMaterialShaderMaterialPreprocessedMaterial
PBR 使用 Metallic-Roughness 工作流,支持 Base Color、Normal、Metallic/Roughness、AO、Emissive 和环境反射。
默认 Scene 会懒加载一个程序化 Studio Environment,所以创建 PBR 材质后不额外配置灯光也能看到结果。
另外可以通哟 .fmat 自定义材质语言 ,.fmat 的定位类似 Filament .mat、Godot Shader 和一种简化的 Shader DSL :
scss
material {
name: "Toon",
shading_model: lit,
blending: opaque,
parameters: [
{ type: vec4, name: base_color,
hint: source_color, default: [1, 1, 1, 1] },
{ type: float, name: bands,
hint: range(1, 8, 1), default: 4 },
],
}
fragment {
void Surface(inout MaterialInputs material) {
float lighting = max(dot(
GetWorldNormal(),
normalize(vec3(0.4, 0.8, 0.2))
), 0.0);
material.base_color =
material_params.base_color *
floor(lighting * material_params.bands) /
material_params.bands;
PrepareMaterial(material);
}
}
之后 Build Hook 会编译 Shader,同时生成参数 Metadata,然后 Dart 侧通过名称设置类型安全参数:
csharp
final material = await loadFmatMaterial('assets/toon.fmat');
material.parameters
..setColor('base_color', const Color(0xFFE0A030))
..setFloat('bands', 4);
而相比原始的 ShaderMaterial,开发者不需要手工处理 std140 Packing、Sampler 和 Uniform Slot,甚至它还支持材质主动声明:
scene_color
scene_depth
只有可见材质请求时,引擎才创建对应资源。
不过也不是没有限制,项目距离完整 glTF/PBR 还有明显差距:
- 没有自定义 per-light BRDF Hook
mat3因 GLES std140 问题暂不支持- lit Shader 已接近 Metal 最低 16 Sampler 限制
- 第二 UV、glTF Tangent、Texture Transform 尚未完成
- Clearcoat、Sheen、Transmission 等 glTF 扩展尚未完成
因此普通 Metallic-Roughness 模型基本可用,但汽车漆、玻璃、镜片、布料、复杂 UV 资源和高保真工业模型可能不能正确还原。
之后导入的 glTF Animation 会保存在模型根节点上,通过以下代码执行:
ini
final walk = model.createAnimationClip(
model.findAnimationByName('Walk')!,
)
..loop = true
..play();
每个 Clip 有独立播放状态、速度、时间、循环和 Weight,多个 Clip 可以同时运行,内部
AnimationPlayer会按权重混合,适合 Idle、Walk、Run Cross Fade。
然后针对光照、阴影和后处理,目前已经有:
- HDR/EXR/equirectangular Environment Ma
- Diffuse SH 和 Prefiltered Specular IBL
- Procedural Gradient/Physical Sky
- 自定义
.fmatSky - Directional、Point、Spot Light
- Cascaded Directional Shadow
- Spot Shadow Atlas
- 静态阴影缓存
- Environment Volume 混合
- Exposure 和 Tone Mapping
- SSAO、SSR、Fog、God Rays
- Bloom、Color Grading、Vignette、Film Grain
- Chromatic Aberration、DoF、FXAA、MSAA
- 自定义 Render Pass 和 Post Effect
其中静态阴影缓存最值得单独说,只需要给静态物体设置:
ini
node.shadowStatic = true;
之后它们的 Cascade Shadow Tile 就可以跨帧复用,只把动态物体叠加进去,这在大规模静态世界里,比每帧每个 Cascade 重绘所有建筑和地面便宜很多。
然后最重磅的就是,Flutter Widget 可以放进 3D 场景,这是项目最有 Flutter 特色的功能:
less
scene.add(
Node()
..addComponent(
WidgetComponent(
size: const Size(480, 300),
pixelRatio: 2,
worldHeight: 1.5,
child: const MyControlPanel(),
),
),
);
甚至可以把 Widget 绑定到 glTF 模型已有的显示屏材质,不额外创建 Quad。
不过因为它目前的工作方式是:
Flutter Raster
→ GPU/CPU Readback
→ CPU
→ 再上传到 3D Texture
也就是有一次 CPU 往返开销,比如典型 Panel 每次捕获可能花费数毫秒,而动态 Widget 默认每帧捕获,所以大量高分辨率 Widget Surface 会很增加不少开销。
另外其他限制还有:
- MouseRegion Hover 不能转发
- 键盘和 Focus 不支持
- TextField 还不可用
- WidgetComponent 不进入
.fscene序列化
然后在物理架构上,核心包只定义抽象物理接口:
PhysicsWorldRigidBodyColliderJoint- Raycast、ShapeCast、Overlap
- Collision Event
而 Rapier 3D 是通过 flutter_scene_rapier ,利用 Dart FFI 调用 Rust Rapier ,它支持刚体、碰撞器、关节、电机、查询和 Character Controller,并提供大部分平台的预编译二进制,物理由固定时间步推进,渲染 Transform 使用插值。
而 Box3D 是通过 flutter_scene_box3d ,利用 Dart box3d 包接入,支持常用 Shape、查询和碰撞事件,但关节和显式质量/惯性覆盖貌似还没接好。

所以可以看出来,这是一整套完善的 Flutter 3D 引擎,而且还是可用的成套支持,还支持 Flutter Widget 融合使用,比如在游戏之外的支持,例如:
- 商品和设备 3D 展示
- App 里的可交互 3D 数字形象
- 数据可视化
- 3D 编辑工具
- ···
这个项目算是直接补全了 Flutter 官方一直没时间支持的 3D 场景,同时作者也是前 Impeller 工程师,貌似离开谷歌之后做的这套项目,想想有趣,在谷歌没做成的事情,离开谷歌反而做成了。