包阅导读总结
1.
关键词:Flutter GPU、Impeller、3D 渲染、图形 API、自定义渲染
2.
总结:
Flutter 3.24 引入了处于预览阶段的 Flutter GPU 及 3D 渲染支持库,它是底层图形 API,能通过 Dart 构建自定义渲染器。Flutter GPU 是 Impeller 对 HAL 的轻包装,目前处于早期阶段,未来将支持 3D。文中还介绍了相关架构、资源管理及简单的代码示例。
3.
主要内容:
– Flutter GPU 简介
– 是 Flutter 3.24 引入的新底层图形 API
– 处于早期预览阶段
– 依赖 Impeller 实现
– 功能与特点
– 可通过 Dart 与 GLSL 着色器构建自定义渲染器
– 目前提供基本光栅化 API,后续会完善
– 是 Impeller 对 HAL 的轻包装
– 着色器提前编译,仅支持 Impeller 平台
– 与 Impeller 的关系
– Impeller 架构包括 Aiks、Entities Framework、HAL 等
– HAL 为底层图形硬件提供统一接口
– Flutter GPU 不是 Impeller,两者存在差异
– 使用方式
– 在 main channel 分支添加 flutter_gpu SDK 包
– 编写 GLSL 着色器,定义顶点和片段着色器
– 通过相关 API 实现渲染
– 提供了 2D 和 3D 渲染的示例代码
思维导图:
文章地址:https://juejin.cn/post/7399985723673821193
文章来源:juejin.cn
作者:恋猫de小郭
发布时间:2024/8/6 22:27
语言:中文
总字数:2939字
预计阅读时间:12分钟
评分:87分
标签:Flutter,GPU,3D渲染,自定义渲染器,跨平台
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
Flutter 3.24 版本引入了 Flutter GPU 概念的新底层图形 API flutter_gpu
,还有 flutter_scene
的 3D 渲染支持库,它们目前都是预览阶段,只能在 main channel 上体验,并且依赖 Impeller 的实现。
Flutter GPU 是 Flutter 内置的底层图形 API,它可以通过编写 Dart 代码和 GLSL 着色器在 Flutter 中构建和集成自定义渲染器,而无需 Native 平台代码。
目前 Flutter GPU 处于早期预览阶段并只提供基本的光栅化 API,但随着 API 接近稳定,会继续添加和完善更多功能。
详细说,Flutter GPU 是 Impeller 对于 HAL 的一层很轻的包装,并搭配了关于着色器和管道编排的自动化能力,也通过 Flutter GPU 就可以使用 Dart 直接构建自定义渲染器。
Flutter GPU 和 Impeller 一样,它的着色器也是使用 impellerc 提前编译,所以 Flutter GPU 也只支持 Impeller 的平台上可用。
Impeller 的 HAL 和 Flutter GPU 都没打算成为类似 WebGPU 这样的正式标准,相反,Flutter GPU 主要是由 Flutter 社区开发和发展,专职为了 Flutter 服务,所以不需要考虑「公有化」的兼容问题。
在 Flutter GPU 上,可直接从 Dart 与 Impeller 的 HAL 对话,甚至 Impeller Scene API(3D)也将作为重写的一部分出现。
说人话就是,可以用 Dart 通过 Flutter GPU 直接构建自定义渲染效果,未来直接支持 3D
可能有的人对于 Impeller 的整体结构和 HAL 还很模式无法理解,那么这里我们简单过一下:
-
在 Framework 上层,我们知道 Widget -> Element -> RenderObject -> Layer 这样的过程,而后其实流程就来到了 Flutter 自定义抽象的 DisplayList
-
DisplayList 帮助 Flutter 在 Engine 做了接耦,从而让 Flutter 可以在 skia 和 Impeller 之间进行的切换
-
之后 Impeller 架构的顶层是 Aiks,这一层主要作为绘图操作的高级接口,它接受来自 Flutter 框架的命令,例如绘制路径或图像,并将这些命令转换为一组更精细的 “Entities”,然后转给下一层。
-
Entities Framework,它是 Impeller 架构的核心组件,当 Aiks 处理完命令时生成 Entities 后,每一个 Entity 其实就是渲染指令的独立单元,其中包含绘制特定元素的所有必要信息(编码位置、旋转、缩放、content object),此时还不能直接作用于 GPU
-
HAL(Hardware Abstraction Layer) 则为底层图形硬件提供了统一的接口,抽象了不同图形 API 的细节,该层确保了 Impeller 的跨平台能力,它将高级渲染命令转换为低级 GPU 指令,充当 Impeller 渲染逻辑和设备图形硬件之间的桥梁。
所以 HAL 它包装了各种图形 API,以提供通用的设备作业调度接口、一致的资源管理语义和统一的着色器创作体验,而对于 Impeller , Entities (2D renderer) 和 Scene (3D renderer) 都是直接通过 HAL 对接,甚至可以认为,Impeller 的 HAL 抽象并统一了 Metal 和 Vulkan 的常见用法和相似结构。
Unity 现在也有在 C# 直接向用户公开其 HAL 版本,称为 “Scriptable Render Pipeline” ,并提供了两个基于该 API 构建的默认渲染器 “Universal RP” / “High Definition RP” 用于服务不同的场景,所以 Unity 开发可以从使用这些渲染器去进行修改或扩展一些特定渲染需求。
而在 Flutter 的设计上,Flutter GPU 会作为 Flutter SDK 的一部分,并以 flutter_gpu
的 Dart 包的形式提供使用。
当然,Flutter GPU 由 Impeller 支持,但重要的是要记住它不是 Impeller ,Impeller 的 HAL 是私有内部代码与 Flutter GPU 的要求非常不同, Impeller 的私有 HAL 和 Flutter GPU 的公共 API 设计之间是存在一定差异化实现,而前面的流程,如 Scene (3D renderer) ,也可以被调整为基于 Flutter GPU 的全新模式实现。
而通过 Flutter GPU,如曾经的 Scene (3D renderer) 支持,也可以被调整为基于 Flutter GPU 的全新模式实现,因为 Flutter GPU 的 API 允许完全控制渲染通道附件、顶点阶段和数据上传到 GPU。这种灵活性对于创建复杂的渲染解决方案(从 2D 角色动画到复杂的 3D 场景)至关重要。
Flutter GPU 支持的自定义 2D 渲染器的一个很好的用例:依赖于骨骼网格变形的 2D 角色动画格式。
Spine 2D 就是一个很好的例子,骨骼网格解决方案通常具有动画剪辑,可以按层次结构操纵骨骼的平移、旋转和缩放属性,并且每个顶点都有几个相关的“bone weights”,这些权重决定了哪些骨骼应该影响顶点以及影响程度如何。
使用像 drawVertices
这样的 Canvas 解决方案,需要在 CPU 上对每个顶点应用骨骼权重变换,而 使用 Flutter GPU,骨骼变换可以用统一数组或纹理采样器的形式发送到顶点着色器,从而允许根据骨架状态和每个顶点的 “bone weights” 在 GPU 上并行计算每个顶点的最终位置。
使用 Flutter GPU
首先你需要在最新的 main channel 分支,然后通过 flutter pub add flutter_gpu --sdk=flutter
将 flutter_gpu SDK 包添加到你的 pubspec。
为了使用 Flutter GPU 渲染内容,你会需要编写一些 GLSL 着色器,Flutter GPU 的着色器与 Flutter 的 fragment shader 功能所使用的着色器具有不同的语义,特别是在统一绑定方面,还需要定义一个顶点(vertex)着色器来与 fragment shader 一起使用,然后配合 gpu.ShaderLibrary
等 API 就可以直接实现 Flutter GPU 渲染。
当然,本篇不会介绍详细的 API 使用 ,这里只是单纯做一个简单的介绍,目前 Flutter GPU 进行光栅化的简单流程如下:
-
获取 GPUContext。
-
GpuContext.createCommandBuffer
创建一个CommandBuffer
-
CommandBuffer.createRenderPass
创建一个RenderPass
-
使用各种方法设置状态/管道并绑定资源
RenderPass
-
附加绘图命令
RenderPass.draw
-
CommandBuffer
使用CommandBuffer.submit
(异步)提交绘制,所有RenderPass
会按照其创建顺序进行编码
·····import 'package:flutter_gpu/gpu.dart' as gpu;ByteData float32(List<double> values) { return Float32List.fromList(values).buffer.asByteData();}ByteData float32Mat(Matrix4 matrix) { return Float32List.fromList(matrix.storage).buffer.asByteData();}class TrianglePainter extends CustomPainter { TrianglePainter(this.time, this.seedX, this.seedY); double time; double seedX; double seedY; @override void paint(Canvas canvas, Size size) { final gpu.Texture? renderTexture = gpu.gpuContext.createTexture( gpu.StorageMode.devicePrivate, 300, 300, enableRenderTargetUsage: true, enableShaderReadUsage: true, coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture); if (renderTexture == null) { return; } final gpu.Texture? depthTexture = gpu.gpuContext.createTexture( gpu.StorageMode.deviceTransient, 300, 300, format: gpu.gpuContext.defaultDepthStencilFormat, enableRenderTargetUsage: true, coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture); if (depthTexture == null) { return; } final commandBuffer = gpu.gpuContext.createCommandBuffer(); final renderTarget = gpu.RenderTarget.singleColor( gpu.ColorAttachment(texture: renderTexture), depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthTexture), ); final encoder = commandBuffer.createRenderPass(renderTarget); final library = gpu.ShaderLibrary.fromAsset('assets/TestLibrary.shaderbundle')!; final vertex = library['UnlitVertex']!; final fragment = library['UnlitFragment']!; final pipeline = gpu.gpuContext.createRenderPipeline(vertex, fragment); encoder.bindPipeline(pipeline); encoder.setColorBlendEnable(true); encoder.setColorBlendEquation(gpu.ColorBlendEquation( colorBlendOperation: gpu.BlendOperation.add, sourceColorBlendFactor: gpu.BlendFactor.one, destinationColorBlendFactor: gpu.BlendFactor.oneMinusSourceAlpha, alphaBlendOperation: gpu.BlendOperation.add, sourceAlphaBlendFactor: gpu.BlendFactor.one, destinationAlphaBlendFactor: gpu.BlendFactor.oneMinusSourceAlpha)); final transients = gpu.HostBuffer(); final vertices = transients.emplace(float32(<double>[ -0.5, -0.5, 0, 0.5, 0.5, -0.5, ])); final color = transients.emplace(float32(<double>[0, 1, 0, 1])); final mvp = transients.emplace(float32Mat(Matrix4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0.5, 1, ) * Matrix4.rotationX(time) * Matrix4.rotationY(time * seedX) * Matrix4.rotationZ(time * seedY))); encoder.bindVertexBuffer(vertices, 3); final mvpSlot = pipeline.vertexShader.getUniformSlot('mvp')!; final colorSlot = pipeline.vertexShader.getUniformSlot('color')!; encoder.bindUniform(mvpSlot, mvp); encoder.bindUniform(colorSlot, color); encoder.draw(); commandBuffer.submit(); final image = renderTexture.asImage(); canvas.drawImage(image, Offset(-renderTexture.width / 2, 0), Paint()); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; }}class TrianglePage extends StatefulWidget { const TrianglePage({super.key}); @override State<TrianglePage> createState() => _TrianglePageState();}class _TrianglePageState extends State<TrianglePage> { Ticker? tick; double time = 0; double deltaSeconds = 0; double seedX = -0.512511498387847167; double seedY = 0.521295573094847167; @override void initState() { tick = Ticker( (elapsed) { setState(() { double previousTime = time; time = elapsed.inMilliseconds / 1000.0; deltaSeconds = previousTime > 0 ? time - previousTime : 0; }); }, ); tick!.start(); super.initState(); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Slider( value: seedX, max: 1, min: -1, onChanged: (value) => {setState(() => seedX = value)}), Slider( value: seedY, max: 1, min: -1, onChanged: (value) => {setState(() => seedY = value)}), CustomPaint( painter: TrianglePainter(time, seedX, seedY), ), ], ); }}
GpuContext 是分配所有 GPU 资源并调度 GPU 的存在,而 GpuContext 仅有启用 Impeller 时才能访问。
DeviceBuffer 和 Texture 就是 GPU 拥有的资源,可以通过 GPUContext 创建获取,如 createDeviceBuffer
和 createTexture
:
- DeviceBuffer 简单理解就是在 GPU 上分配的简单字节串,主要用于存储几何数据(索引和顶点属性)以及统一数据
- Texture 是一个特殊的设备缓冲区
CommandBuffer 用于对 GPU 上的异步执行进行排队和调度工作。
RenderPass 是 GPU 上渲染工作的顶层单元。
RenderPipeline 提供增量更改绘制所有状态以及附加绘制调用的方法如 RenderPass.draw()
可以想象,通过 Flutter GPU,Flutter 开发者可以更简单地对 GPU 进行更精细的控制,通过与 HAL 直接通信,创建 GPU 资源并记录 GPU 命令,从而最大限度的发挥 Flutter 的渲染能力。
另外,对于 3D 支持的 Flutter Scene , 可以通过使用 native-assets 来设置 Flutter Scene 的 3D 模型自动导入,通过导入编译模型 .model 之后,就可以通过 Dart 实现一些 3D 的渲染。
import 'dart:math';import 'package:flutter/material.dart';import 'package:flutter_scene/camera.dart';import 'package:flutter_scene/node.dart';import 'package:flutter_scene/scene.dart';import 'package:vector_math/vector_math.dart';void main() { runApp(const MyApp());}class MyApp extends StatefulWidget { const MyApp({super.key}); @override MyAppState createState() => MyAppState();}class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { double elapsedSeconds = 0; Scene scene = Scene(); @override void initState() { createTicker((elapsed) { setState(() { elapsedSeconds = elapsed.inMilliseconds.toDouble() / 1000; }); }).start(); Node.fromAsset('build/models/DamagedHelmet.model').then((model) { model.name = 'Helmet'; scene.add(model); }); super.initState(); } @override Widget build(BuildContext context) { final painter = ScenePainter( scene: scene, camera: PerspectiveCamera( position: Vector3(sin(elapsedSeconds) * 3, 2, cos(elapsedSeconds) * 3), target: Vector3(0, 0, 0), ), ); return MaterialApp( title: 'My 3D app', home: CustomPaint(painter: painter), ); }}class ScenePainter extends CustomPainter { ScenePainter({required this.scene, required this.camera}); Scene scene; Camera camera; @override void paint(Canvas canvas, Size size) { scene.render(camera, canvas, viewport: Offset.zero & size); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true;}
目前 Flutter GPU 和 Flutter Scene 的支持还十分有限,但是借助 Impeller ,Flutter 开启了新的可能,可以说是,Flutter 团队完全掌控了渲染堆栈,在除了自定义更丰富的 2D 场景之外,也为 Flutter 开启了 3D 游戏的可能,2023 年 Flutter Forward 大会的承诺,目前正在被落地实现。
详细 API 使用例子可以参看 :medium.com/flutter/get…
如果你对 Flutter Impeller 和其着色器感兴趣,也可以看: