[Flutter翻译]Flutter & Shaders--初探

724 阅读7分钟

本文由 简悦SimpRead 转码,原文地址 wolfenrain.medium.com

对Flutter及其着色器支持的初步了解

image.png

图片来源:Gradienta on Unsplash

介绍

在过去的几年里,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);
}

这个着色器基本上是根据输入值afragColor设置为绿色。

我在新创建的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像素的绿色正方形。

image.png

Flutter应用程序的输出

现在我们知道了如何在Flutter中编译着色器并向其传递参数,我们可以用不同的着色器来捣乱,并获得一些着色器的魔力!但我不会在这里做这些。但我不会在这篇文章中这样做,我将在以后的日子里再搞一搞,可能会写一篇后续文章,介绍我的发现,以及我们可以用着色器做哪些有趣的事情!

谢谢你和我一起进行这次冒险,我希望你能学到一些东西,我知道我做到了;-)。

这个项目的源代码可以在这里找到。github.com/wolfenrain/…


www.deepl.com 翻译