Flutter WebGL支持插件flutter_web_gl的使用

Flutter WebGL支持插件flutter_web_gl的使用

FlutterWebGL Pub Version

目前在Flutter中高效渲染3D对象的方法尚未实现。同时,直接从Dart访问和编程GPU也未被支持。该插件旨在解决这一问题。

最初,@kentcb和我为Aaronia公司的一个项目共同开发了一个用于OpenGL的封装。尽管我们在那个项目上取得了不错的进展,但考虑到Apple已废弃了OpenGL,我在想这是否仍是正确的方向。

幸运的是,Simon让我意识到Google有一个名为Angle的项目,该项目为不支持它们的所有平台实现了OpenGL ES API。

此项目将解决两个目标:

  • 提供一个低级Dart FFI层,通过OpenGL ES 3.0 API将渲染内容输出到Flutter Texture小部件。
  • 实现一个Dart版本的WebGL接口,覆盖了OpenGL ES的大部分功能,但无需处理FFI本地类型和适当的错误处理。

此外,其他包可以构建更易于使用的3D API,例如,有一个Dart版的JavaScript 3D框架three.js。

如何工作

Flutter有一个Texture小部件,它是图形内容的占位符,这些内容会被渲染到原生的Texture对象上。这通过在插件的原生部分创建一个Texture对象,并将其注册到Flutter引擎来实现。引擎会返回一个ID,然后可以在Flutter中的Texture小部件中使用它。

Flutter将显示该纹理的当前内容。如果该纹理的内容由原生代码更改,我们需要通知Flutter引擎,以便它可以更新相关的Texture小部件。

此插件使用OpenGL ES将内容渲染到这些原生纹理上。由于OpenGL的工作方式,我们可以通过Dart-FFI绑定到OpenGL ES API来访问OpenGL的“渲染缓冲区对象”(RBO),这样我们就可以用Dart编写所有渲染代码(除了着色器)。

要使用此功能,你需要执行以下操作:

  1. 调用FlutterWebGL.initOpenGL(),这将在插件的Dart和原生部分设置所需的基础设施。
  2. 使用FlutterWebGL.createTexture创建一个纹理,这将返回纹理ID以在Texture小部件中使用。
  3. 激活纹理以使用OpenGL命令访问它(此部分尚未完成,目前只支持一个纹理)。
  4. 使用OpenGL ES底层或WebGL命令渲染到该纹理。
  5. 通过调用FlutterWebGL.updateTexture通知Flutter引擎该纹理上有新内容可用。

查看示例以了解其工作原理。

目前,渲染处理故意与任何特定的小部件分离,而是保持独立。这使得即使从单独的Isolate更新纹理内容也成为可能,从而开启了更多可能性。

Roadmap

幸运的是,Aaronia支持此项目,因此我可以利用部分时间来做这个项目。

由于还有很多事情要做,并且需要了解不同平台的工具链,如果您能加入这个项目将会非常棒。

目前,我认为需要解决的任务如下:

  • 构建Angle框架
    • Windows ✅
    • Linux ❌(直到Flutter引擎在Linux上支持原生纹理为止)
    • MacOS ❌
    • iOS ❌(似乎主Angle项目没有跟上其分支metalangle,所以我们可能应该使用这个)
  • 创建一个Dart-FFI层,用于OpenGL ES 3.0 API和EGL 1.5
  • 实现原生插件部分,注册并更新纹理
    • Windows ✅(处理多个纹理的缺失)
    • Android ❌
    • iOS ❌
    • macOS ❌
    • Linux ❌(见上文)
    • Web ❌(不确定如何实现,但或许有人知道)
  • 在Dart中实现WebGL
  • 添加一种渲染文本的方式。我的当前想法是使用Flutter将文本渲染到画布上,然后将其传递给OpenGL,以便可以将其作为多边形上的纹理使用。
  • 设计小部件,使您可以使用WebGL而无需关心纹理分配和通知。
  • 编写示例和文档

示例代码

import 'dart:async';
import 'dart:ffi';

