
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎨 一、着色器基础
📚 1.1 什么是着色器
着色器(Shader)是运行在GPU上的小程序,用于控制图形渲染的各个阶段。它们使用GLSL(OpenGL Shading Language)或类似语言编写,能够高效地并行处理大量数据。
渲染管线概述:
图形渲染管线:
┌─────────────┐
│ 顶点数据 │ ← 位置、颜色、纹理坐标
└──────┬──────┘
↓
┌─────────────┐
│ 顶点着色器 │ ← 变换、光照计算
└──────┬──────┘
↓
┌─────────────┐
│ 图元装配 │ ← 三角形、线段
└──────┬──────┘
↓
┌─────────────┐
│ 几何着色器 │ ← 可选:生成新图元
└──────┬──────┘
↓
┌─────────────┐
│ 光栅化 │ ← 转换为片段
└──────┬──────┘
↓
┌─────────────┐
│ 片段着色器 │ ← 颜色、纹理、光照
└──────┬──────┘
↓
┌─────────────┐
│ 输出合并 │ ← 深度测试、混合
└──────┬──────┘
↓
┌─────────────┐
│ 帧缓冲 │ ← 最终图像
└─────────────┘
着色器类型:
主要着色器类型:
1. 顶点着色器(Vertex Shader)
- 处理每个顶点
- 坐标变换
- 逐顶点光照
- 传递数据给片段着色器
2. 片段着色器(Fragment Shader)
- 处理每个像素
- 纹理采样
- 逐像素光照
- 颜色计算
3. 几何着色器(Geometry Shader)
- 处理图元
- 生成新顶点
- 程序化几何
4. 计算着色器(Compute Shader)
- 通用计算
- 物理模拟
- 图像处理
🔬 1.2 GLSL 语言基础
数据类型:
glsl
// 标量类型
float f = 1.0; // 浮点数
int i = 1; // 整数
bool b = true; // 布尔值
// 向量类型
vec2 v2 = vec2(1.0, 2.0); // 2D向量
vec3 v3 = vec3(1.0, 2.0, 3.0); // 3D向量
vec4 v4 = vec4(1.0, 2.0, 3.0, 4.0); // 4D向量
// 向量分量访问
v3.x, v3.y, v3.z // 位置分量
v3.r, v3.g, v3.b // 颜色分量
v3.s, v3.t, v3.p // 纹理坐标
// 矩阵类型
mat2 m2; // 2x2矩阵
mat3 m3; // 3x3矩阵
mat4 m4; // 4x4矩阵
// 纹理采样器
sampler2D tex; // 2D纹理
samplerCube cube; // 立方体贴图
内置函数:
glsl
// 数学函数
abs(x) // 绝对值
sin(x), cos(x) // 三角函数
pow(x, y) // 幂函数
sqrt(x) // 平方根
log(x) // 对数
// 几何函数
length(v) // 向量长度
distance(a, b) // 两点距离
dot(a, b) // 点积
cross(a, b) // 叉积
normalize(v) // 归一化
reflect(v, n) // 反射
refract(v, n, ratio) // 折射
// 插值与平滑
mix(a, b, t) // 线性插值
smoothstep(edge0, edge1, x) // 平滑阶梯
step(edge, x) // 阶梯函数
// 纹理采样
texture(tex, uv) // 纹理采样
textureLod(tex, uv, lod) // 指定LOD采样
🎯 1.3 Flutter 中的着色器
Flutter Shader API:
dart
import 'dart:ui' as ui;
import 'dart:typed_data';
// 加载着色器
Future<ui.FragmentShader> loadShader(String assetPath) async {
final program = await ui.FragmentShader.fromAsset(assetPath);
return program;
}
// 使用着色器
class ShaderPainter extends CustomPainter {
final ui.FragmentShader shader;
final double time;
ShaderPainter(this.shader, this.time);
@override
void paint(Canvas canvas, Size size) {
// 设置 uniform 变量
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setFloat(2, time);
// 使用着色器绘制
final paint = Paint()..shader = shader;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
@override
bool shouldRepaint(covariant ShaderPainter old) => time != old.time;
}
🌈 二、经典着色器效果
🎨 2.1 渐变与颜色效果
径向渐变着色器:
glsl
// radial_gradient.frag
#version 460 core
precision mediump float;
layout(location = 0) out vec4 fragColor;
uniform float u_width;
uniform float u_height;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / vec2(u_width, u_height);
vec2 center = vec2(0.5, 0.5);
float dist = distance(uv, center);
// 多色渐变
vec3 color1 = vec3(1.0, 0.2, 0.4); // 粉红
vec3 color2 = vec3(0.2, 0.4, 1.0); // 蓝色
vec3 color3 = vec3(0.2, 1.0, 0.4); // 绿色
float t = dist * 2.0 + u_time * 0.5;
vec3 color = mix(color1, color2, sin(t) * 0.5 + 0.5);
color = mix(color, color3, sin(t * 2.0) * 0.5 + 0.5);
fragColor = vec4(color, 1.0);
}
彩虹效果:
glsl
// rainbow.frag
#version 460 core
precision mediump float;
layout(location = 0) out vec4 fragColor;
uniform float u_width;
uniform float u_height;
uniform float u_time;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec2 uv = gl_FragCoord.xy / vec2(u_width, u_height);
float hue = uv.x + uv.y + u_time * 0.1;
vec3 color = hsv2rgb(vec3(hue, 0.8, 1.0));
fragColor = vec4(color, 1.0);
}
🌀 2.2 噪声与分形效果
Perlin 噪声实现:
glsl
// perlin_noise.frag
#version 460 core
precision mediump float;
layout(location = 0) out vec4 fragColor;
uniform float u_width;
uniform float u_height;
uniform float u_time;
// 伪随机函数
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
// 2D 噪声
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
// 四个角的随机值
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;
}
// 分形布朗运动(FBM)
float fbm(vec2 st) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < 6; i++) {
value += amplitude * noise(st * frequency);
frequency *= 2.0;
amplitude *= 0.5;
}
return value;
}
void main() {
vec2 uv = gl_FragCoord.xy / vec2(u_width, u_height);
// 动态噪声
float n = fbm(uv * 5.0 + u_time * 0.2);
// 颜色映射
vec3 color1 = vec3(0.1, 0.1, 0.3);
vec3 color2 = vec3(0.8, 0.4, 0.2);
vec3 color = mix(color1, color2, n);
fragColor = vec4(color, 1.0);
}
🌊 2.3 波纹与扭曲效果
水波纹效果:
glsl
// water_ripple.frag
#version 460 core
precision mediump float;
layout(location = 0) out vec4 fragColor;
uniform float u_width;
uniform float u_height;
uniform float u_time;
void main() {
vec2 uv = gl_FragCoord.xy / vec2(u_width, u_height);
vec2 center = vec2(0.5, 0.5);
// 计算到中心的距离
float dist = distance(uv, center);
// 波纹效果
float wave = sin(dist * 30.0 - u_time * 3.0) * 0.02;
// 扭曲 UV
vec2 distortedUV = uv + normalize(uv - center) * wave;
// 颜色
vec3 color1 = vec3(0.1, 0.3, 0.6);
vec3 color2 = vec3(0.2, 0.5, 0.8);
float t = sin(dist * 20.0 - u_time * 2.0) * 0.5 + 0.5;
vec3 color = mix(color1, color2, t);
// 添加高光
float highlight = pow(1.0 - abs(wave * 50.0), 3.0) * 0.5;
color += highlight;
fragColor = vec4(color, 1.0);
}
🎵 三、音频驱动的着色器效果
🎼 3.1 音频频谱可视化
频谱条形图着色器:
glsl
// spectrum_bars.frag
#version 460 core
precision mediump float;
layout(location = 0) out vec4 fragColor;
uniform float u_width;
uniform float u_height;
uniform float u_time;
uniform float u_bars[64]; // 频谱数据
void main() {
vec2 uv = gl_FragCoord.xy / vec2(u_width, u_height);
// 计算当前像素属于哪个频谱条
int barIndex = int(uv.x * 64.0);
float barValue = u_bars[barIndex];
// 绘制条形
float barHeight = barValue;
float inBar = step(uv.y, barHeight);
// 颜色渐变
float hue = uv.x * 0.8;
vec3 color = vec3(
sin(hue * 6.28) * 0.5 + 0.5,
sin((hue + 0.33) * 6.28) * 0.5 + 0.5,
sin((hue + 0.66) * 6.28) * 0.5 + 0.5
);
// 发光效果
float glow = exp(-abs(uv.y - barHeight) * 10.0) * 0.5;
color = color * inBar + color * glow;
fragColor = vec4(color, 1.0);
}
🎚️ 3.2 音乐节拍响应
脉冲效果着色器:
glsl
// pulse.frag
#version 460 core
precision mediump float;
layout(location = 0) out vec4 fragColor;
uniform float u_width;
uniform float u_height;
uniform float u_time;
uniform float u_beat; // 节拍强度 [0, 1]
uniform float u_energy; // 总能量
void main() {
vec2 uv = gl_FragCoord.xy / vec2(u_width, u_height);
vec2 center = vec2(0.5, 0.5);
// 距离中心的距离
float dist = distance(uv, center);
// 脉冲波
float pulse = sin(dist * 20.0 - u_time * 5.0) * 0.5 + 0.5;
// 节拍响应
float beatPulse = u_beat * exp(-dist * 3.0);
// 颜色
vec3 color1 = vec3(0.8, 0.2, 0.4);
vec3 color2 = vec3(0.2, 0.4, 0.8);
vec3 color = mix(color1, color2, pulse);
// 添加节拍脉冲
color += vec3(beatPulse);
// 能量影响亮度
color *= 0.5 + u_energy * 0.5;
fragColor = vec4(color, 1.0);
}
🔧 四、Dart/Flutter 着色器集成
🎯 4.1 着色器加载与管理
dart
import 'dart:ui' as ui;
import 'dart:typed_data';
class ShaderManager {
static final ShaderManager _instance = ShaderManager._internal();
factory ShaderManager() => _instance;
ShaderManager._internal();
final Map<String, ui.FragmentShader> _shaders = {};
Future<void> loadShader(String name, String assetPath) async {
try {
final shader = await ui.FragmentShader.fromAsset(assetPath);
_shaders[name] = shader;
} catch (e) {
debugPrint('Failed to load shader $name: $e');
}
}
ui.FragmentShader? getShader(String name) => _shaders[name];
void dispose() {
for (final shader in _shaders.values) {
shader.dispose();
}
_shaders.clear();
}
}
🎨 4.2 着色器绘制器
dart
class ShaderPainter extends CustomPainter {
final ui.FragmentShader shader;
final List<double> uniforms;
ShaderPainter(this.shader, this.uniforms);
@override
void paint(Canvas canvas, Size size) {
int index = 0;
shader.setFloat(index++, size.width);
shader.setFloat(index++, size.height);
for (final uniform in uniforms) {
shader.setFloat(index++, uniform);
}
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..shader = shader,
);
}
@override
bool shouldRepaint(covariant ShaderPainter old) =>
!listEquals(uniforms, old.uniforms);
}
💻 五、完整代码实现
dart
import 'package:flutter/material.dart';
import 'package:just_audio_ohos/just_audio_ohos.dart';
import 'package:audio_session/audio_session.dart';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui;
void main() {
runApp(const ShaderApp());
}
class ShaderApp extends StatelessWidget {
const ShaderApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '着色器效果',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark),
useMaterial3: true,
),
home: const ShaderHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class ShaderHomePage extends StatelessWidget {
const ShaderHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('着色器效果'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildCard(context, title: '渐变效果', description: '多彩径向渐变', icon: Icons.gradient, color: Colors.purple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const GradientDemo()))),
_buildCard(context, title: '噪声效果', description: 'Perlin与Simplex噪声', icon: Icons.grain, color: Colors.indigo,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const NoiseDemo()))),
_buildCard(context, title: '波纹效果', description: '水波与涟漪', icon: Icons.waves, color: Colors.blue,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RippleDemo()))),
_buildCard(context, title: '漩涡效果', description: '扭曲与旋转', icon: Icons.cyclone, color: Colors.cyan,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwirlDemo()))),
_buildCard(context, title: '频谱可视化', description: '音频频谱着色器', icon: Icons.equalizer, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SpectrumShaderDemo()))),
_buildCard(context, title: '音乐脉冲', description: '节拍响应效果', icon: Icons.music_note, color: Colors.green,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MusicPulseDemo()))),
_buildCard(context, title: '等离子效果', description: '经典等离子体', icon: Icons.bolt, color: Colors.yellow,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PlasmaDemo()))),
_buildCard(context, title: '万花筒', description: '对称镜像效果', icon: Icons.filter_vintage, color: Colors.pink,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const KaleidoscopeDemo()))),
],
),
);
}
Widget _buildCard(BuildContext context, {required String title, required String description, required IconData icon,
required Color color, required VoidCallback onTap}) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(children: [
Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
child: Icon(icon, color: color, size: 28)),
const SizedBox(width: 16),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(description, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
])),
Icon(Icons.chevron_right, color: Colors.grey[400]),
]),
),
),
);
}
}
class GradientDemo extends StatefulWidget {
const GradientDemo({super.key});
@override
State<GradientDemo> createState() => _GradientDemoState();
}
class _GradientDemoState extends State<GradientDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _gradientType = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('渐变效果')),
body: Stack(children: [
CustomPaint(painter: GradientPainter(_time, _gradientType), size: Size.infinite),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
const Text('渐变类型', style: TextStyle(color: Colors.white70, fontSize: 12)),
const SizedBox(height: 8),
Wrap(spacing: 8, children: [
ActionChip(label: const Text('径向', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _gradientType == 0 ? Colors.purple : Colors.purple.withOpacity(0.3),
onPressed: () => setState(() => _gradientType = 0)),
ActionChip(label: const Text('线性', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _gradientType == 1 ? Colors.purple : Colors.purple.withOpacity(0.3),
onPressed: () => setState(() => _gradientType = 1)),
ActionChip(label: const Text('彩虹', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _gradientType == 2 ? Colors.purple : Colors.purple.withOpacity(0.3),
onPressed: () => setState(() => _gradientType = 2)),
]),
]),
);
}
}
class GradientPainter extends CustomPainter {
final double time;
final int gradientType;
GradientPainter(this.time, this.gradientType);
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
switch (gradientType) {
case 0:
_drawRadialGradient(canvas, size, center);
break;
case 1:
_drawLinearGradient(canvas, size);
break;
case 2:
_drawRainbowGradient(canvas, size, center);
break;
}
}
void _drawRadialGradient(Canvas canvas, Size size, Offset center) {
final colors = <Color>[
HSVColor.fromAHSV(1, (time * 30) % 360, 0.8, 1).toColor(),
HSVColor.fromAHSV(1, (time * 30 + 60) % 360, 0.8, 1).toColor(),
HSVColor.fromAHSV(1, (time * 30 + 120) % 360, 0.8, 1).toColor(),
HSVColor.fromAHSV(1, (time * 30 + 180) % 360, 0.8, 1).toColor(),
];
final shader = ui.Gradient.radial(center, size.width * 0.7, colors, [0.0, 0.33, 0.66, 1.0]);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..shader = shader);
}
void _drawLinearGradient(Canvas canvas, Size size) {
final angle = time * 0.5;
final dx = cos(angle) * size.width;
final dy = sin(angle) * size.height;
final colors = <Color>[
HSVColor.fromAHSV(1, (time * 40) % 360, 0.9, 1).toColor(),
HSVColor.fromAHSV(1, (time * 40 + 90) % 360, 0.9, 1).toColor(),
HSVColor.fromAHSV(1, (time * 40 + 180) % 360, 0.9, 1).toColor(),
];
final shader = ui.Gradient.linear(Offset(-dx, -dy), Offset(size.width + dx, size.height + dy), colors, [0.0, 0.5, 1.0]);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..shader = shader);
}
void _drawRainbowGradient(Canvas canvas, Size size, Offset center) {
for (int y = 0; y < size.height; y++) {
final hue = (y / size.height * 360 + time * 30) % 360;
canvas.drawRect(Rect.fromLTWH(0, y.toDouble(), size.width, 1),
Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor());
}
}
@override
bool shouldRepaint(covariant GradientPainter old) => true;
}
class NoiseDemo extends StatefulWidget {
const NoiseDemo({super.key});
@override
State<NoiseDemo> createState() => _NoiseDemoState();
}
class _NoiseDemoState extends State<NoiseDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _noiseType = 0;
double _scale = 5.0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('噪声效果')),
body: Stack(children: [
CustomPaint(painter: NoisePainter(_time, _noiseType, _scale), size: Size.infinite),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Row(children: [
Expanded(child: Column(children: [
const Text('噪声类型', style: TextStyle(color: Colors.white70, fontSize: 11)),
Wrap(spacing: 4, children: [
ActionChip(label: const Text('Perlin', style: TextStyle(color: Colors.white, fontSize: 10)),
backgroundColor: _noiseType == 0 ? Colors.indigo : Colors.indigo.withOpacity(0.3),
onPressed: () => setState(() => _noiseType = 0)),
ActionChip(label: const Text('FBM', style: TextStyle(color: Colors.white, fontSize: 10)),
backgroundColor: _noiseType == 1 ? Colors.indigo : Colors.indigo.withOpacity(0.3),
onPressed: () => setState(() => _noiseType = 1)),
]),
])),
Expanded(child: Column(children: [
Text('缩放: ${_scale.toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _scale, min: 1, max: 20, onChanged: (v) => setState(() => _scale = v), activeColor: Colors.indigo),
])),
]),
]),
);
}
}
class NoisePainter extends CustomPainter {
final double time;
final int noiseType;
final double scale;
NoisePainter(this.time, this.noiseType, this.scale);
@override
void paint(Canvas canvas, Size size) {
for (int x = 0; x < size.width; x += 2) {
for (int y = 0; y < size.height; y += 2) {
double noise;
switch (noiseType) {
case 0:
noise = _perlinNoise(x / size.width * scale, y / size.height * scale, time * 0.5);
break;
default:
noise = _fbmNoise(x / size.width * scale, y / size.height * scale, time * 0.3);
}
final hue = (noise * 180 + time * 20) % 360;
canvas.drawRect(Rect.fromLTWH(x.toDouble(), y.toDouble(), 2, 2),
Paint()..color = HSVColor.fromAHSV(1, hue, 0.7, noise * 0.5 + 0.5).toColor());
}
}
}
double _perlinNoise(double x, double y, double t) {
return (sin(x * 3 + t) * cos(y * 3 + t) + 1) / 2;
}
double _fbmNoise(double x, double y, double t) {
double value = 0;
double amplitude = 0.5;
for (int i = 0; i < 4; i++) {
value += amplitude * (sin(x * (i + 1) + t * (i + 1)) * cos(y * (i + 1) + t * (i + 1)) + 1) / 2;
amplitude *= 0.5;
}
return value;
}
@override
bool shouldRepaint(covariant NoisePainter old) => true;
}
class RippleDemo extends StatefulWidget {
const RippleDemo({super.key});
@override
State<RippleDemo> createState() => _RippleDemoState();
}
class _RippleDemoState extends State<RippleDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
double _waveSpeed = 3.0;
double _waveFreq = 20.0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('波纹效果')),
body: Stack(children: [
CustomPaint(painter: RipplePainter(_time, _waveSpeed, _waveFreq), size: Size.infinite),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Row(children: [
Expanded(child: Column(children: [
Text('波速: ${_waveSpeed.toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _waveSpeed, min: 1, max: 10, onChanged: (v) => setState(() => _waveSpeed = v), activeColor: Colors.blue),
])),
Expanded(child: Column(children: [
Text('频率: ${_waveFreq.toStringAsFixed(0)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _waveFreq, min: 5, max: 50, onChanged: (v) => setState(() => _waveFreq = v), activeColor: Colors.blue),
])),
]),
]),
);
}
}
class RipplePainter extends CustomPainter {
final double time;
final double waveSpeed;
final double waveFreq;
RipplePainter(this.time, this.waveSpeed, this.waveFreq);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a1520));
final center = Offset(size.width / 2, size.height / 2);
final maxDist = sqrt(size.width * size.width + size.height * size.height) / 2;
for (int x = 0; x < size.width; x += 3) {
for (int y = 0; y < size.height; y += 3) {
final dist = sqrt(pow(x - center.dx, 2) + pow(y - center.dy, 2));
final wave = sin(dist / maxDist * waveFreq - time * waveSpeed);
final intensity = (wave + 1) / 2;
final hue = (200 + intensity * 40) % 360;
canvas.drawRect(Rect.fromLTWH(x.toDouble(), y.toDouble(), 3, 3),
Paint()..color = HSVColor.fromAHSV(intensity * 0.8, hue, 0.8, 1).toColor());
}
}
}
@override
bool shouldRepaint(covariant RipplePainter old) => true;
}
class SwirlDemo extends StatefulWidget {
const SwirlDemo({super.key});
@override
State<SwirlDemo> createState() => _SwirlDemoState();
}
class _SwirlDemoState extends State<SwirlDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
double _swirlStrength = 5.0;
double _rotationSpeed = 2.0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('漩涡效果')),
body: Stack(children: [
CustomPaint(painter: SwirlPainter(_time, _swirlStrength, _rotationSpeed), size: Size.infinite),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Row(children: [
Expanded(child: Column(children: [
Text('漩涡强度: ${_swirlStrength.toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _swirlStrength, min: 1, max: 15, onChanged: (v) => setState(() => _swirlStrength = v), activeColor: Colors.cyan),
])),
Expanded(child: Column(children: [
Text('旋转速度: ${_rotationSpeed.toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _rotationSpeed, min: 0.5, max: 5, onChanged: (v) => setState(() => _rotationSpeed = v), activeColor: Colors.cyan),
])),
]),
]),
);
}
}
class SwirlPainter extends CustomPainter {
final double time;
final double swirlStrength;
final double rotationSpeed;
SwirlPainter(this.time, this.swirlStrength, this.rotationSpeed);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final center = Offset(size.width / 2, size.height / 2);
for (int x = 0; x < size.width; x += 4) {
for (int y = 0; y < size.height; y += 4) {
final dx = x - center.dx;
final dy = y - center.dy;
final dist = sqrt(dx * dx + dy * dy);
final angle = atan2(dy, dx) + dist * swirlStrength / 100 - time * rotationSpeed;
final srcX = center.dx + cos(angle) * dist;
final srcY = center.dy + sin(angle) * dist;
final pattern = sin(srcX * 0.05) * cos(srcY * 0.05);
final intensity = (pattern + 1) / 2;
final hue = (intensity * 180 + time * 30) % 360;
canvas.drawRect(Rect.fromLTWH(x.toDouble(), y.toDouble(), 4, 4),
Paint()..color = HSVColor.fromAHSV(0.7, hue, 0.8, 1).toColor());
}
}
}
@override
bool shouldRepaint(covariant SwirlPainter old) => true;
}
class SpectrumShaderDemo extends StatefulWidget {
const SpectrumShaderDemo({super.key});
@override
State<SpectrumShaderDemo> createState() => _SpectrumShaderDemoState();
}
class _SpectrumShaderDemoState extends State<SpectrumShaderDemo> with TickerProviderStateMixin {
late AnimationController _animController;
late AudioPlayer _audioPlayer;
Float32List _audioData = Float32List(64);
double _energy = 0, _bass = 0, _mid = 0, _treble = 0;
double _time = 0;
bool _isPlaying = false;
int _spectrumType = 0;
static const String _audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
@override
void initState() {
super.initState();
_initAudio();
_animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 33))..repeat();
_animController.addListener(_update);
}
Future<void> _initAudio() async {
_audioPlayer = AudioPlayer();
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
_audioPlayer.playerStateStream.listen((s) => setState(() => _isPlaying = s.playing));
try { await _audioPlayer.setUrl(_audioUrl); } catch (e) { debugPrint('加载失败: $e'); }
}
void _update() {
_time += 0.033;
for (int i = 0; i < 64; i++) {
if (_isPlaying) {
final freq = (i / 64) * 8 + 1;
_audioData[i] = _audioData[i] * 0.85 + (sin(_time * freq) * 0.5 + 0.5) * 0.15;
} else {
_audioData[i] *= 0.95;
}
}
double total = 0, bassE = 0, midE = 0, trebleE = 0;
for (int i = 0; i < 64; i++) {
total += _audioData[i];
if (i < 16) bassE += _audioData[i];
else if (i < 40) midE += _audioData[i];
else trebleE += _audioData[i];
}
_energy = total / 64;
_bass = bassE / 16;
_mid = midE / 24;
_treble = trebleE / 24;
setState(() {});
}
@override
void dispose() {
_animController.dispose();
_audioPlayer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('频谱可视化')),
body: Stack(children: [
CustomPaint(painter: SpectrumPainter(_audioData, _time, _spectrumType), size: Size.infinite),
Positioned(bottom: 30, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.8), borderRadius: BorderRadius.circular(16)),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Icon(Icons.music_note, color: Colors.teal),
const SizedBox(width: 8),
const Expanded(child: Text('SoundHelix - Song 1', style: TextStyle(color: Colors.white, fontSize: 14))),
Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: _isPlaying ? Colors.teal : Colors.grey[800], borderRadius: BorderRadius.circular(12)),
child: Text(_isPlaying ? '播放中' : '暂停', style: const TextStyle(color: Colors.white, fontSize: 12))),
]),
const SizedBox(height: 12),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.teal, size: 36),
onPressed: () => _isPlaying ? _audioPlayer.pause() : _audioPlayer.play()),
]),
Wrap(spacing: 8, children: [
ActionChip(label: const Text('条形', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _spectrumType == 0 ? Colors.teal : Colors.teal.withOpacity(0.3),
onPressed: () => setState(() => _spectrumType = 0)),
ActionChip(label: const Text('圆形', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _spectrumType == 1 ? Colors.teal : Colors.teal.withOpacity(0.3),
onPressed: () => setState(() => _spectrumType = 1)),
ActionChip(label: const Text('波浪', style: TextStyle(color: Colors.white, fontSize: 11)),
backgroundColor: _spectrumType == 2 ? Colors.teal : Colors.teal.withOpacity(0.3),
onPressed: () => setState(() => _spectrumType = 2)),
]),
]),
);
}
}
class SpectrumPainter extends CustomPainter {
final Float32List audioData;
final double time;
final int spectrumType;
SpectrumPainter(this.audioData, this.time, this.spectrumType);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a1520));
switch (spectrumType) {
case 0:
_drawBarSpectrum(canvas, size);
break;
case 1:
_drawCircularSpectrum(canvas, size);
break;
case 2:
_drawWaveSpectrum(canvas, size);
break;
}
}
void _drawBarSpectrum(Canvas canvas, Size size) {
final barWidth = size.width / audioData.length;
for (int i = 0; i < audioData.length; i++) {
final barHeight = audioData[i] * size.height * 0.8;
final hue = (i / audioData.length * 180 + time * 20) % 360;
final rect = Rect.fromLTWH(i * barWidth, size.height - barHeight, barWidth - 2, barHeight);
canvas.drawRect(rect, Paint()..color = HSVColor.fromAHSV(0.8, hue, 0.8, 1).toColor());
}
}
void _drawCircularSpectrum(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final baseRadius = min(size.width, size.height) * 0.2;
for (int i = 0; i < audioData.length; i++) {
final angle = (i / audioData.length) * 2 * pi - pi / 2;
final radius = baseRadius + audioData[i] * 100;
final hue = (i / audioData.length * 360 + time * 30) % 360;
final x = center.dx + cos(angle) * radius;
final y = center.dy + sin(angle) * radius;
canvas.drawLine(
Offset(center.dx + cos(angle) * baseRadius, center.dy + sin(angle) * baseRadius),
Offset(x, y),
Paint()..color = HSVColor.fromAHSV(0.8, hue, 0.8, 1).toColor()..strokeWidth = 4..strokeCap = StrokeCap.round,
);
}
}
void _drawWaveSpectrum(Canvas canvas, Size size) {
final path = Path();
final stepX = size.width / (audioData.length - 1);
for (int i = 0; i < audioData.length; i++) {
final x = i * stepX;
final y = size.height / 2 + audioData[i] * size.height * 0.4;
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
final hue = time * 20 % 360;
canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(0.8, hue, 0.8, 1).toColor()..style = PaintingStyle.stroke..strokeWidth = 2);
}
@override
bool shouldRepaint(covariant SpectrumPainter old) => true;
}
class MusicPulseDemo extends StatefulWidget {
const MusicPulseDemo({super.key});
@override
State<MusicPulseDemo> createState() => _MusicPulseDemoState();
}
class _MusicPulseDemoState extends State<MusicPulseDemo> with TickerProviderStateMixin {
late AnimationController _animController;
late AudioPlayer _audioPlayer;
Float32List _audioData = Float32List(64);
double _energy = 0, _bass = 0, _mid = 0, _treble = 0;
double _time = 0;
bool _isPlaying = false;
static const String _audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
@override
void initState() {
super.initState();
_initAudio();
_animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 33))..repeat();
_animController.addListener(_update);
}
Future<void> _initAudio() async {
_audioPlayer = AudioPlayer();
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
_audioPlayer.playerStateStream.listen((s) => setState(() => _isPlaying = s.playing));
try { await _audioPlayer.setUrl(_audioUrl); } catch (e) { debugPrint('加载失败: $e'); }
}
void _update() {
_time += 0.033;
for (int i = 0; i < 64; i++) {
if (_isPlaying) {
final freq = (i / 64) * 8 + 1;
_audioData[i] = _audioData[i] * 0.85 + (sin(_time * freq) * 0.5 + 0.5) * 0.15;
} else {
_audioData[i] *= 0.95;
}
}
double total = 0, bassE = 0, midE = 0, trebleE = 0;
for (int i = 0; i < 64; i++) {
total += _audioData[i];
if (i < 16) bassE += _audioData[i];
else if (i < 40) midE += _audioData[i];
else trebleE += _audioData[i];
}
_energy = total / 64;
_bass = bassE / 16;
_mid = midE / 24;
_treble = trebleE / 24;
setState(() {});
}
@override
void dispose() {
_animController.dispose();
_audioPlayer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('音乐脉冲')),
body: Stack(children: [
CustomPaint(painter: MusicPulsePainter(_energy, _bass, _mid, _treble, _time), size: Size.infinite),
Positioned(bottom: 30, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.8), borderRadius: BorderRadius.circular(16)),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Icon(Icons.music_note, color: Colors.green),
const SizedBox(width: 8),
const Expanded(child: Text('SoundHelix - Song 1', style: TextStyle(color: Colors.white, fontSize: 14))),
Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: _isPlaying ? Colors.green : Colors.grey[800], borderRadius: BorderRadius.circular(12)),
child: Text(_isPlaying ? '播放中' : '暂停', style: const TextStyle(color: Colors.white, fontSize: 12))),
]),
const SizedBox(height: 12),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.green, size: 36),
onPressed: () => _isPlaying ? _audioPlayer.pause() : _audioPlayer.play()),
]),
]),
);
}
}
class MusicPulsePainter extends CustomPainter {
final double energy;
final double bass;
final double mid;
final double treble;
final double time;
MusicPulsePainter(this.energy, this.bass, this.mid, this.treble, this.time);
@override
void paint(Canvas canvas, Size size) {
final bgColor = Color.lerp(const Color(0xFF0a0a15), const Color(0xFF1a2a1a), energy)!;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = bgColor);
final center = Offset(size.width / 2, size.height / 2);
final maxRadius = min(size.width, size.height) * 0.4;
for (int i = 0; i < 10; i++) {
final radius = maxRadius * (i / 10) + bass * 50;
final pulse = sin(radius * 0.05 - time * 3) * 0.5 + 0.5;
final alpha = (1 - i / 10) * 0.3 * pulse;
canvas.drawCircle(center, radius, Paint()..color = Colors.green.withOpacity(alpha));
}
final coreRadius = 20 + energy * 30;
final coreColor = HSVColor.fromAHSV(0.8, 120 + energy * 60, 0.8, 1).toColor();
canvas.drawCircle(center, coreRadius, Paint()..color = coreColor);
}
@override
bool shouldRepaint(covariant MusicPulsePainter old) => true;
}
class PlasmaDemo extends StatefulWidget {
const PlasmaDemo({super.key});
@override
State<PlasmaDemo> createState() => _PlasmaDemoState();
}
class _PlasmaDemoState extends State<PlasmaDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
double _speed = 1.0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016 * _speed;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('等离子效果')),
body: Stack(children: [
CustomPaint(painter: PlasmaPainter(_time), size: Size.infinite),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Text('速度: ${_speed.toStringAsFixed(1)}x', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _speed, min: 0.1, max: 3, onChanged: (v) => setState(() => _speed = v), activeColor: Colors.yellow),
]),
);
}
}
class PlasmaPainter extends CustomPainter {
final double time;
PlasmaPainter(this.time);
@override
void paint(Canvas canvas, Size size) {
for (int x = 0; x < size.width; x += 3) {
for (int y = 0; y < size.height; y += 3) {
final value1 = sin(x * 0.02 + time);
final value2 = sin(y * 0.02 + time * 0.5);
final value3 = sin((x + y) * 0.02 + time * 0.7);
final value4 = sin(sqrt(x * x + y * y) * 0.02 + time);
final plasma = (value1 + value2 + value3 + value4) / 4;
final hue = (plasma * 180 + 180 + time * 20) % 360;
canvas.drawRect(Rect.fromLTWH(x.toDouble(), y.toDouble(), 3, 3),
Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor());
}
}
}
@override
bool shouldRepaint(covariant PlasmaPainter old) => true;
}
class KaleidoscopeDemo extends StatefulWidget {
const KaleidoscopeDemo({super.key});
@override
State<KaleidoscopeDemo> createState() => _KaleidoscopeDemoState();
}
class _KaleidoscopeDemoState extends State<KaleidoscopeDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _segments = 8;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('万花筒')),
body: Stack(children: [
CustomPaint(painter: KaleidoscopePainter(_time, _segments), size: Size.infinite),
Positioned(bottom: 20, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Text('对称段数: $_segments', style: const TextStyle(color: Colors.white70, fontSize: 11)),
Slider(value: _segments.toDouble(), min: 2, max: 16, divisions: 14,
onChanged: (v) => setState(() => _segments = v.toInt()), activeColor: Colors.pink),
]),
);
}
}
class KaleidoscopePainter extends CustomPainter {
final double time;
final int segments;
KaleidoscopePainter(this.time, this.segments);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final center = Offset(size.width / 2, size.height / 2);
final maxRadius = min(size.width, size.height) / 2;
for (int seg = 0; seg < segments; seg++) {
final baseAngle = (seg / segments) * 2 * pi;
canvas.save();
canvas.translate(center.dx, center.dy);
canvas.rotate(baseAngle);
for (int r = 0; r < maxRadius; r += 5) {
final angle = (r / maxRadius) * pi / segments + time;
final hue = (r / maxRadius * 180 + time * 30) % 360;
final x = r * cos(angle);
final y = r * sin(angle);
canvas.drawCircle(Offset(x, y), 3, Paint()..color = HSVColor.fromAHSV(0.7, hue, 0.8, 1).toColor());
}
canvas.restore();
}
}
@override
bool shouldRepaint(covariant KaleidoscopePainter old) => true;
}
🎯 六、总结与展望
📝 6.1 本文要点回顾
着色器编程核心概念:
1. 着色器基础
- 渲染管线
- 顶点与片段着色器
- GLSL 语言
2. 经典效果
- 渐变与颜色
- 噪声与分形
- 波纹与扭曲
3. 音频驱动
- 频谱可视化
- 节拍响应
- 参数映射
4. Flutter 集成
- Shader API
- 着色器加载
- 实时更新
🚀 6.2 性能优化建议
着色器优化策略:
1. 减少分支
- 避免复杂条件
- 使用数学函数替代
- 向量化计算
2. 纹理优化
- 使用纹理图集
- 合理的纹理大小
- Mipmap 使用
3. 计算优化
- 预计算常量
- 减少循环次数
- 利用 GPU 并行
4. 内存管理
- 减少 uniform 数量
- 使用纹理存储数据
- 避免动态分配
💡 提示:着色器是现代图形编程的核心技术,通过GPU加速可以实现令人惊叹的视觉效果。掌握GLSL语言和渲染管线是创建高性能可视化的关键。