Flutter Animations : with Fragment shaders
Fragment shaders are programs run on Graphical Processing Unit to determine color of a pixel. Except Images (Bitmaps), All UI built with various Widgets are rendered with the help of Fragment Shaders.
Flutter (which is an awesome framework) has been impressive in its continuous expansion of API support over the years, from physics, shaders to the latest Flutter GPU package.
Let’s explore an example where shaders can be used as an alternative to commonly known animations, such as those based on Rive or the Flutter/Animation library. ShaderToy.com has many cool shaders. We are going to use this shader authored by Azorlogh. Our output is going to look like,
Using shaders results in reduced memory consumption, smoother playback with no jank, and improved frame rates. This snowfall shader is complex, yet it offers a glimpse into the possibilities that can be achieved through shader programming.
I’m one among the engineers building Zoho Tables mobile apps using Flutter, Check it out on App Store and Play Store.
Implementation
Create empty flutter project and a directory named “shaders” which is going to contain the shader code. In this directory, create a snowfall.frag copying the shader code from https://www.shadertoy.com/view/sllXDf.
This Shader code use ShaderToy apis and we have to modify to get it working on Flutter. Let’s refactor this snowfall.frag with below changes,
- At the top of file, put #version 120 which is the glsl version we are targetting. In the next line include runtime_glsl library #include <flutter/runtime_effect.glsl>.
- Rename mainImage to main, removing the params fragColor and fragCoord.
- Create a vec2 instance fragCoord and assign it with FlutterFragCoord().xy
- At the top of the file below include line, declare uniform iResolution, iTime, inverter and fragColor. We will be passing values from dart code when running this shader.
- Replace the usage of gl_FragCoord.xy with FlutterFragCoord().xy. This is done so to use local coordinates w.r.t size of our widget instead of screen space coordinates.
- Add this shader file to pubspec.yaml file like below
Finally, our shader file must look like https://github.com/SivaramSS/flutter_shader_guide/blob/main/shaders/snowfall.frag
Changes to main.dart file
- Create a Stateful widget class and a State class with TickerProviderStateMixin. This mixin lets us create a ticker instance and attach a callback that will be called for every frame (roughly called every 16ms).
- Create a Paint instance weatherBgPaint in this state class, to contain the compiled shader code at its member weatherBgPaint.shader.
- Create an async function to load the shader and invoke it in the initState method. Obtain the created shader from ShaderProgram and assign it to weatherBgPaint.shader.
- Create a ticker using createTicker(tickerCallback) passing a callback, which we are going to use it to refresh
- Create a CustomPainter class WeatherBg with constructor variables taking in Paint instance and repaint notifier. Note that repaint notifier instance is also passed to super constructor of CustomPainter. In our case this instance is a ValueNotifier containing the milliseconds counted since launch of the app.
- In ticker callback, we update the value of this notifier, which causes paint() function of our CustomPainter to be invoked.
- In the Paint() function we also pass the data required for our shader to function correctly. These are the iTime (milliseconds counted), iResolution (size of this CustomPaint widget, in this project it is full screen).
- And finally we send an integer value inverter. In iOS, shaders are rendered upside down. So we invert the Y coordinates before using it in the shader by multiplying with -1.
And that is it. I have made a few more changes adding a widget above this shader. Finally, the main.dart file should look like https://github.com/SivaramSS/flutter_shader_guide/blob/main/lib/main.dart
Note: For Android, the compiler will throw syntax errors as it tries to convert our glsl shader to sksl. Hence recommended to run the project in profile mode with enable-impeller flag.
flutter run --profile --enable-impeller
The output should look like the first gif on this blog. You can find complete code in this Github repo, to run the project. Happy coding!
Zoho Tables is a no code work management tool, for organizing and tracking data with real time collaboration, providing 50+ inbuilt solutions across industry verticals. Check it out!