import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_gl/flutter_web_gl.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  int textureId = 0;

  [@override](/user/override)
  void initState() {
    super.initState();
    initPlatformState();
  }

  // 平台消息是异步的,所以我们初始化在一个异步方法中。
  Future<void> initPlatformState() async {
    String platformVersion;
    // 平台消息可能会失败,所以我们使用try/catch PlatformException。
    try {
      platformVersion = await FlutterWebGL.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }
    FlutterWebGL.initOpenGL();

    try {
      textureId = await FlutterWebGL.createTexture(600, 400);
    } on PlatformException {
      print("failed to get texture id");
    }

    // 如果在异步平台消息飞行时小部件从树中移除,我们想要丢弃回复而不是调用setState来更新我们的非存在的外观。
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.red,
        appBar: AppBar(
          title: const Text('插件示例应用'),
        ),
        body: Center(
          child: Column(
            children: [
              Text('运行在: $_platformVersion\n'),
              SizedBox(width: 600, height: 400, child: Texture(textureId: textureId)),
              MaterialButton(
                onPressed: draw,
                color: Colors.grey,
                child: Text('绘制'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void draw() async {
    final gl = FlutterWebGL.rawOpenGl;

    int vertexShader = gl.glCreateShader(GL_VERTEX_SHADER);
    var sourceString = Utf8.toUtf8(vertexShaderSource);
    var arrayPointer = allocate<Pointer<Int8>>();
    arrayPointer.value = Pointer.fromAddress(sourceString.address);
    gl.glShaderSource(vertexShader, 1, arrayPointer, nullptr);
    gl.glCompileShader(vertexShader);
    free(arrayPointer);
    free(sourceString);

    int fragmentShader = gl.glCreateShader(GL_FRAGMENT_SHADER);
    sourceString = Utf8.toUtf8(fragmentShaderSource);
    arrayPointer = allocate<Pointer<Int8>>();
    arrayPointer.value = Pointer.fromAddress(sourceString.address);
    gl.glShaderSource(fragmentShader, 1, arrayPointer, nullptr);
    gl.glCompileShader(fragmentShader);
    free(arrayPointer);
    free(sourceString);

    final shaderProgram = gl.glCreateProgram();
    gl.glAttachShader(shaderProgram, vertexShader);
    gl.glAttachShader(shaderProgram, fragmentShader);
    gl.glLinkProgram(shaderProgram);

    gl.glClearColor(0, 0, 1, 1);
    gl.glClear(GL_COLOR_BUFFER_BIT);

    gl.glUseProgram(shaderProgram);

    final points = [-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0];
    Pointer<Uint32> vbo = allocate();
    gl.glGenBuffers(1, vbo);
    gl.glBindBuffer(GL_ARRAY_BUFFER, vbo.value);
    gl.glBufferData(GL_ARRAY_BUFFER, 36, floatListToArrayPointer(points).cast(), GL_STATIC_DRAW);

    gl.glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, Pointer<Void>.fromAddress(0).cast());
    gl.glEnableVertexAttribArray(0);
    gl.glDrawArrays(GL_TRIANGLES, 0, 3);

    gl.glDeleteShader(vertexShader);
    gl.glDeleteShader(fragmentShader);

    await FlutterWebGL.updateTexture(textureId);
  }
}

const vertexShaderSource =
    '#version 300 es\n'
    'layout (location = 0) in vec4 aPos;\n'
    '\n'
    'void main()\n'
    '{\n'
    '    gl_Position = aPos;\n'
    '}\n';

const fragmentShaderSource =
    '#version 300 es\n'
    'precision mediump float;\n'
    'out vec4 FragColor;\n'
    '\n'
    'void main()\n'
    '{\n'
    '    FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);\n'
    '} \n';

Pointer<Float> floatListToArrayPointer(List<double> list) {
  final ptr = allocate<Float>(count: list.length);
  for (var i = 0; i < list.length; i++) {
    ptr.elementAt(i).value = list[i];
  }
  return ptr;
}

更多关于Flutter WebGL支持插件flutter_web_gl的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter WebGL支持插件flutter_web_gl的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_web_gl 是一个用于在 Flutter Web 应用中集成 WebGL 的插件。它允许你在 Flutter Web 项目中使用 WebGL 进行 3D 图形渲染。以下是如何使用 flutter_web_gl 插件的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 flutter_web_gl 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_web_gl: ^0.1.0 # 请检查最新版本

然后运行 flutter pub get 来获取依赖。

2. 导入插件

在你的 Dart 文件中导入 flutter_web_gl 插件:

import 'package:flutter_web_gl/flutter_web_gl.dart';

3. 使用 WebGL 视图

你可以使用 WebGLView 来嵌入 WebGL 内容。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:flutter_web_gl/flutter_web_gl.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebGL Example'),
        ),
        body: Center(
          child: WebGLView(
            onWebViewCreated: (WebGLController controller) {
              // 在这里初始化 WebGL 上下文并加载你的 3D 场景
              controller.initialize();
              controller.loadScene();
            },
          ),
        ),
      ),
    );
  }
}

4. 初始化 WebGL 上下文

onWebViewCreated 回调中,你可以初始化 WebGL 上下文并加载你的 3D 场景。以下是一个简单的示例:

controller.initialize();
controller.loadScene();

5. 编写 WebGL 代码

你可以在 WebGLController 中编写 WebGL 代码。例如,你可以初始化 WebGL 上下文、加载着色器、创建缓冲区等。

class WebGLController {
  void initialize() {
    // 初始化 WebGL 上下文
  }

  void loadScene() {
    // 加载 3D 场景
  }
}

6. 运行应用

最后,运行你的 Flutter Web 应用:

flutter run -d chrome
回到顶部