- 1:为什么 shaders 运行特别快
- 2:Shader 分类
- 2.1:Vertex Shader(顶点着色器)
- 2.2:Fragment Shader(片段着色器 / 像素着色器)
- 2.3:Geometry Shader(几何着色器)(可选)
- 2.4:Tessellation Shader(细分着色器)(高级功能)
- 2.5:Compute Shader(计算着色器)
- 3:Shader 工作流程
- 4:Shader 编写语言
- 5:Shader 的作用与优势
- 6:Fragment Shader 基本结构
- 7:Fragment Shader 语法详解
- 7.1:#version 指令
- 7.2:precision mediump float;
- 7.3:in 输入变量
- 7.4:out 输出变量
- 7.5:uniform 变量
- 8:内置函数与变量
- 8.1:gl_FragCoord
- 9:main() 函数
- 10:Fragment Shader 常见代码模式
- 10.1:固定颜色
- 10.2:纹理映射
- 10.3:光照计算(Lambert Diffuse)
- 10.4:丢弃透明片段
- 11:注意事项
- 12:Flutter渲染引擎的发展
- 12.1:早期阶段:Skia 渲染引擎
- 12.2:关键改进:Shader Warm-up (预编译)
- 12.3:重大突破:Impeller 渲染引擎
- 13:Flutter渲染引擎下可编写的Shader
- 14:在Flutter项目下编写Fragment Shader
- 14.1:Flutter 支持的Uniforms
- 14.2:Flutter中获取当前位置
- 14.3:Flutter中的采样器(Samplers)
- 14.4:Flutter Skia为后端的性能问题
- 14.5:Flutter实现烟雾飘动的背景
OpenGL(Open Graphics Library)是一个跨语言、跨平台的图形渲染API,主要用于二维和三维图形的开发。它自1992年发布以来,经历了多个重大版本的演进,逐步从固定管线架构过渡到可编程管线,适应了现代图形硬件的发展。以下是OpenGL的发展历程和趋势概览:
版本 | 时间 | 特点 | 说明 |
---|---|---|---|
1.x | 1992\~1997 | 固定功能管线 | 开发者无法控制渲染流程细节,只能使用内置函数(如glBegin/glEnd )进行图形绘制。 |
2.0 | 2004 | 可编程管线引入 | 加入GLSL(OpenGL Shading Language),开发者可以自定义顶点/片元着色器。 |
3.x | 2008\~2010 | 弃用固定功能管线 | 引入VAO、VBO、FBO等对象模型,提升性能,同时标记旧API为弃用。 |
4.x | 2010\~2023 | 高级渲染特性 | 支持Tessellation(细分着色器)、Compute Shader(计算着色器)、多重采样、延迟渲染等。 |
OpenGL ES | 2003\~现今 | 移动设备图形API | 为嵌入式设备设计的轻量级OpenGL子集,是Android/iOS等平台的主流图形API。 |
虽然OpenGL仍然存在并在一些跨平台应用中被使用,但其在桌面图形开发中的地位正逐渐被以下技术替代:
图形API | 是否支持 Shader | 使用语言 | 特点 |
---|---|---|---|
OpenGL 2.0+ | ✅ 支持 | GLSL(OpenGL Shading Language) | 首个引入可编程 Shader 的 OpenGL 版本 |
OpenGL ES 2.0+ | ✅ 支持 | GLSL ES | 面向嵌入式设备,语法简化 |
Vulkan | ✅ 必须使用 | GLSL/SPIR-V/HLSL | 必须使用 Shader,没有默认固定功能;更接近硬件 |
Metal | ✅ 必须使用 | Metal Shading Language (MSL) | 苹果自定义语言,基于C++14 |
DirectX 10+ | ✅ 支持 | HLSL(High Level Shader Language) | 微软专用语言 |
WebGL 1.0(基于 OpenGL ES 2.0) | ✅ 支持 | GLSL ES | Web环境中的图形编程 |
WebGPU | ✅ 必须使用 | WGSL | 全新语言,类似 Rust 风格 |
什么是 Shaders?
如果你曾经有用计算机绘图的经验,你就知道在这个过程中你需要画一个圆,然后一个长方形,一条线,一些三角形……直到画出你想要的图像。这个过程很像用手写一封信或一本书 —— 都是一系列的指令,需要你一件一件完成。
Shaders 也是一系列的指令,但是这些指令会对屏幕上的每个像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置执行不同的操作。就像活字印刷,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。
为什么 shaders 运行特别快
不得不给大家介绍并行处理(parallel processing)的神奇之处。
想象你的 CPU 是一个大的工业管道,然后每一个任务都是通过这个管道的某些东西 —— 就像一个生产流水线那样。有些任务要比别的大,也就是说要花费更多时间和精力去处理。我们就称它要求更强的处理能力。由于计算机自身的架构,这些任务需要串行;即一次一个地依序完成。现代计算机通常有一组四个处理器,就像这个管道一样运行,一个接一个地处理这些任务,从而使计算机流畅运行。每个管道通常被称为线程。
视频游戏和其他图形应用比起别的程序来说,需要高得多的处理能力。因为它们的图形内容需要操作无数像素。想想看,屏幕上的每一个像素都需要计算,而在 3D 游戏中几何和透视也都需要计算。
让我们回到开始那个关于管道和任务的比喻。屏幕上的每个像素都代表一个最简单的任务。单独来看完成任何一个像素的任务对 CPU 来说都很容易,那么问题来了,屏幕上的每一个像素都需要解决这样的小任务!也就是说,哪怕是对于一个老式的屏幕(分辨率 800x600)来说,都需要每帧处理480000个像素,即每秒进行14400000次计算!是的,这对于微处理器就是大问题了!而对于一个现代的 2800x1800 视网膜屏,每秒运行60帧,就需要每秒进行311040000次计算。图形工程师是如何解决这个问题的?
这个时候,并行处理就是最好的解决方案。比起用三五个强大的微处理器(或者说“管道”)来处理这些信息,用一大堆小的微处理器来并行计算,就要好得多。这就是图形处理器(GPU : Graphic Processor Unit)的来由。
设想一堆小型微处理器排成一个平面的画面,假设每个像素的数据是乒乓球。14400000个乒乓球可以在一秒内阻塞几乎任何管道。但是一面800x600的管道墙,每秒接收30波480000个像素的信息就可以流畅完成。这在更高的分辨率下也是成立的 —— 并行的处理器越多,可以处理的数据流就越大。
另一个 GPU 的魔法是特殊数学函数可通过硬件加速。非常复杂的数学操作可以直接被微芯片解决,而无须通过软件。这就表示可以有更快的三角和矩阵运算 —— 和电流一样快。
Shader 分类
在 OpenGL 中,最常用的 Shader 有以下几种:
Vertex Shader(顶点着色器)
作用:对每个顶点进行处理(如变换、法线计算、纹理坐标传递等)。
输入:顶点属性(如位置、法线、纹理坐标)。
输出:传递给下一个阶段的数据(如齐次坐标、varying 变量)。
#version 330 core
layout (location = 0) in vec3 aPos; // 顶点位置
void main() {
gl_Position = vec4(aPos, 1.0); // 变换到裁剪空间
}
Fragment Shader(片段着色器 / 像素着色器)
作用:对屏幕上的每个片段(像素)进行着色(如颜色计算、光照、纹理映射)。
输入:从顶点着色器插值而来的数据(如颜色、纹理坐标)。
输出:最终写入到 Framebuffer 的颜色。
#version 330 core
out vec4 FragColor; // 最终颜色输出
void main() {
FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 橙色
}
Geometry Shader(几何着色器)(可选)
作用:对图元(点、线、三角形)进行处理,可以动态生成新的顶点。
一般用于复杂几何效果(如草丛、爆炸)。
Tessellation Shader(细分着色器)(高级功能)
作用:将图形细分为更小的片段,主要用于高精度的曲面细分。
包含:Tessellation Control Shader、Tessellation Evaluation Shader。
Compute Shader(计算着色器)
作用:通用 GPU 计算,不走图形管线(GPGPU),用于粒子系统、图像处理、物理模拟等。
Shader 工作流程
CPU 传递顶点数据到 GPU。
Vertex Shader 处理顶点,将其转换到屏幕坐标。
经过装配与光栅化,生成片段(Fragment)。
Fragment Shader 处理每个片段的颜色输出。
最终输出到 Framebuffer 显示在屏幕上。
Shader 编写语言
OpenGL 使用 GLSL(OpenGL Shading Language) 编写 Shader 代码。
语法类似 C 语言,但针对 GPU 并行处理优化。
Shader 的作用与优势
传统固定管线 | 可编程 Shader |
---|---|
功能固定 | 灵活编程自定义效果 |
光照模型单一 | 支持各种光照/后处理特效 |
无法扩展 | 可实现水面、火焰、阴影、后期特效等 |
Fragment Shader
Fragment Shader 基本结构
#version 330 core // GLSL版本声明
precision mediump float; //声明浮点精度
// Uniform变量(全局变量,由CPU设置)
uniform sampler2D texture1;
uniform vec3 lightColor;
// 输入变量(从 Vertex Shader 传入,必须与 Vertex Shader 的 out 匹配)
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoord;
// 输出变量
out vec4 FragColor;
void main() {
vec4 texColor = texture(texture1, TexCoord); // 采样纹理颜色
vec3 lighting = lightColor * texColor.rgb; // 简单的乘法光照
FragColor = vec4(lighting, texColor.a); // 输出到屏幕
}
Fragment Shader 语法详解
#version 指令
#version 330 core
- 必须在第一行,指定 GLSL 版本。
- 常见:330 core、300 es(OpenGL ES)、450 core。
precision mediump float;
precision mediump float;
- float类型在 shaders 中非常重要,所以精度非常重要。
- 更低的精度会有更快的渲染速度,但是会以质量为代价。
- 你可以选择每一个浮点值的精度。在第二行(precision mediump float;) 我们就是设定了所有的浮点值都是中等精度。但我们也可以选择把这个值设为“低”(precision lowp float;)或者“高”(precision highp float;)
in 输入变量
in vec2 TexCoord;
- 从顶点着色器(Vertex Shader)的 out 传递过来的变量。
- Fragment Shader 中可以进行插值使用。
out 输出变量
out vec4 FragColor;
最终输出颜色,OpenGL 默认 framebuffer 只接受 vec4(RGBA)。
uniform 变量
uniform sampler2D texture1;
uniform vec3 lightColor;
- 这些输入值叫做 uniform (统一值),它们的数据类型通常为:float, vec2, vec3, vec4, mat2, mat3, mat4, sampler2D and samplerCube。
- 由 CPU 传递给 GPU,全局共享变量。
- 因为显卡的架构,所有线程的输入值必须统一(uniform),而且必须设为只读。
- uniform 值需要数值类型前后一致。且在 shader 的开头,在设定精度之后,就对其进行定义。
- 常用于传递矩阵、颜色、光照参数、纹理等。
- 应该按业界传统应在 uniform 值的名字前加 u_ ,这样一看即知是 uniform。
precision mediump float;
uniform vec2 u_resolution; // 画布尺寸(宽,高)
uniform vec2 u_mouse; // 鼠标位置(在屏幕上哪个像素)
uniform float u_time; // 时间(加载后的秒数)
内置函数与变量
类型 | 说明 |
---|---|
gl_FragCoord |
当前片段的屏幕坐标(vec4) |
texture(sampler, uv) |
纹理采样 |
normalize(vec3) |
单位向量 |
dot(a, b) |
点乘 |
mix(a, b, t) |
线性插值 |
discard; |
丢弃该片段(透明效果) |
gl_FragCoord
fragment shader有一个默认输入值( vec4 gl_FragCoord )。gl_FragCoord存储了活动线程正在处理的像素或屏幕碎片的坐标。
有了它我们就知道了屏幕上的哪一个线程正在运转。
为什么我们不叫 gl_FragCoord uniform (统一值)呢?因为每个像素的坐标都不同,所以我们把它叫做 varying(变化值)。
#version version 330 core
precision mediump float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
out vec4 FragColor;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution;
FragColor = vec4(st.x,st.y,0.0,1.0);
}
上述代码中我们用 gl_FragCoord.xy 除以 u_resolution,对坐标进行了规范化。这样做是为了使所有的值落在 0.0 到 1.0 之间,这样就可以轻松把 X 或 Y 的值映射到红色或者绿色通道。
main() 函数
void main() {
// 颜色计算逻辑
FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 输出红色
}
- Fragment Shader 的入口函数,必须定义。
- 主要编写片段颜色、透明度、光照等效果。
Fragment Shader 常见代码模式
固定颜色
void main() {
FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
}
纹理映射
uniform sampler2D texture1;
in vec2 TexCoord;
void main() {
FragColor = texture(texture1, TexCoord);
}
光照计算(Lambert Diffuse)
in vec3 Normal;
uniform vec3 lightDir;
void main() {
float diff = max(dot(normalize(Normal), normalize(lightDir)), 0.0);
FragColor = vec4(vec3(diff), 1.0);
}
丢弃透明片段
void main() {
vec4 texColor = texture(texture1, TexCoord);
if(texColor.a < 0.1)
discard;
FragColor = texColor;
}
注意事项
- Fragment Shader 必须输出 vec4。
- 输入的 in 变量必须和 Vertex Shader 的 out 名称与类型匹配。
- Uniform 是全局变量,只能 CPU 传值,Shader 内不能修改。
- 浮点数需要写 .0,如 1.0。
void main() {
gl_FragColor = vec4(1,0,0,1); // 出错
}
Flutter 与 Shader
Flutter渲染引擎的发展
Flutter 渲染引擎的发展经历了几个关键阶段,从最早的 Skia 渲染架构,到引入 Impeller 渲染器,逐步优化了跨平台性能、视觉效果和 GPU 适配性。
早期阶段:Skia 渲染引擎
Skia 是 Google 开源的 2D 图形库,Chrome、Android、Firefox 都用它。
Flutter 最初的渲染核心完全基于 Skia,配合 Dart VM 实现跨平台 UI 渲染。
层级 | 技术 |
---|---|
Framework层 | Flutter Widgets / Dart |
Engine层 | Skia (C++) |
Platform层 | OpenGL / Vulkan / Metal |
它的优点主要如下:
- 跨平台性强(iOS/Android/Windows/Linux/Mac)。
- 高效的 2D 渲染性能。
它的缺点主要如下:
- 对于 GPU Overdraw、抗锯齿、实时Shader编译抖动(Shader Jank) 不够友好。
- 针对 Metal(iOS)与 Vulkan(Android)有性能瓶颈(Shader Warm-up 必须手动管理)。
关键改进:Shader Warm-up (预编译)
Flutter 团队曾通过 Shader Warm-up(着色器预编译) 方式缓解首帧卡顿的问题。
手动预加载 shader,但依然无法彻底解决 “Shader Jank”。
需要开发者自己维护 shader 列表,繁琐易错。
重大突破:Impeller 渲染引擎
Flutter 从 3.10 开始引入 Impeller 渲染器,意图取代 Skia。
Impeller 是 新一代 GPU 渲染器,专为 Flutter 优化,目标是“Shader Jank Zero”。
- Flutter 3.10 开始 iOS 上默认启用。
- Android 端仍在逐步测试中(Vulkan为主)。
- 仍保留 Skia 引擎作为回退方案(兼容性考虑)。
Flutter引入Impeller带来的好处:
优势 | 说明 |
---|---|
✅ 去除 JIT Shader 编译卡顿 | Skia 在运行时实时编译着色器,导致首次渲染卡顿(Jank);Impeller 使用预编译 Pipeline,无需运行时编译 Shader。 |
✅ 预编译 Shader 管线 | Shader 使用 GLSL/SPIR-V 等编译为各平台(Metal、Vulkan)目标语言,构建时就完成,提升首帧体验和稳定性。 |
✅ 提高帧率一致性 | 减少掉帧、卡顿问题,适合高刷新率设备(90Hz/120Hz) |
✅ 更易实现高级图形功能 | Impeller 拥有更明确的图形抽象,便于未来集成高级特效(模糊、阴影、滤镜、粒子等) |
✅ 更强的跨平台一致性 | 提供一致的行为和视觉输出,无论底层是 Metal、Vulkan、OpenGL |
✅ 构建 Flutter GPU 的基础 | Impeller 提供构建 Flutter 自定义渲染器(Flutter GPU)的图形接口,以Flutter GPU实现的通用3D渲染库flutter_Scene,未来可扩展为更强大的图形平台。 |
Flutter渲染引擎下可编写的Shader
在传统 Flutter 中:
- 官方文档明确指出Flutter 不支持顶点着色器 (.vert 文件),仅支持 .frag 格式的 Fragment Shader。
在 Impeller + Flutter GPU 环境下:
- Impeller 渲染引擎是 Flutter 的新一代图形后端,支持 GPU 全流程优化,包括支持将 GLSL 顶点和片段着色器预编译到不同平台专有格式(如 SPIR-V、Metal Shader)以提升性能。
- Flutter GPU 是建立在 Impeller 之上的低级 API,允许开发者直接使用 Dart + GLSL 构建自定义渲染管线,并支持自定义顶点着色器
在Flutter项目下编写Fragment Shader
Flutter支持从 460 到 100 的任何 GLSL 版本,但部分可用功能受到限制。
着色器与 Flutter 一起使用时会受到以下限制:
- 不支持 UBO 和 SSBO
- sampler2D是唯一受支持的采样器类型
- texture仅支持 (sampler 和 uv)的双参数版本
- 不能声明额外的变化输入
- 以 Skia 为目标时,所有精度提示都会被忽略
- 不支持无符号整数和布尔值
Flutter 支持的Uniforms
可以通过uniform在 GLSL 着色器源中定义值,然后在 Dart 中为每个片段着色器实例设置这些值来配置片段程序。
GLSL 类型为float、vec2、vec3和 的浮点型统一变量vec4使用 方法来设置FragmentShader.setFloat。使用 类型的 GLSL 采样器值sampler2D使用 方法来设置FragmentShader.setImageSampler。
每个值的正确索引uniform由统一值在片段程序中定义的顺序决定。对于由多个浮点数组成的数据类型(例如 )vec4,您必须FragmentShader.setFloat为每个值调用一次。
uniform float uScale;
uniform sampler2D uTexture;
uniform vec2 uMagnitude;
uniform vec4 uColor;
初始化这些uniform值的相应 Dart 代码如下:
void updateShader(FragmentShader shader, Color color, Image image) {
shader.setFloat(0, 23); // uScale
shader.setFloat(1, 114); // uMagnitude x
shader.setFloat(2, 83); // uMagnitude y
// Convert color to premultiplied opacity.
shader.setFloat(3, color.red / 255 * color.opacity); // uColor r
shader.setFloat(4, color.green / 255 * color.opacity); // uColor g
shader.setFloat(5, color.blue / 255 * color.opacity); // uColor b
shader.setFloat(6, color.opacity); // uColor a
// Initialize sampler uniform.
shader.setImageSampler(0, image);
}
请注意, sampler2D的索引不会按照FragmentShader.setFloat
中使用的索引计算。该sampler2D是使用单独设置的索引从 0 开始。
Flutter中获取当前位置
着色器可以访问varying
包含正在计算的特定片段的局部坐标的值。使用此功能可以计算依赖于当前位置的效果,可以通过导入flutter/runtime_effect.glsl
库并调用FlutterFragCoord
函数来访问当前位置。例如:
#include <flutter/runtime_effect.glsl>
void main() {
vec2 currentPos = FlutterFragCoord().xy;
}
FlutterFragCoord
返回的值与gl_FragCoord
不同。gl_FragCoord
提供屏幕空间坐标,以确保着色器在各个后端保持一致,通常应避免使用。当以 Skia 后端为目标时, 调用gl_FragCoord会被重写以访问本地坐标,但 Impeller 无法进行这种重写。
Flutter中的采样器(Samplers)
采样器提供对 dart:ui Image 对象的访问
#include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec2 uv = FlutterFragCoord().xy / uSize;
fragColor = texture(uTexture, uv);
}
Flutter Skia为后端的性能问题
当以 Skia 后端为目标时,加载着色器可能会很昂贵,因为必须在运行时将其编译为特定于平台的相应着色器。如果打算在动画期间使用一个或多个着色器,应在开始动画之前预先缓存片段程序对象。
Flutter实现烟雾飘动的背景
- 在项目目录下创建
shaders/smoke.frag
定义shader:
#include <flutter/runtime_effect.glsl>
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
out vec4 fragColor;
float random (in vec2 _st) {
return fract(sin(dot(_st.xy,
vec2(12.9898,78.233)))*
43758.5453123);
}
float noise (in vec2 _st) {
vec2 i = floor(_st);
vec2 f = fract(_st);
// Four corners in 2D of a tile
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) +
(c - a)* u.y * (1.0 - u.x) +
(d - b) * u.x * u.y;
}
#define NUM_OCTAVES 5
float fbm ( in vec2 _st) {
float v = 0.0;
float a = 0.5;
vec2 shift = vec2(100.0);
// Rotate to reduce axial bias
mat2 rot = mat2(cos(0.5), sin(0.5),
-sin(0.5), cos(0.50));
for (int i = 0; i < NUM_OCTAVES; ++i) {
v += a * noise(_st);
_st = rot * _st * 2.0 + shift;
a *= 0.5;
}
return v;
}
void main() {
vec2 st = FlutterFragCoord().xy/u_resolution.xy*3.;
// st += st * abs(sin(u_time*0.1)*3.0);
vec3 color = vec3(0.0);
vec2 q = vec2(0.);
q.x = fbm( st + 0.00*u_time);
q.y = fbm( st + vec2(1.0));
vec2 r = vec2(0.);
r.x = fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*u_time );
r.y = fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*u_time);
float f = fbm(st+r);
color = mix(vec3(0.101961,0.619608,0.666667),
vec3(0.666667,0.666667,0.498039),
clamp((f*f)*4.0,0.0,1.0));
color = mix(color,
vec3(0,0,0.164706),
clamp(length(q),0.0,1.0));
color = mix(color,
vec3(0.666667,1,1),
clamp(length(r.x),0.0,1.0));
fragColor = vec4((f*f*f+.6*f*f+.5*f)*color,1.);
}
- pubspec.yaml配置shaders
flutter:
uses-material-design: true
shaders:
- shaders/smoke.frag
- Flutter加载Fragment Shader
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MaterialApp(home: ShaderWidget(),));
class ShaderWidget extends StatefulWidget {
const ShaderWidget({super.key});
@override
State<ShaderWidget> createState() => _ShaderWidgetState();
}
class _ShaderWidgetState extends State<ShaderWidget>
with SingleTickerProviderStateMixin {
late final Ticker _ticker;
double _elapsed = 0;
late final Future<ui.FragmentProgram> _shaderFuture;
@override
void initState() {
super.initState();
_shaderFuture = ui.FragmentProgram.fromAsset('shaders/smoke.frag');
_ticker = createTicker((Duration elapsed) {
setState(() {
_elapsed = elapsed.inMilliseconds / 1000.0;
});
})..start();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<ui.FragmentProgram>(
future: _shaderFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox.shrink();
final shader = snapshot.data!.fragmentShader();
return CustomPaint(
painter: _ShaderPainter(shader, _elapsed),
size: MediaQuery.of(context).size,
);
},
);
}
}
class _ShaderPainter extends CustomPainter {
final ui.FragmentShader shader;
final double time;
_ShaderPainter(this.shader, this.time);
@override
void paint(Canvas canvas, Size size) {
shader.setFloat(0, size.width); // u_resolution.x
shader.setFloat(1, size.height); // u_resolution.y
shader.setFloat(2, time); // u_time
final paint = Paint()..shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}
@override
bool shouldRepaint(_ShaderPainter oldDelegate) {
return oldDelegate.time != time;
}
}
这样就实现了背景动态烟雾效果Fragment shader的加载与渲染。
如果想实现更多效果,参考:
- Fragment Shader在线学习 The Book of Shaders。
- 在线预览Shader效果和提供GLSL源码 shadertoy