Skip to content

Instantly share code, notes, and snippets.

@abichinger
Created June 5, 2023 10:39
Show Gist options
  • Save abichinger/191c0c413d34d4c926455219f4ce0665 to your computer and use it in GitHub Desktop.
Save abichinger/191c0c413d34d4c926455219f4ce0665 to your computer and use it in GitHub Desktop.
Flutter pixel-by-pixel fade-in shader
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class PixelFade extends StatefulWidget {
final String shaderAsset;
final Widget? child;
final Duration duration;
final int width;
final int height;
const PixelFade({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 1000),
this.shaderAsset = 'shaders/pixel_fade.frag',
required this.width,
required this.height,
});
@override
State<PixelFade> createState() => _PixelFadeState();
}
class _PixelFadeState extends State<PixelFade> with TickerProviderStateMixin {
late final AnimationController controller;
late final Animation<double> animation;
ui.FragmentShader? shader;
@override
void initState() {
controller = AnimationController(
duration: widget.duration,
vsync: this,
);
animation = Tween<double>(
begin: 0,
end: 2,
).animate(controller);
super.initState();
Future.delayed(Duration.zero, () async {
var program = await ui.FragmentProgram.fromAsset(widget.shaderAsset);
shader = program.fragmentShader();
controller.forward();
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return ShaderMask(
shaderCallback: (bounds) {
var shader = this.shader;
if (shader == null) {
return const LinearGradient(
colors: [Colors.transparent, Colors.transparent],
).createShader(bounds);
}
shader.setFloat(0, animation.value);
shader.setFloat(1, bounds.width);
shader.setFloat(2, bounds.height);
shader.setFloat(3, widget.width.toDouble());
shader.setFloat(4, widget.height.toDouble());
return shader;
},
child: widget.child,
);
},
);
}
}
#version 460 core
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_size;
out vec4 fragColor;
float manhattan(vec2 pos){
// Calculate the normalized coordinates
vec2 uv=pos/u_resolution;
vec2 pixel=uv*u_size;
return floor(pixel.x)+floor(pixel.y);
}
void main(){
// Calculate manhatten distance
float distance=manhattan(gl_FragCoord.xy);
// Calculate the normalized distance
float ud=distance/(u_size.x+u_size.y);
// Calculate the fade value based on time and distance
float fade=smoothstep(0.,1.,u_time-ud);
// Set the output color with the fade value
fragColor=vec4(fade,fade,fade,fade);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment