Skip to content

Commit b75f50e

Browse files
committed
Simple shader: Gradient based one.
1 parent 4bcc3f1 commit b75f50e

File tree

5 files changed

+262
-0
lines changed

5 files changed

+262
-0
lines changed

lib/filter-wave/wave_filter.dart

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import 'package:flutter/material.dart';
2+
import 'dart:math' as math;
3+
4+
class WaveWarpEffect extends StatefulWidget {
5+
final Widget child;
6+
final double waveHeight;
7+
final double waveWidth;
8+
final double speed;
9+
final double phase;
10+
11+
const WaveWarpEffect({
12+
Key? key,
13+
required this.child,
14+
this.waveHeight = 20.0,
15+
this.waveWidth = 100.0,
16+
this.speed = 1.0,
17+
this.phase = 0.0,
18+
}) : super(key: key);
19+
20+
@override
21+
_WaveWarpEffectState createState() => _WaveWarpEffectState();
22+
}
23+
24+
class _WaveWarpEffectState extends State<WaveWarpEffect>
25+
with SingleTickerProviderStateMixin {
26+
late AnimationController _controller;
27+
28+
@override
29+
void initState() {
30+
super.initState();
31+
_controller = AnimationController(
32+
vsync: this,
33+
duration: Duration(seconds: (5 / widget.speed).round()),
34+
)..repeat();
35+
}
36+
37+
@override
38+
void dispose() {
39+
_controller.dispose();
40+
super.dispose();
41+
}
42+
43+
@override
44+
Widget build(BuildContext context) {
45+
return AnimatedBuilder(
46+
animation: _controller,
47+
builder: (context, child) {
48+
return ClipPath(
49+
clipper: WaveClipper(
50+
animationValue: _controller.value,
51+
waveHeight: widget.waveHeight,
52+
waveWidth: widget.waveWidth,
53+
phase: widget.phase,
54+
),
55+
child: widget.child,
56+
);
57+
},
58+
);
59+
}
60+
}
61+
62+
class WaveClipper extends CustomClipper<Path> {
63+
final double animationValue;
64+
final double waveHeight;
65+
final double waveWidth;
66+
final double phase;
67+
68+
WaveClipper({
69+
required this.animationValue,
70+
required this.waveHeight,
71+
required this.waveWidth,
72+
required this.phase,
73+
});
74+
75+
@override
76+
Path getClip(Size size) {
77+
final path = Path();
78+
path.moveTo(0, 0);
79+
80+
// Create wave effect
81+
for (double x = 0; x < size.width; x++) {
82+
final double y = waveHeight *
83+
math.sin((x / waveWidth) * 2 * math.pi +
84+
(animationValue * 2 * math.pi) +
85+
phase);
86+
87+
path.lineTo(x, y);
88+
}
89+
90+
// Complete the path
91+
path.lineTo(size.width, size.height);
92+
path.lineTo(0, size.height);
93+
path.close();
94+
95+
return path;
96+
}
97+
98+
@override
99+
bool shouldReclip(WaveClipper oldClipper) =>
100+
animationValue != oldClipper.animationValue;
101+
}
102+
103+
// Usage Example:
104+
class WaveWarpDemo extends StatelessWidget {
105+
@override
106+
Widget build(BuildContext context) {
107+
return Scaffold(
108+
body: Center(
109+
child: WaveWarpEffect(
110+
waveHeight: 20,
111+
waveWidth: 100,
112+
speed: 1.0,
113+
phase: 0,
114+
child: Container(
115+
width: 300,
116+
height: 200,
117+
color: Colors.blue,
118+
child: Center(
119+
child: Text(
120+
'Wave Warp Effect',
121+
style: TextStyle(color: Colors.white),
122+
),
123+
),
124+
),
125+
),
126+
),
127+
);
128+
}
129+
}

lib/main.dart

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:fx_2_folder/butterfly-interactive/butterfly_interactive_demo.dar
1212
import 'package:fx_2_folder/decoration-bulbs/decoration_bulbs_demo.dart';
1313
import 'package:fx_2_folder/decoration-thread/decoration_thread.dart';
1414
import 'package:fx_2_folder/decoration-thread/decoration_thread_demo_1.dart';
15+
import 'package:fx_2_folder/filter-wave/wave_filter.dart';
1516
import 'package:fx_2_folder/fractal-glass/fractal_glass.dart';
1617
import 'package:fx_2_folder/fx_14_text_chaotic_spring/demo.dart';
1718
import 'package:fx_2_folder/gemini-splash/gemini_splash_demo.dart';
@@ -55,6 +56,7 @@ import 'package:fx_2_folder/primitives/primitives_demo.dart';
5556
import 'package:fx_2_folder/progress-bar/progress_bar_demo.dart';
5657
import 'package:fx_2_folder/progress-bar/progress_bar_demo_2.dart';
5758
import 'package:fx_2_folder/scroll-progress/scroll_progress_demo.dart';
59+
import 'package:fx_2_folder/shader-learning/shader_1.dart';
5860
import 'package:fx_2_folder/slider/slider.dart';
5961
import 'package:fx_2_folder/slider/slider_demo.dart';
6062
import 'package:fx_2_folder/smoke/smoke.dart';
@@ -431,6 +433,12 @@ class HomeScreen extends StatelessWidget {
431433
appBarColor: Colors.black,
432434
isFullScreen: true,
433435
),
436+
AnimationExample(
437+
title: "Simple Shader",
438+
builder: (context) => SimpleShaderExample(),
439+
appBarColor: Colors.black,
440+
isFullScreen: true,
441+
),
434442
];
435443

