Flutter自定义渲染插件shader的使用
Flutter自定义渲染插件shader的使用
shader
shader
管理将您的 GLSL shaders 编译为 SPIR-V 字节码和 Dart 代码。
快速开始
# 安装命令行工具
dart pub global activate shader
# 编译项目中的所有 glsl 文件
shader --use-remote --to-dart
# 查看所有功能
shader --help
使用方法
编译到Dart
最简单的使用着色器的方法如下:
shader --use-remote --to-dart
它会扫描项目中的 *.glsl
文件并使用托管的云服务进行编译。Flutter 在运行时需要 SPIR-V 字节码。
一个非常简单的着色器 red-shader.glsl
可以是这样的:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
这个着色器没有 uniforms(输入参数),并且将每个像素绘制为红色。
编译器创建了一个 Dart 文件 red_shader_sprv.dart
,其中包含一个函数 Future<FragmentProgram> redShaderFragmentProgram()
,在运行时初始化着色器。
我们可以利用 FutureBuilder
来加载它:
import 'dart:ui';
import 'package:flutter/material.dart';
/// 导入由 CLI 工具生成的文件
import 'package:flutter_app/shader/red_shader_sprv.dart';
class RedShaderWidget extends StatelessWidget {
const RedShaderWidget({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return FutureBuilder<FragmentProgram>(
/// 使用生成的加载器函数
future: redShaderFragmentProgram(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// 着色器正在加载
return const CircularProgressIndicator(); // 替换为实际的加载组件
}
/// 着色器已准备好使用
return CustomPaint(
painter: RedShaderPainter(snapshot.data!),
);
}),
);
}
}
/// 自定义画笔类,使用着色器
class RedShaderPainter extends CustomPainter {
RedShaderPainter(this.fragmentProgram);
final FragmentProgram fragmentProgram;
[@override](/user/override)
void paint(Canvas canvas, Size size) {
/// 创建带有着色器的画笔
final paint = Paint()..shader = fragmentProgram.shader();
/// 使用带有着色器的画笔绘制矩形
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
[@override](/user/override)
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is RedShaderPainter &&
oldDelegate.fragmentProgram == fragmentProgram) {
/// 当画笔具有相同的属性集时不重绘
return false;
}
return true;
}
}
Uniforms 的使用
Uniforms 是 GLSL 中的术语,表示输入参数。Uniforms 可以在每一帧中更改,但在单个帧中对于每个像素来说是常量。
给定以下 GLSL 文件:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
// 定义 uniforms:
layout(location = 0) uniform vec3 color1;
layout(location = 1) uniform vec3 color2;
layout(location = 2) uniform float someValue;
layout(location = 3) uniform vec2 size;
void main() {
// ...
}
这可以在 FragmentProgram
的 shader()
方法中处理:
[@override](/user/override)
void paint(Canvas canvas, Size size) {
/// 输入参数
Color color1 = Colors.blue;
Color color2 = Colors.green;
double someValue = 0.5;
/// 创建带有着色器的画笔
final paint = Paint()
..shader = fragmentProgram.shader(
/// 指定输入参数 (uniforms)
floatUniforms: Float32List.fromList([
/// color1 需要 3 个浮点数,并将映射到 `vec3`
color1.red / 255.0,
color1.green / 255.0,
color1.blue / 255.0,
/// color2 同样需要 3 个浮点数,并将映射到 `vec3`
color2.red / 255.0,
color2.green / 255.0,
color2.blue / 255.0,
/// someValue 需要 1 个浮点数,并将映射到 `float`
someValue,
/// size 需要 2 个浮点数,并将映射到 `vec2`
size.width,
size.height,
]));
/// 使用带有着色器的画笔绘制矩形
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
使用 sampler uniform
图像纹理可以通过 sampler2d
uniform 访问:
layout(location = 0) uniform sampler2D image;
为此,您需要创建一个 ImageShader
:
final asset = await rootBundle.load("assets/image.jpg");
final image = await decodeImageFromList(asset.buffer.asUint8List());
/// 创建 ImageShader 提供 GLSL sampler
final ImageShader imageShader = ImageShader(
image,
// 指定 x 和 y 维度的图像重复处理方式
TileMode.repeated,
TileMode.repeated,
// 变换矩阵(单位矩阵 = 无变换)
Matrix4.identity().storage,
);
该 ImageShader
可以作为 samplerUniform
传递给 FragmentProgram
的 shader()
方法:
final paint = Paint()
..shader = fragmentProgram.shader(
samplerUniforms: [
imageShader,
],
);
使用本地编译器
如果您不想依赖托管的云服务或云服务不可用,可以使用本地编译器。
您可以从 这里 下载编译器。
--use-local
选项接受指向编译器二进制文件的路径:
shader --use-local $HOME/sdk/shaderc --to-dart
改进开发周期
为了更快地迭代并使用热重载,可以使用 --watch
标志:
shader --use-remote --to-dart --watch
shader --use-local $HOME/sdk/shaderc --to-dart --watch
编写着色器
Flutter 中的约束条件
着色器目前不支持 Flutter Web,但 Flutter 引擎开发人员有一个 计划 来启用它。
GLSL 语言功能的能力也受到限制。请查看 SPIR-V 转换器规范。
此包将 GLSL 代码编译为 SPIR-V 代码,在运行时 SPIR-V 转换器将其转换为本机 API(例如 OpenGL、Vulkan)。因此,可能 shader
编译成功了,但在运行时失败。
学习 GLSL
有许多资源可以学习 GLSL:
示例代码
红色着色器
red-shader.glsl
非常简单,将所有像素变为红色:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
可以通过 shader
执行程序编译:
shader --use-remote --to-dart
编译后应用程序可以使用它:
import 'dart:ui';
import 'package:flutter/material.dart';
/// 导入由 CLI 工具生成的文件
import 'package:flutter_app/shader/red_shader_sprv.dart';
void main() {
runApp(const MaterialApp(home: Page()));
}
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<FragmentProgram>(
/// 使用生成的加载器函数
future: redShaderFragmentProgram(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// 着色器正在加载
return const CircularProgressIndicator();
}
/// 着色器已准备好使用
return SizedBox.expand(
child: CustomPaint(
painter: RedShaderPainter(snapshot.data!),
),
);
})),
);
}
}
/// 自定义画笔类,使用着色器
class RedShaderPainter extends CustomPainter {
RedShaderPainter(this.fragmentProgram);
final FragmentProgram fragmentProgram;
[@override](/user/override)
void paint(Canvas canvas, Size size) {
/// 创建带有着色器的画笔
final paint = Paint()..shader = fragmentProgram.shader();
/// 使用带有着色器的画笔绘制矩形
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
[@override](/user/override)
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is RedShaderPainter &&
oldDelegate.fragmentProgram == fragmentProgram) {
/// 当画笔具有相同的属性集时不重绘
return false;
}
return true;
}
}
颜色着色器
color-shader.glsl
使用输入参数(uniforms)并将所有像素变为该颜色:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) uniform vec3 color;
void main() {
fragColor = vec4(color.rgb, 1.0);
}
可以通过 shader
执行程序编译:
shader --use-remote --to-dart
编译后应用程序可以使用它:
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
/// 导入由 CLI 工具生成的文件
import 'package:flutter_app/shader/color_shader_sprv.dart';
void main() {
runApp(const MaterialApp(home: Page()));
}
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<FragmentProgram>(
/// 使用生成的加载器函数
future: colorShaderFragmentProgram(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// 着色器正在加载
return const CircularProgressIndicator();
}
/// 着色器已准备好使用
return TweenAnimationBuilder<Color?>(
/// Flutter 动画:在 10 秒内渐变颜色
tween: ColorTween(begin: Colors.green, end: Colors.blue),
duration: const Duration(seconds: 10),
builder: (context, color, _) {
return SizedBox.expand(
child: CustomPaint(
/// 将颜色作为输入参数传递到着色器中
painter: RedShaderPainter(snapshot.data!, color!),
),
);
},
);
})),
);
}
}
/// 自定义画笔类,使用着色器
class RedShaderPainter extends CustomPainter {
RedShaderPainter(this.fragmentProgram, this.color);
final FragmentProgram fragmentProgram;
final Color color;
[@override](/user/override)
void paint(Canvas canvas, Size size) {
/// 创建带有着色器的画笔
final paint = Paint()
..shader = fragmentProgram.shader(
/// 指定输入参数 (uniforms)
floatUniforms: Float32List.fromList([
// 设置第一个浮点数为相对红色
color.red / 255.0,
// 设置第二个浮点数为相对绿色
color.green / 255.0,
// 设置第三个浮点数为相对蓝色
color.blue / 255.0,
]));
/// 使用带有着色器的画笔绘制矩形
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
[@override](/user/override)
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is RedShaderPainter &&
oldDelegate.fragmentProgram == fragmentProgram &&
oldDelegate.color == color) {
/// 当画笔具有相同的属性集时不重绘
return false;
}
return true;
}
}
图像缩放着色器
image-scale-shader.glsl
使用图像纹理作为采样器均匀变量以及一个浮点均匀变量:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) uniform float scale;
layout(location = 1) uniform sampler2D image;
void main() {
vec2 coords = (0.0015 / scale) * (gl_FragCoord.xy);
vec4 textureColor = texture(image, coords);
fragColor = textureColor;
}
可以通过 shader
执行程序编译:
shader --use-remote --to-dart
编译后应用程序可以使用它:
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app/shader/image_scale_shader_sprv.dart';
void main() {
runApp(const MaterialApp(home: Page()));
}
/// 将多个内容组合在一起
class PainterNeeds {
final ImageShader imageShader;
final FragmentProgram fragmentProgram;
PainterNeeds(this.imageShader, this.fragmentProgram);
}
/// 加载 JPEG 图像和 [FragmentProgram]
Future<PainterNeeds> loadPainterNeeds() async {
final asset = await rootBundle.load("assets/image.jpg");
final image = await decodeImageFromList(asset.buffer.asUint8List());
/// 创建 ImageShader 提供 GLSL sampler
final ImageShader imageShader = ImageShader(
image,
// 指定 x 和 y 维度的图像重复处理方式
TileMode.repeated,
TileMode.repeated,
// 变换矩阵(单位矩阵 = 无变换)
Matrix4.identity().storage,
);
return PainterNeeds(imageShader, await imageScaleShaderFragmentProgram());
}
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<PainterNeeds>(
/// 使用生成的加载器函数
future: loadPainterNeeds(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// 着色器正在加载
return const CircularProgressIndicator();
}
/// 着色器已准备好使用
return SizedBox.expand(
child: CustomPaint(
painter: ImageScaleShaderPainter(snapshot.data!),
),
);
})),
);
}
}
/// 自定义画笔类,使用着色器
class ImageScaleShaderPainter extends CustomPainter {
ImageScaleShaderPainter(this.painterNeeds);
final PainterNeeds painterNeeds;
[@override](/user/override)
void paint(Canvas canvas, Size size) {
/// 创建带有着色器的画笔
final paint = Paint()
..shader = painterNeeds.fragmentProgram.shader(
floatUniforms: Float32List.fromList([
// scale 均匀变量
0.1,
]),
samplerUniforms: [
painterNeeds.imageShader,
],
);
/// 使用带有着色器的画笔绘制矩形
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
[@override](/user/override)
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is ImageScaleShaderPainter &&
oldDelegate.painterNeeds == painterNeeds) {
/// 当画笔具有相同的属性集时不重绘
return false;
}
return true;
}
}
更多关于Flutter自定义渲染插件shader的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter自定义渲染插件shader的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,自定义渲染插件通常涉及到使用平台通道(Platform Channels)与原生代码(iOS/Android)进行交互,以及利用Flutter的渲染引擎来创建自定义的渲染效果。对于使用Shader来自定义渲染效果,通常需要在Dart代码中创建自定义的CustomPainter
,并在其中使用ui.Shader
来定义渲染逻辑。
下面是一个简单的示例,展示了如何在Flutter中使用自定义Shader来实现一个基本的渐变效果。这个示例不会涉及到平台通道,而是直接在Dart代码中实现。
Dart 代码
首先,我们定义一个自定义的CustomPainter
,在其中使用ui.Gradient
和ui.LinearGradient
来创建一个线性渐变效果,并将其应用到Canvas
上。
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class GradientShaderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..shader = ui.LinearGradient(
colors: [Colors.blue, Colors.red],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Gradient Shader Example'),
),
body: CustomPaint(
painter: GradientShaderPainter(),
size: Size.infinite,
),
),
);
}
}
解释
-
GradientShaderPainter
类:- 继承自
CustomPainter
。 - 在
paint
方法中,我们创建了一个Paint
对象,并使用ui.LinearGradient
为其设置了一个线性渐变Shader。 ui.LinearGradient
接受一个颜色列表colors
,以及渐变的起始和结束点begin
和end
。- 使用
canvas.drawRect
方法将渐变效果绘制到整个画布上。
- 继承自
-
shouldRepaint
方法:- 返回
false
,因为我们不需要在Widget状态改变时重绘这个自定义Painter。
- 返回
-
MyApp
类:- 创建一个简单的Flutter应用,包含一个
Scaffold
和一个CustomPaint
组件。 CustomPaint
组件使用我们自定义的GradientShaderPainter
来绘制内容。
- 创建一个简单的Flutter应用,包含一个
注意事项
- 在这个例子中,我们直接在Dart代码中使用了Flutter提供的
ui.Shader
。如果你需要更复杂的自定义渲染逻辑,可能需要结合原生平台代码(如使用OpenGL ES在Android或iOS上绘制),这通常涉及到平台通道的使用。 - 对于更高级的自定义渲染需求,例如3D渲染或复杂的图形处理,可以考虑使用Flutter的
impeller
渲染引擎(如果可用)或者与第三方图形库集成。
这个示例展示了如何在Flutter中使用自定义Shader进行基本的渲染。对于更复杂的需求,你可能需要深入研究Flutter的渲染引擎和平台通道的使用。