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

相关推荐
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
拭心8 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王10 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
coder_pig10 小时前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
梦想平凡11 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道11 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库12 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道12 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
捡芝麻丢西瓜13 小时前
flutter自学笔记5- dart 编码规范
flutter·dart
MuYe13 小时前
Android Hook - 动态加载so库
android