在之前研究 opengl 时,知道 Shader 的强大,我们可以通过着色器完成很多特效。之前在 Android 中写过 《 [ - OpenGLES3.0 - ] 第三集 主线 - shader着色器与图片特效》 一文, 其中详细介绍了 OpenGLEs 的着色器。而
Flutter 本身是支持 glsl 着色器的,也就是说,你可以在
全平台
使用着色器 shader 实现特效。
先从最简单的一个颜色开始认识 shader 的使用,如下所示在屏幕中展示单一颜色。在项目中创建一个 shaders
文件夹,并创建 color.frag
着色器文件,其中输出一个 vec4
四维向量 fragColor 表示颜色的 rgba 。在 main 函数中为 fragColor 赋值即可:
注意: 需要在 pubspec.yaml
中的 flutter/shaders 节点下配置着色器文件:
---->[shaders/color.frag]----
#version 460 core
precision mediump float;
out vec4 fragColor;
vec3 blue = vec3(5, 83, 177) / 255;
void main() {
fragColor = vec4(blue, 1);
}
shader 的使用分为三步:
FragmentProgram
。FragmentShader
,并设置给 Paint
画笔。下面是视图组件,在初始化状态时通过 _loadShader
加载着色器,并通过 CustomPaint
展示绘制内容。为画板传入 shader 对象:
---->[lib/paint/shaders/color_shader_demo.dart]----
class ColorShaderDemo extends StatefulWidget {
const ColorShaderDemo({super.key});
@override
State<ColorShaderDemo> createState() => _ColorShaderDemoState();
}
class _ColorShaderDemoState extends State<ColorShaderDemo> {
FragmentShader? shader;
@override
void initState() {
super.initState();
_loadShader();
}
@override
Widget build(BuildContext context) {
if (shader == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: CustomPaint(
size: const Size(500 * 0.8, 658 * 0.8),
painter: ShaderPainter( shader: shader! ),
),
);
}
void _loadShader() async {
String path = 'shaders/color.frag';
FragmentProgram program = await FragmentProgram.fromAsset(path);
shader = program.fragmentShader();
setState(() {});
}
}
在 ShaderPainter 中为画笔设置 shader
着色器即可,这样 color.frag
中的主色逻辑就会应用到画笔上:
---->[lib/paint/shaders/color_shader_demo.dart]----
class ShaderPainter extends CustomPainter {
ShaderPainter({required this.shader});
FragmentShader shader;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..shader = shader;
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
下面通过展示一张图片,来介绍一下如何通过 shader 展示图片。如下的着色器文件中,定义了两个参数
vec2
的二维向量 uSize 表示图片尺寸。sampler2D
类型的 uTexture 表示图片采样数据。其实本质上就是通过 texture
根据图片数据在纹理坐标上拾取颜色,将颜色值赋值给 fragColor
输出:
---->[shaders/image.frag]----
#version 460 core
precision mediump float;
#include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec2 coo = FlutterFragCoord().xy / uSize;
vec4 color = texture(uTexture, coo);
fragColor = color;
}
由于这里有两个入参,我们可以通过 shader.setFloat
设置。如下所示:
在状态类中需要加载图片资源
和着色器资源
,通过 ShaderPainter 的构造传入这样一张贴图就可以附着在着色器上了。
---->[lib/paint/shaders/image_shader_demo.dart]----
class ShaderPainter extends CustomPainter {
ShaderPainter({required this.shader, required this.image});
FragmentShader shader;
ui.Image image;
@override
void paint(Canvas canvas, Size size) {
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setImageSampler(0, image);
final paint = Paint()..shader = shader;
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
可能有人会问,这有什么用? Canvas 不是一样可以绘制图片吗? 着色器的强大之处在于可以 操作像素 , 从而完成复杂的特效。如下所示,当我们得到颜色的像素之后,可以对像素进行运算再输出:
下面的着色器会将灰度小于 0.5 的像素变成白色,灰度大于 0.5 的像素变成灰色:
#version 460 core
precision mediump float;
#include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
const float threshold = 0.5;//阈值
void main() {
vec2 coo = FlutterFragCoord().xy / uSize;
vec4 color = texture(uTexture,coo);
float r = color.r;
float g = color.g;
float b = color.b;
g = r * 0.3 + g * 0.59 + b * 0.11;
g = g <= threshold ? 0.0 : 1.0;
fragColor = vec4(g, g, g, 1.0);
}
下面通过对纹理坐标的校验决定绘制颜色还是空白,从而达到圆形马赛克的效果。
---->[shaders/mask.frag]----
#version 460 core
#include <flutter/runtime_effect.glsl>
precision mediump float;
out vec4 fragColor;
uniform vec2 uSize;
uniform sampler2D uTexture;
void main() {
float rate = uSize.x / uSize.y;
float cellX = 2.0;
float cellY = 2.0;
float rowCount = 100.0;
vec2 coo = FlutterFragCoord().xy / uSize;
vec2 sizeFmt = vec2(rowCount, rowCount / rate);
vec2 sizeMsk = vec2(cellX, cellY / rate);
vec2 posFmt = vec2(coo.x * sizeFmt.x, coo.y * sizeFmt.y);
float posMskX = floor(posFmt.x / sizeMsk.x) * sizeMsk.x;
float posMskY = floor(posFmt.y / sizeMsk.y) * sizeMsk.y;
vec2 posMsk = vec2(posMskX, posMskY) + 0.5 * sizeMsk;
bool inCircle = length(posMsk - posFmt)<cellX / 2.0;
vec4 result;
if (inCircle) {
vec2 UVMosaic = vec2(posMsk.x / sizeFmt.x, posMsk.y / sizeFmt.y);
result = texture(uTexture, UVMosaic);
} else {
result = vec4(1.0, 1.0, 1.0, 0.0);
}
fragColor = result;
}
本篇通过几个小案例让大家对 shader 着色器的强大能力有一个简单的认识。之后还会结合图片特效信息地介绍一下着色器的用法,Flutter 有了 Shader 的支持,可谓如虎添翼。那本篇就到这里,谢谢观看~