Flutter 绘制集录 | Shader 让绘制无限强大 - 壹

在之前研究 opengl 时,知道 Shader 的强大,我们可以通过着色器完成很多特效。之前在 Android 中写过
《 [ - OpenGLES3.0 - ] 第三集 主线 - shader着色器与图片特效》 一文, 其中详细介绍了 OpenGLEs 的着色器。而

Flutter 本身是支持 glsl 着色器的 ,也就是说,你可以在全平台使用着色器 shader 实现特效。


1. 从一个颜色开始说起

先从最简单的一个颜色开始认识 shader 的使用,如下所示在屏幕中展示单一颜色。在项目中创建一个 shaders 文件夹,并创建 color.frag 着色器文件,其中输出一个 vec4 四维向量 fragColor 表示颜色的 rgba 。在 main 函数中为 fragColor 赋值即可:
注意 : 需要在 pubspec.yaml 中的 flutter/shaders 节点下配置着色器文件:

dart 复制代码
---->[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 的使用分为三步:

  • [1]. 加载 frag 着色器文件,得到片元程序 FragmentProgram
  • [2]. 通过 FragmentProgram 获取着色器 FragmentShader,并设置给 Paint 画笔。
  • [3]. 使用画笔绘制内容。

下面是视图组件,在初始化状态时通过 _loadShader 加载着色器,并通过 CustomPaint 展示绘制内容。为画板传入 shader 对象:

dart 复制代码
---->[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 中的主色逻辑就会应用到画笔上:

dart 复制代码
---->[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;
}

2. 图片纹理贴图

下面通过展示一张图片,来介绍一下如何通过 shader 展示图片。如下的着色器文件中,定义了两个参数

  • vec2 的二维向量 uSize 表示图片尺寸。
  • sampler2D 类型的 uTexture 表示图片采样数据。

其实本质上就是通过 texture 根据图片数据在纹理坐标上拾取颜色,将颜色值赋值给 fragColor 输出:

dart 复制代码
---->[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 设置。如下所示:

  • 0 表示 uniform 入参的第一个维度,也就是尺寸的宽度;1 表示高度。
  • setImageSampler 方法用于设置着色器的图片资源,入参是 ui.Image 图片。

在状态类中需要加载图片资源着色器资源 ,通过 ShaderPainter 的构造传入这样一张贴图就可以附着在着色器上了。

dart 复制代码
---->[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;
  }
}

3. 图片纹理贴图的特效

可能有人会问,这有什么用? Canvas 不是一样可以绘制图片吗? 着色器的强大之处在于可以 操作像素 , 从而完成复杂的特效。如下所示,当我们得到颜色的像素之后,可以对像素进行运算再输出:

原图 黑白

下面的着色器会将灰度小于 0.5 的像素变成白色,灰度大于 0.5 的像素变成灰色:

dart 复制代码
#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);
}

原图 圆点马赛克

下面通过对纹理坐标的校验决定绘制颜色还是空白,从而达到圆形马赛克的效果。

c 复制代码
---->[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 的支持,可谓如虎添翼。那本篇就到这里,谢谢观看~

相关推荐
闲暇部落44 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX3 小时前
Android 分区相关介绍
android
大白要努力!4 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee4 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood4 小时前
Perfetto学习大全
android·性能优化·perfetto
旭日猎鹰5 小时前
Flutter踩坑记录(三)-- 更改入口执行文件
flutter
旭日猎鹰5 小时前
Flutter踩坑记录(一)debug运行生成的项目,不能手动点击运行
flutter
️ 邪神5 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】自定义View
flutter·ios·鸿蒙·reactnative·anroid
Dnelic-7 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen9 小时前
MTK Android12 user版本MtkLogger
android·framework