做全景网站,设计企业门户网站,湖南东方红建设集团有限公司网站,聚美优品网站建设的目标Flutter 3.24 版本引入了 Flutter GPU 概念的新底层图形 API flutter_gpu #xff0c;还有 flutter_scene 的 3D 渲染支持库#xff0c;它们目前都是预览阶段#xff0c;只能在 main channel 上体验#xff0c;并且依赖 Impeller 的实现。 Flutter GPU 是 Flutter 内置的底…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 API3D也将作为重写的一部分出现。 说人话就是可以用 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 HALHardware 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 --sdkflutter 将 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 会按照其创建顺序进行编码
·····
///导入 flutter_gpu
import package:flutter_gpu/gpu.dart as gpu;ByteData float32(Listdouble 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;overridevoid paint(Canvas canvas, Size size) {/// Allocate a new renderable texture.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;}/// Create the command buffer. This will be used to submit all encoded/// commands at the end.final commandBuffer gpu.gpuContext.createCommandBuffer();/// Define a render target. This is just a collection of attachments that a/// RenderPass will write to.final renderTarget gpu.RenderTarget.singleColor(gpu.ColorAttachment(texture: renderTexture),depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthTexture),);/// Add a render pass encoder to the command buffer so that we can start/// encoding commands.final encoder commandBuffer.createRenderPass(renderTarget);/// Load a shader bundle asset.final library gpu.ShaderLibrary.fromAsset(assets/TestLibrary.shaderbundle)!;/// Create a RenderPipeline using shaders from the asset.final vertex library[UnlitVertex]!;final fragment library[UnlitFragment]!;final pipeline gpu.gpuContext.createRenderPipeline(vertex, fragment);encoder.bindPipeline(pipeline);/// (Optional) Configure blending for the first color attachment.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));/// Append quick geometry and uniforms to a host buffer that will be/// automatically uploaded to the GPU later on.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])); // rgbafinal 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)));/// Bind the vertex data. In this case, we wont bother binding an index/// buffer.encoder.bindVertexBuffer(vertices, 3);/// Bind the host buffer data we just created to the vertex shaders uniform/// slots. Although the locations are specified in the shader and are/// predictable, we can optionally fetch the uniform slots by name for/// convenience.final mvpSlot pipeline.vertexShader.getUniformSlot(mvp)!;final colorSlot pipeline.vertexShader.getUniformSlot(color)!;encoder.bindUniform(mvpSlot, mvp);encoder.bindUniform(colorSlot, color);/// And finally, we append a draw call.encoder.draw();/// Submit all of the previously encoded passes. Passes are encoded in the/// same order they were created in.commandBuffer.submit();/// Wrap the Flutter GPU texture as a ui.Image and draw it like normal!final image renderTexture.asImage();canvas.drawImage(image, Offset(-renderTexture.width / 2, 0), Paint());}overridebool shouldRepaint(covariant CustomPainter oldDelegate) {return true;}
}class TrianglePage extends StatefulWidget {const TrianglePage({super.key});overrideStateTrianglePage createState() _TrianglePageState();
}class _TrianglePageState extends StateTrianglePage {Ticker? tick;double time 0;double deltaSeconds 0;double seedX -0.512511498387847167;double seedY 0.521295573094847167;overridevoid initState() {tick Ticker((elapsed) {setState(() {double previousTime time;time elapsed.inMilliseconds / 1000.0;deltaSeconds previousTime 0 ? time - previousTime : 0;});},);tick!.start();super.initState();}overrideWidget 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 GPUFlutter 开发者可以更简单地对 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});overrideMyAppState createState() MyAppState();
}class MyAppState extends StateMyApp with SingleTickerProviderStateMixin {double elapsedSeconds 0;Scene scene Scene();overridevoid 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();}overrideWidget 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;overridevoid paint(Canvas canvas, Size size) {scene.render(camera, canvas, viewport: Offset.zero size);}overridebool shouldRepaint(covariant CustomPainter oldDelegate) true;
} 目前 Flutter GPU 和 Flutter Scene 的支持还十分有限但是借助 Impeller Flutter 开启了新的可能可以说是Flutter 团队完全掌控了渲染堆栈在除了自定义更丰富的 2D 场景之外也为 Flutter 开启了 3D 游戏的可能2023 年 Flutter Forward 大会的承诺目前正在被落地实现。 详细 API 使用例子可以参看 https://medium.com/flutter/getting-started-with-flutter-gpu-f33d497b7c11 如果你对 Flutter Impeller 和其着色器感兴趣也可以看 《快速了解 Flutter 的渲染引擎的优势》 《Flutter 里的着色器预热原理》