1. 为什么说 Fragment Shader 是 Flutter 3D 动画的性能救星?
如果你在 Flutter 里做过稍微复杂一点的动画,尤其是那种带点 3D 透视、翻页折叠效果的,大概率都踩过同一个坑:卡。我之前做小说阅读器的翻页效果时,在 Dart 层用 Canvas 配合矩阵变换 (Matrix4) 折腾了好久,帧率就是上不去,尤其在低端机上,翻页动作一快,那个掉帧简直让人抓狂。
后来我发现,问题出在渲染流程上。传统的 Dart + Canvas 方案,所有的几何计算、顶点变换、颜色填充,都是在 CPU 上完成的。CPU 当然很强大,但它并不擅长这种海量像素的并行计算。每帧动画,CPU 都要吭哧吭哧地算一遍所有点的位置和颜色,再交给 GPU 去画,这个瓶颈非常明显。
而 Fragment Shader(片段着色器)的出现,直接把游戏规则给改了。它允许我们写一段 GLSL 代码,这段代码会直接运行在 GPU 上。GPU 是什么?就是为并行处理成千上万个像素点而生的硬件。一个 3D 翻页效果,本质上就是对屏幕上每个像素点,根据其位置和动画进度,计算出它最终应该显示什么颜色(可能是原图某个位置的颜色,也可能是阴影、高光或者透明)。这种“对每个像素做同样计算”的任务,正是 GPU 的拿手好戏。
我实测过一个对比:用 Dart 矩阵实现的翻页,在复杂页面(比如带很多图片)上,帧率可能掉到 30 FPS 以下,有明显的卡顿感。而换成 Fragment Shader 实现后,同样的页面,帧率能稳定在 60 FPS,滑动跟手,动画丝滑。这个性能提升是数量级的,因为它把最重的计算工作从 CPU 搬到了更适合的 GPU。
所以,当你下次再想实现一个酷炫但吃性能的动画时,别只盯着 Dart 和 CustomPainter 了。想想 Fragment Shader,它可能就是让你应用从“能用”到“流畅炫酷”的关键一跃。这不仅仅是换一种写法,而是换了一种更接近图形硬件本质的思考方式。
2. 手把手:从零创建一个你的第一个 Fragment Shader 文件
光说原理不够,咱们直接上手。在 Flutter 项目里使用 Fragment Shader,第一步就是创建 .frag 文件。别被 GLSL 吓到,它没你想的那么复杂。
首先,在你的 Flutter 项目根目录下,创建一个 shaders 文件夹。这个文件夹专门用来存放我们的着色器代码。然后,在里面新建一个文件,比如叫 my_first_shader.frag。
打开这个文件,我们来写一个最简单的着色器:一个从左上角到右下角的渐变色。
// 1. 指定 GLSL 语言版本,Flutter 推荐用 460 core
#version 460 core
// 2. 引入 Flutter 运行时特效库,这是必须的,它提供了 FlutterFragCoord 等特有函数
#include <flutter/runtime_effect.glsl>
// 3. 定义从 Dart 端传入的变量(uniforms)
// 这里我们传入画布的分辨率,用于计算坐标
uniform vec2 uResolution;
// 4. 定义输出颜色。每个像素最终的颜色就由这个变量决定
out vec4 fragColor;
void main() {
// 5. 获取当前像素的坐标(基于整个 Flutter 视图)
vec2 fragCoord = FlutterFragCoord().xy;
// 6. 将坐标归一化到 [0.0, 1.0] 范围,方便计算
// fragCoord.x / uResolution.x 得到横向比例,y 同理
vec2 uv = fragCoord / uResolution;
// 7. 计算颜色:红色分量从左到右增强,绿色分量从上到下增强
// uv.x 从 0 到 1,所以红色也从 0 到 1
// (1.0 - uv.y) 是因为屏幕坐标原点在左上角,我们想让绿色从下往上增强
float red = uv.x;
float green = 1.0 - uv.y;
float blue = 0.5; // 蓝色固定为 0.5
float alpha = 1.0; // 完全不透明
// 8. 将计算结果赋值给输出变量
fragColor = vec4(red, green, blue, alpha);
}
我来拆解一下这段代码的关键点:
#version 460 core:这行必须放在最前面,告诉编译器我们用哪个版本的 GLSL。460 是目前 Flutter 支持较好的版本。#include <flutter/runtime_effect.glsl>:这是 Flutter 的“魔法头文件”。没有它,你就无法使用FlutterFragCoord()来获取当前像素在 Flutter 画布中的坐标。这是 Flutter 对标准 GLSL 的扩展。uniform:这个词你可以理解为“从外部传入的常量”。在着色器执行过程中,它的值由 Dart 代码设置,并且对于所有像素都是相同的。比如这里的uResolution,我们会在 Dart 里把屏幕宽高传进来。out vec4 fragColor:这是着色器唯一的“作业”——告诉 GPU 这个像素最终是什么颜色。vec4代表四个浮点数,分别对应 RGBA(红、绿、蓝、透明度)。FlutterFragCoord().xy:这是获取当前像素坐标的“钥匙”。注意,它的原点(0,0)在画布的左上角,x 向右增长,y 向下增长。
保存好这个文件后,别忘了在 pubspec.yaml 里声明它,否则 Flutter 打包时不会把它包含进去:
flutter:
shaders:
- shaders/my_first_shader.frag
好了,一个最简单的 Fragment Shader 就写完了。它虽然没做 3D 翻页,但它展示了最核心的流程:获取像素坐标 -> 根据坐标计算颜色 -> 输出颜色。所有复杂的特效,包括我们后面要做的翻页,都是在这个基础骨架上添加血肉。
3. 在 Dart 中加载并驱动你的 Shader
.frag 文件写好了,它就像一段被编译好的“GPU 程序”。现在我们需要在 Dart 端把它加载进来,并把动画参数“喂”给它。这个过程比想象中简单。
首先,我们需要一个 CustomPainter 来承载绘制逻辑。我把它命名为 GradientShaderPainter:
import 'dart:ui' as ui; // 注意这里,必须导入 dart:ui 来使用 FragmentShader
class GradientShaderPainter extends CustomPainter {
final ui.FragmentShader shader;
final Size size;
GradientShaderPainter(this.shader, this.size);
@override
void paint(Canvas canvas, Size canvasSize) {
// 关键步骤1:给 shader 设置参数



被折叠的 条评论
为什么被折叠?



