编写和使用片段着色器
自定义着色器可用于提供超出 Flutter SDK 提供的功能的丰富图形效果。着色器是用一种类似于 Dart 的小型语言(称为 GLSL)编写的程序,并在用户的 GPU 上执行。
自定义着色器通过在 pubspec.yaml
文件中列出它们来添加到 Flutter 项目中,并使用 FragmentProgram
API 获取。
在应用程序中添加着色器
#以 .frag
扩展名结尾的 GLSL 文件形式的着色器必须在项目的 pubspec.yaml
文件的 shaders
部分中声明。Flutter 命令行工具将着色器编译成其适当的后端格式,并生成其必要的运行时元数据。然后,编译后的着色器就像资源一样包含在应用程序中。
flutter:
shaders:
- shaders/myshader.frag
在调试模式下运行时,对着色器程序的更改会触发重新编译并在热重载或热重启期间更新着色器。
来自包的着色器通过在着色器程序名称前添加 packages/$pkgname
前缀来添加到项目中(其中 $pkgname
是包的名称)。
在运行时加载着色器
#要在运行时将着色器加载到 FragmentProgram
对象中,请使用 FragmentProgram.fromAsset
构造函数。资源的名称与 pubspec.yaml
文件中给出的着色器路径相同。
void loadMyShader() async {
var program = await FragmentProgram.fromAsset('shaders/myshader.frag');
}
FragmentProgram
对象可用于创建一个或多个 FragmentShader
实例。FragmentShader
对象表示片段程序以及一组特定的uniform(配置参数)。可用的 uniform 取决于着色器定义的方式。
void updateShader(Canvas canvas, Rect rect, FragmentProgram program) {
var shader = program.fragmentShader();
shader.setFloat(0, 42.0);
canvas.drawRect(rect, Paint()..shader = shader);
}
Canvas API
#片段着色器可以通过设置 Paint.shader
与大多数 Canvas API 一起使用。例如,当使用 Canvas.drawRect
时,会为矩形内的所有片段计算着色器。对于像 Canvas.drawPath
这样的具有描边路径的 API,会为描边线内的所有片段计算着色器。某些 API(例如 Canvas.drawImage
)会忽略着色器的值。
void paint(Canvas canvas, Size size, FragmentShader shader) {
// Draws a rectangle with the shader used as a color source.
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..shader = shader,
);
// Draws a stroked rectangle with the shader only applied to the fragments
// that lie within the stroke.
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()
..style = PaintingStyle.stroke
..shader = shader,
)
}
编写着色器
#片段着色器被编写为 GLSL 源文件。按照惯例,这些文件使用 .frag
扩展名。(Flutter 不支持顶点着色器,顶点着色器将使用 .vert
扩展名。)
支持从 460 到 100 的任何 GLSL 版本,但某些可用功能受到限制。本文档中的其余示例使用版本 460 core
。
与 Flutter 一起使用时,着色器受以下限制
- 不支持 UBO 和 SSBO
sampler2D
是唯一支持的采样器类型- 仅支持
texture
的两个参数版本(采样器和 uv) - 无法声明其他 varying 输入
- 面向 Skia 时,所有精度提示都会被忽略
- 不支持无符号整数和布尔值
Uniform
#可以通过在 GLSL 着色器源代码中定义 uniform
值,然后为每个片段着色器实例在 Dart 中设置这些值来配置片段程序。
使用 GLSL 类型 float
、vec2
、vec3
和 vec4
的浮点 uniform 使用 FragmentShader.setFloat
方法设置。使用 sampler2D
类型的 GLSL 采样器值使用 FragmentShader.setImageSampler
方法设置。
每个 uniform
值的正确索引由片段程序中定义 uniform 值的顺序确定。对于由多个浮点数组成的类型(例如 vec4
),必须对每个值调用 FragmentShader.setFloat
一次。
例如,在 GLSL 片段程序中给定以下 uniform 声明
uniform float uScale;
uniform sampler2D uTexture;
uniform vec2 uMagnitude;
uniform vec4 uColor;
初始化这些 uniform
值的相应 Dart 代码如下所示
void updateShader(FragmentShader shader, Color color, Image image) {
shader.setFloat(0, 23); // uScale
shader.setFloat(1, 114); // uMagnitude x
shader.setFloat(2, 83); // uMagnitude y
// Convert color to premultiplied opacity.
shader.setFloat(3, color.red / 255 * color.opacity); // uColor r
shader.setFloat(4, color.green / 255 * color.opacity); // uColor g
shader.setFloat(5, color.blue / 255 * color.opacity); // uColor b
shader.setFloat(6, color.opacity); // uColor a
// Initialize sampler uniform.
shader.setImageSampler(0, image);
}
请注意,与 FragmentShader.setFloat
一起使用的索引不计算 sampler2D
uniform。此 uniform 使用 FragmentShader.setImageSampler
单独设置,索引从 0 重新开始。
任何未初始化的浮点 uniform 将默认为 0.0
。
当前位置
#着色器可以访问一个 varying
值,该值包含正在计算的特定片段的局部坐标。使用此功能计算取决于当前位置的效果,可以通过导入 flutter/runtime_effect.glsl
库并调用 FlutterFragCoord
函数来访问。例如
#include <flutter/runtime_effect.glsl>
void main() {
vec2 currentPos = FlutterFragCoord().xy;
}
FlutterFragCoord
返回的值与 gl_FragCoord
不同。gl_FragCoord
提供屏幕空间坐标,通常应避免使用,以确保着色器在不同后端之间保持一致。当面向 Skia 后端时,对 gl_FragCoord
的调用会被重写以访问局部坐标,但在 Impeller 中无法进行此重写。
颜色
#没有用于颜色的内置数据类型。相反,它们通常表示为 vec4
,每个分量对应于 RGBA 颜色通道之一。
单个输出 fragColor
预期颜色值已归一化为 0.0
到 1.0
的范围,并且具有预乘 alpha。这与使用 0-255
值编码且具有非预乘 alpha 的典型 Flutter 颜色不同。
采样器
#采样器提供对 dart:ui
Image
对象的访问。此图像可以从解码的图像或使用 Scene.toImageSync
或 Picture.toImageSync
从应用程序的一部分获取。
#include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec2 uv = FlutterFragCoord().xy / uSize;
fragColor = texture(uTexture, uv);
}
默认情况下,图像使用 TileMode.clamp
来确定 [0, 1]
范围之外的值如何表现。不支持自定义平铺模式,需要在着色器中模拟。
性能注意事项
#当面向 Skia 后端时,加载着色器可能会很昂贵,因为它必须在运行时编译成适当的平台特定着色器。如果您打算在动画过程中使用一个或多个着色器,请考虑在开始动画之前预缓存片段程序对象。
您可以在帧之间重用 FragmentShader
对象;这比为每一帧创建一个新的 FragmentShader
更有效。
有关编写高性能着色器的更详细指南,请查看 GitHub 上的 编写高效的着色器。
其他资源
#有关更多信息,以下是一些资源。
- 着色器宝典,由 Patricio Gonzalez Vivo 和 Jen Lowe 编写
- Shader toy,一个协作的着色器游乐场
simple_shader
,一个简单的 Flutter 片段着色器示例项目flutter_shaders
,一个简化在 Flutter 中使用片段着色器的包
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-09-26。 查看源代码 或 报告问题。