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

相关推荐
太空漫步112 小时前
android社畜模拟器
android
神秘_博士2 小时前
自制AirTag,支持安卓/鸿蒙/PC/Home Assistant,无需拥有iPhone
arm开发·python·物联网·flutter·docker·gitee
陈皮话梅糖@4 小时前
Flutter 网络请求与数据处理:从基础到单例封装
flutter·网络请求
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android