shader 1.0.0 shader: ^1.0.0 copied to clipboard
Command-line application for compiling GLSL shaders into Flutter-compatible SPR-V byte code.
Examples #
Table of Contents #
Red shader #
The red-shader.glsl
is very simple and turns all pixels red:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
It can be compiled via shader
executable:
shader --use-remote --to-dart
After compilation the app can use it:
import 'dart:ui';
import 'package:flutter/material.dart';
/// Import file generated by cli
import 'package:flutter_app/shader/red_shader_sprv.dart';
void main() {
runApp(const MaterialApp(home: Page()));
}
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<FragmentProgram>(
/// Use the generated loader function here
future: redShaderFragmentProgram(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// Shader is loading
return const CircularProgressIndicator();
}
/// Shader is ready to use
return SizedBox.expand(
child: CustomPaint(
painter: RedShaderPainter(snapshot.data!),
),
);
})),
);
}
}
/// Customer painter that makes use of the shader
class RedShaderPainter extends CustomPainter {
RedShaderPainter(this.fragmentProgram);
final FragmentProgram fragmentProgram;
@override
void paint(Canvas canvas, Size size) {
/// Create paint using a shader
final paint = Paint()..shader = fragmentProgram.shader();
/// Draw a rectangle with the shader-paint
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is RedShaderPainter &&
oldDelegate.fragmentProgram == fragmentProgram) {
/// Do not repaint when painter has same set of properties
return false;
}
return true;
}
}
Color shader #
The color-shader.glsl
uses input parameters (uniforms) and turns all pixels in that color:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) uniform vec3 color;
void main() {
fragColor = vec4(color.rgb, 1.0);
}
It can be compiled via shader
executable:
shader --use-remote --to-dart
After compilation the app can use it:
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
/// Import file generated by cli
import 'package:flutter_app/shader/color_shader_sprv.dart';
void main() {
runApp(const MaterialApp(home: Page()));
}
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<FragmentProgram>(
/// Use the generated loader function here
future: colorShaderFragmentProgram(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// Shader is loading
return const CircularProgressIndicator();
}
/// Shader is ready to use
return TweenAnimationBuilder<Color?>(
/// Flutter animation: tween color over 10s
tween: ColorTween(begin: Colors.green, end: Colors.blue),
duration: const Duration(seconds: 10),
builder: (context, color, _) {
return SizedBox.expand(
child: CustomPaint(
/// Pass color into shader as input parameter
painter: RedShaderPainter(snapshot.data!, color!),
),
);
},
);
})),
);
}
}
/// Customer painter that makes use of the shader
class RedShaderPainter extends CustomPainter {
RedShaderPainter(this.fragmentProgram, this.color);
final FragmentProgram fragmentProgram;
final Color color;
@override
void paint(Canvas canvas, Size size) {
/// Create paint using a shader
final paint = Paint()
..shader = fragmentProgram.shader(
/// Specify input parameter (uniforms)
floatUniforms: Float32List.fromList([
// Set red as relative color for first float
color.red / 255.0,
// Set green as relative color for second float
color.green / 255.0,
// Set blue as relative color for third float
color.blue / 255.0,
]));
/// Draw a rectangle with the shader-paint
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is RedShaderPainter &&
oldDelegate.fragmentProgram == fragmentProgram &&
oldDelegate.color == color) {
/// Do not repaint when painter has same set of properties
return false;
}
return true;
}
}
Image scale shader #
The image-scale-shader.glsl
uses an image texture as an sampler uniform along with a float uniform:
#version 320 es
precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) uniform float scale;
layout(location = 1) uniform sampler2D image;
void main() {
vec2 coords = (0.0015 / scale) * (gl_FragCoord.xy);
vec4 textureColor = texture(image, coords);
fragColor = textureColor;
}
It can be compiled via shader
executable:
shader --use-remote --to-dart
After compilation the app can use it:
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app/shader/image_scale_shader_sprv.dart';
void main() {
runApp(const MaterialApp(home: Page()));
}
/// Will combine loading multiple things
class PainterNeeds {
final ImageShader imageShader;
final FragmentProgram fragmentProgram;
PainterNeeds(this.imageShader, this.fragmentProgram);
}
/// Loads JPEG image and the [FragmentProgram]
Future<PainterNeeds> loadPainterNeeds() async {
final asset = await rootBundle.load("assets/image.jpg");
final image = await decodeImageFromList(asset.buffer.asUint8List());
/// Create ImageShader that will provide a GLSL sampler
final ImageShader imageShader = ImageShader(
image,
// Specify how image repetition is handled for x and y dimension
TileMode.repeated,
TileMode.repeated,
// Transformation matrix (identity matrix = no transformation)
Matrix4.identity().storage,
);
return PainterNeeds(imageShader, await imageScaleShaderFragmentProgram());
}
class Page extends StatelessWidget {
const Page({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<PainterNeeds>(
/// Use the generated loader function here
future: loadPainterNeeds(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
/// Shader is loading
return const CircularProgressIndicator();
}
/// Shader is ready to use
return SizedBox.expand(
child: CustomPaint(
painter: ImageScaleShaderPainter(snapshot.data!),
),
);
})),
);
}
}
/// Customer painter that makes use of the shader
class ImageScaleShaderPainter extends CustomPainter {
ImageScaleShaderPainter(this.painterNeeds);
final PainterNeeds painterNeeds;
@override
void paint(Canvas canvas, Size size) {
/// Create paint using a shader
final paint = Paint()
..shader = painterNeeds.fragmentProgram.shader(
floatUniforms: Float32List.fromList([
// scale uniform
0.1,
]),
samplerUniforms: [
painterNeeds.imageShader,
],
);
/// Draw a rectangle with the shader-paint
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
if (oldDelegate is ImageScaleShaderPainter &&
oldDelegate.painterNeeds == painterNeeds) {
/// Do not repaint when painter has same set of properties
return false;
}
return true;
}
}