436444
HomeScreen({super.key});

lib/shader-learning/shader_1.dart

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import 'dart:ui';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter/scheduler.dart';
5+
6+
class SimpleShaderExample extends StatefulWidget {
7+
@override
8+
State<SimpleShaderExample> createState() => _SimpleShaderExampleState();
9+
}
10+
11+
class _SimpleShaderExampleState extends State<SimpleShaderExample>
12+
with SingleTickerProviderStateMixin {
13+
late final Ticker _ticker;
14+
final Stopwatch _stopwatch = Stopwatch()..start();
15+
16+
@override
17+
void initState() {
18+
super.initState();
19+
_ticker = createTicker((_) {
20+
setState(() {}); // Force rebuild to update the shader time
21+
})
22+
..start();
23+
}
24+
25+
@override
26+
void dispose() {
27+
_stopwatch.stop();
28+
_ticker.dispose();
29+
super.dispose();
30+
}
31+
32+
@override
33+
Widget build(BuildContext context) {
34+
return MediaQuery.removePadding(
35+
context: context,
36+
removeTop: true,
37+
removeBottom: true,
38+
child: SafeArea(
39+
left: false,
40+
top: false,
41+
right: false,
42+
bottom: false,
43+
child: FutureBuilder<FragmentShader>(
44+
future: _loadShader(),
45+
builder: (context, snapshot) {
46+
if (!snapshot.hasData) {
47+
return const Center(
48+
child: CircularProgressIndicator(),
49+
);
50+
}
51+
52+
return LayoutBuilder(
53+
builder: (context, constraints) {
54+
return CustomPaint(
55+
painter: ShaderPainter(
56+
snapshot.data!,
57+
resolution:
58+
Size(constraints.maxWidth, constraints.maxHeight),
59+
time: _stopwatch.elapsedMilliseconds / 1000.0,
60+
),
61+
size: Size(constraints.maxWidth, constraints.maxHeight),
62+
);
63+
},
64+
);
65+
},
66+
),
67+
),
68+
);
69+
}
70+
71+
Future<FragmentShader> _loadShader() async {
72+
final program = await FragmentProgram.fromAsset('shaders/simple.frag');
73+
return program.fragmentShader();
74+
}
75+
}
76+
77+
class ShaderPainter extends CustomPainter {
78+
final FragmentShader shader;
79+
final Size resolution;
80+
final double time;
81+
82+
ShaderPainter(
83+
this.shader, {
84+
required this.resolution,
85+
required this.time,
86+
});
87+
88+
@override
89+
void paint(Canvas canvas, Size size) {
90+
shader.setFloat(0, resolution.width);
91+
shader.setFloat(1, resolution.height);
92+
shader.setFloat(2, time);
93+
94+
final paint = Paint()..shader = shader;
95+
canvas.drawRect(
96+
Rect.fromLTWH(0, 0, size.width, size.height),
97+
paint,
98+
);
99+
}
100+
101+
@override
102+
bool shouldRepaint(covariant ShaderPainter oldDelegate) {
103+
return oldDelegate.time != time;
104+
}
105+
}

pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ flutter:
7070

7171
shaders:
7272
- shaders/motion_blur.frag
73+
- shaders/simple.frag

shaders/simple.frag

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#version 460 core
2+
3+
#include <flutter/runtime_effect.glsl>
4+
5+
uniform vec2 uResolution;
6+
uniform float uTime;
7+
8+
out vec4 fragColor;
9+
10+
void main() {
11+
// Get normalized coordinates (0 to 1)
12+
vec2 uv = FlutterFragCoord().xy/uResolution.xy;
13+
14+
// Create a simple gradient that moves with time
15+
vec3 color = vec3(uv.x + sin(uTime), uv.y, 0.5);
16+
17+
// Output the color
18+
fragColor = vec4(color, 1.0);
19+
}

0 commit comments

Comments
 (0)