本文由 简悦SimpRead 转码,原文地址 wolfenrain.medium.com
对Flutter及其着色器支持的初步了解
介绍
在过去的几年里,Flutter一直在增长,不同的社区也同时围绕Flutter涌现出来。其中一个社区是Flame,一个写在Flutter之上的游戏引擎。作为Flame的核心维护者,我参与了该社区的工作,我经常回答人们提出的与Flutter中的GameDev(或没有Flame)有关的问题。通常情况下,我们可以很容易地回答这些问题,但有一个问题我一直无法给出一个满意的答案。
Flutter/Flame会有着色器支持吗? -- Flame社区
"但是Flutter已经在研究着色器支持了,不是吗",这是我听到你说的,是的,你说的完全正确。在2021年6月,我甚至在Twitter上对Flutter团队(确切地说,是Christopher Crawford)到目前为止所做的工作做了一个简单的概述。
然而,在当时,着色器的支持是无法使用的,我很遗憾地无法让它发挥作用。但是,我围绕这个问题的问题(或者说问题)仍然没有得到解答。会有支持吗?它将如何工作?它是否会被完全整合到Flutter的构建管道中?我不知道,你知道吗?
这就是为什么我决定再次审视它,并得到一个正在运行的Flutter应用程序,它使用着色器在屏幕上可视化一些东西
我从哪里开始呢?
由于我在2021年6月所做的研究,我已经找到了一些与该主题相关的 "文档",所以我决定再次通读这些文档,同时也查看一下所做的任何评论。为了分享知识,我也将在这里链接这些文件。
根据这些链接,我能够推断出一些我需要做的事情,并进行设置以使其全部运作。首先,我需要在Flutter引擎的主分支上,因为那里有最新的实验性着色器代码。我还需要准备一些可以在我的应用程序中使用的GLSL着色器,最后我需要一种方法来将GLSL着色器编译成SPIR-V二进制文件。SPIR-V是一个由Khronos集团开发的标准,是一种跨API(OpenGL、Vulkan等)的图形语言。
第一个不是很困难,因为我已经熟悉了GLSL,但如果你还不熟悉GLSL或一般的着色器,我可以强烈推荐thebookofshaders.com/ 。
Flutter引擎还附带了用于测试的some shaders,这些shaders对于深入了解现在支持哪些制服是相当有用的。
切换到主分支
通过运行以下命令,可以轻松地切换到主分支。
flutter channel master
flutter upgrade
如果您使用FVM,您也可以在您的项目目录下运行以下命令。
fvm use master
在切换之后,我决定创建我的项目,所以我运行了flutter create命令。
flutter create ./flutter_shader_test
准备好一些GLSL着色器
对于最初的设置,我决定只使用Flutter Engine repo中的simple.glsl着色器。
#version 320 es
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) uniform float a;
void main() {
fragColor = vec4(0.0, a, 0.0, 1.0);
}
这个着色器基本上是根据输入值a将fragColor设置为绿色。
我在新创建的Flutter项目中创建了一个shaders目录,并将该文件保存为simple.glsl。
编译成SPV二进制文件
遗憾的是,我没有找到任何关于如何将GLSL着色器编译成SPIR-V二进制文件的明确说明,也没有提到任何我可以使用的工具。庆幸的是,我有一些谷歌的技能和一个体面的大脑。通过结合我的这两样东西,我发现我可以使用像glslc这样的东西,它是gslangValidator的一个包装,尽管它的名字不是验证,但也可以生成,将我的GLSL着色器编译成SPIR-V二进制文件。这个工具是shaderc repo的一部分,它为Vulkan着色器的编译提供工具和库。Vulkan是一个渲染API,Skia(Flutter使用的渲染引擎)就是建立在这个基础上的。
好吧,"glslc",很棒吧?嗯,是的,也不是。因为我的下一个问题是如何安装这个工具。在对资源库进行了一番检查后,我最终找到了最新版本的工具的下载链接。github.com/google/shad… 。
于是我下载了适合我的平台(这里是macOS)的资产,解压了.tgz文件,并将所有数据存储在我项目的./third_party/shaderc目录内。
在Flutter中运行和编译着色器
安装了正确的工具,GLSL着色器准备好了,并且使用了Flutter引擎的正确通道,我终于可以开始测试新的着色器功能了。
编译我的简单着色器
所以,第一步是弄清楚如何将GLSL着色器编译成SPIR-V二进制。幸运的是,在我研究glslc的时候,我已经发现了这个工具是如何工作的,以及我必须使用哪些参数。我创建了一个assets/shaders目录,这样我就可以把编译后的着色器存放在那里。
编译着色器就像运行一样 "简单"。
./third_party/shaderc/bin/glslc
--target-env=opengl \
-fshader-stage=fragment \
-o assets/shaders/simple.sprv \
shaders/simple.glsl
让我给你快速介绍一下这个命令的不同部分。
./third_party/shaderc/bin/glslc是这个工具本身。--target-env=opengl说明我们希望它与OpenGL兼容。-fshader-stage=fragment表示我们传递的着色器是一个片段着色器,编译其他类型的着色器,如vert或vertex着色器,这个工具支持,但Flutter还不支持。-o assets/shader/simple.sprv是SPIR-V二进制的输出。shader/simple.glsl是输入文件,是我们要编译的着色器。
运行这个命令可以将我们的着色器编译成SPIR-V二进制文件,我在pubspec.yaml中把这个刚编译好的二进制文件添加到我的资产中。
...
flutter:
assets:
- assets/shaders/simple.sprv
...
使用GLSL着色器
现在我们有了一个可以在Flutter中使用的着色器,是时候写一些dart代码了
首先,我们需要建立一个FragmentProgram,它基本上是一个工厂类,可以将我们的SPIR-V二进制文件编译成一个片段程序。使用这个程序,我们就可以用不同的输入值创建不同的着色器实例。
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_shader_test/flutter_shader_test.dart';
void main() async {
// Ensure bindings are initialized otherwise we can't user rootBundle.
WidgetsFlutterBinding.ensureInitialized();
// Compile the binary into a Fragment program.
final program = await FragmentProgram.compile(
spirv: (await rootBundle.load('assets/shaders/simple.sprv')).buffer,
);
// Turn it into a shader with given inputs (floatUniforms).
final shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
runApp(FlutterShaderTest(shader: shader));
}
参数 "floatUniforms "描述了着色器的输入值,所以如果你有多个不同类型的uniforms,那么你必须按照正确的顺序将这些值作为浮点数传入。
例如,如果我们在location = 0有一个uniform float a,在location = 1有一个uniform vec2 b,那么我们的floatUniforms应该是这样的。
// a: 1
// b: [2, 3]
floatUniforms: Float32List.fromList([1, 2, 3])
在我们设置好着色器后,我们将其传递给FlutterShaderTest,它只是作为我们将要定义的CustomPainter的一个Widget类。
import 'package:flutter/material.dart';
import 'package:flutter_shader_test/shader_painter.dart';
/// Wrapper around the [ShaderPainter]. Nothing fancy here.
class FlutterShaderTest extends StatelessWidget {
const FlutterShaderTest({Key? key, required this.shader}) : super(key: key);
final Shader shader;
@override
Widget build(BuildContext context) {
return CustomPaint(painter: ShaderPainter(shader));
}
}
"ShaderPainter"是一个简单的 "CustomPainter",它设置了一个带有给定着色器的 "Paint "实例。然后它将其渲染到画布上,使着色器代码可视化。
import 'package:flutter/material.dart';
/// Renderer used for rendering and testing our shaders.
class ShaderPainter extends CustomPainter {
ShaderPainter(this.shader) : _paint = Paint()..shader = shader;
final Shader shader;
final Paint _paint;
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(const Rect.fromLTWH(0, 0, 100, 100), _paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
有了这些类的定义,我们现在可以运行我们的Flutter应用程序并看到最终结果,这应该是一个100乘100像素的绿色正方形。
Flutter应用程序的输出
现在我们知道了如何在Flutter中编译着色器并向其传递参数,我们可以用不同的着色器来捣乱,并获得一些着色器的魔力!但我不会在这里做这些。但我不会在这篇文章中这样做,我将在以后的日子里再搞一搞,可能会写一篇后续文章,介绍我的发现,以及我们可以用着色器做哪些有趣的事情!
谢谢你和我一起进行这次冒险,我希望你能学到一些东西,我知道我做到了;-)。
这个项目的源代码可以在这里找到。github.com/wolfenrain/…