Currently applying a shader directly to a Sprite could be easier.
I see two different way
Shaders can help customizing the game experience.
I don't see any.
Just shaders stays non trivial.
I already made a try using extension on a project.
Heres what I made.
applyShader
extension on Sprite {
/// Apply permanently a shader to an image
/// useful if you don't want to use the orginal image anymore
Future<void> applyShader(FragmentProgram fragmentProgram) async {
var widgetFrameSampler2D = ImageShader(
image,
TileMode.repeated,
TileMode.repeated,
Matrix4.identity().storage,
);
final shader = fragmentProgram.shader(
floatUniforms: Float32List.fromList(
<double>[
image.width.toDouble(),
image.height.toDouble(),
1.0,
],
),
samplerUniforms: [widgetFrameSampler2D],
);
var painter = Paint()..shader = shader;
var pictureRecorder = PictureRecorder();
var canvas = Canvas(pictureRecorder);
canvas.drawRect(
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
painter,
);
var picture = pictureRecorder.endRecording();
var shaderedImage = await picture.toImage(image.width, image.height);
image = shaderedImage;
}
}
renderWithShader
This method allows you to draw your shader using the image of the sprite as the input.
extension on Sprite {
// apply a shader to a single render cycle
void renderWithShader(
FragmentProgram fragmentProgram,
Canvas canvas, {
Float32List? floatUniforms,
Vector2? position,
Vector2? size,
Anchor anchor = Anchor.topLeft,
}) {
var widgetFrameSampler2D = ImageShader(
image,
TileMode.repeated,
TileMode.repeated,
Matrix4.identity().storage,
);
final shader = fragmentProgram.shader(
floatUniforms: floatUniforms,
samplerUniforms: [widgetFrameSampler2D],
);
var painter = Paint()..shader = shader;
position ??= Vector2.zero();
size ??= srcSize;
position.setValues(
position.x - (anchor.x * size.x),
position.y - (anchor.y * size.y),
);
final drawRect = position.toPositionedRect(size);
canvas.drawRect(drawRect, painter);
}
}
With SpriteAnimationGroupComponent
On SpriteAnimationGroupComponent this allows to apply shader to all sprites or just the current one
extension on SpriteAnimationGroupComponent {
/// Apply permanently a shader to all sprites of the group
Future<void> applyShader(
SpriteAnimation animation,
FragmentProgram fragmentProgram,
) async {
for (int i = 0; i < animation.frames.length; i++) {
await animation.frames[i].sprite.applyShader(fragmentProgram);
}
}
// render a shader to a single render cycle
void renderWithShader(
FragmentProgram fragmentProgram,
Canvas canvas, {
Float32List? floatUniforms,
}) {
animation?.getSprite().renderWithShader(
fragmentProgram,
canvas,
// position: position,
size: size,
floatUniforms: floatUniforms,
);
}
}
Example of calling renderWithShader in a Sprite
This exemple shows how I ended calling it inside the render
function of a SpriteAnimationGroupComponent
.
@override
void render(Canvas canvas) async {
super.render(canvas); // <- we want to render the initial sprite
var sprite = animation!.getSprite();
var image = sprite.image;
// this is all the params we needs to correctly pick the color of the image and manipulate it
var params = Float32List.fromList(
<double>[
image.width.toDouble(), // input image size
image.height.toDouble(), // input image size
size.x, // output drawing size
size.y, // output drawing size
animation!.currentIndex.toDouble(), // sprite index drawing size
sprite.srcSize.x, // textureSize
sprite.srcSize.y, // textureSize
],
);
renderWithShader(
_shadowShader,
canvas,
floatUniforms: params,
);
}
Hope this is understandable.
Maybe there's already another way of doing this and I missed it.
Good job working on Flame, that's an amazing package π.
Let me know if that may interests you to include it in Flame
Gautier - π€
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too