Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Fragment shaders pipelines and Camera based post processing #3404

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/games/crystal_ball/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# crystal_ball

A game to showcase the shader pipeline API in Flame.
1 change: 1 addition & 0 deletions examples/games/crystal_ball/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:flame_lint/analysis_options_with_dcm.yaml
106 changes: 106 additions & 0 deletions examples/games/crystal_ball/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:ui';

import 'package:flame/camera.dart' as c;
import 'package:flame/game.dart';
import 'package:flame/shader_pipeline.dart';
import 'package:flutter/material.dart';
import 'package:flutter_shaders/flutter_shaders.dart';

const (double, double) kCameraSize = (900, 1600);

void main() async {
runApp(const GamePage());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add this example to the examples page too :)

}

class GamePage extends StatefulWidget {
const GamePage({super.key});

@override
State<GamePage> createState() => _GamePageState();
}

class _GamePageState extends State<GamePage> {
late final Future<ui.FragmentProgram> program =
ui.FragmentProgram.fromAsset('shaders/the_ball.frag');

@override
Widget build(BuildContext context) {
return FutureBuilder(
future: program,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}

return GameWidget(
game: CrystalBallGame(
ballProgram: snapshot.data!,
),
);
},
);
}
}

class CrystalBallGame extends FlameGame {
CrystalBallGame({
required this.ballProgram,
}) : super(
camera: c.CameraComponent.withFixedResolution(
width: kCameraSize.$1,
height: kCameraSize.$2,
hudComponents: [
CrystalBallPipelineStep(
program: ballProgram,
),
],
),
);

final ui.FragmentProgram ballProgram;
}

class CrystalBallPipelineStep extends SPipelineStep {
CrystalBallPipelineStep({
required this.program,
super.samplingPasses = 0,
}) : super(size: Vector2(900, 1600));

c.Viewport get camera => c.CameraComponent.currentCamera!.viewport;

final FragmentProgram program;

@override
void postProcess(List<ui.Image> samples, Size size, Canvas canvas) {
final uvBall = Vector2(0.5, 0.5);
final shader = program.fragmentShader();
shader.setFloatUniforms((value) {
value
..setSize(size)
..setVector64(uvBall)
..setVector64(Vector2(0.0, 0.0))
..setFloat(.1)
..setFloat(.3);
});

canvas
..save()
..drawRect(
Offset.zero & size,
Paint()
..shader = shader
..blendMode = BlendMode.lighten,
)
..restore();
}
}

extension on UniformsSetter {
renancaraujo marked this conversation as resolved.
Show resolved Hide resolved
void setVector64(Vector vector) {
setFloats(Float32List.fromList(vector.storage));
}
}
21 changes: 21 additions & 0 deletions examples/games/crystal_ball/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: crystal_ball
description: "A game to showcase the shader pipeline API in Flutter."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flame?

publish_to: 'none'
version: 0.1.0
environment:
sdk: ">=3.4.0 <4.0.0"
dependencies:
flame: ^1.22.0
flutter:
sdk: flutter
flutter_shaders: ^0.1.3
dev_dependencies:
flame_lint: ^1.2.1
flutter:
uses-material-design: true
shaders:
- shaders/platforms.frag
- shaders/the_ball.frag
- shaders/ground.frag
- shaders/fog.frag

83 changes: 83 additions & 0 deletions examples/games/crystal_ball/shaders/fog.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#version 460 core

precision mediump float;

#include <flutter/runtime_effect.glsl>

uniform vec2 uSize;
uniform float uGroundPos;
uniform float uGroundAdd;
uniform float uFade;
uniform float uTime;

out vec4 fragColor;


// Thanks iq:
// https://www.shadertoy.com/view/lsf3WH
// Copyright © 2013 Inigo Quilez
float hash(vec2 p) {
p = 50.0*fract( p*0.3183099 + vec2(0.71,0.113));
return -1.0+2.0*fract( p.x*p.y*(p.x+p.y) );
}

float noise( in vec2 p ) {
vec2 i = floor( p );
vec2 f = fract( p );
vec2 u = f*f*f*(f*(f*6.0-15.0)+10.0);

return mix( mix( hash( i + vec2(0.0,0.0) ),
hash( i + vec2(1.0,0.0) ), u.x),
mix( hash( i + vec2(0.0,1.0) ),
hash( i + vec2(1.0,1.0) ), u.x), u.y);
}

float fractalNoise(vec2 uv) {
float f = 0.0;
uv *= 8.0;
mat2 m = mat2( 1.6, 1.2, -1.2, 1.6 );
f = 0.5000*noise( uv ); uv = m*uv;
f += 0.2500*noise( uv ); uv = m*uv;
f += 0.1250*noise( uv ); uv = m*uv;
f += 0.0625*noise( uv ); uv = m*uv;

f = 0.5 + 0.5*f;

return f;
}


void fragment(vec2 cuv, vec2 pos, inout vec4 color, float timeMultiplier) {
vec2 p = pos.xy / uSize.xy;
vec2 uv = p*vec2(uSize.x/uSize.y,1.0) ;

float waterline = uGroundPos;
float fade = uFade;

float tr = step(waterline - fade,uv.y);
tr *= smoothstep(waterline - fade, waterline, uv.y);
uv.y -= uGroundAdd;
uv *= 1.;
uv.x += uTime * timeMultiplier;

float f = fractalNoise(uv);
f *= tr;
f*= 0.65;

f = pow(f, 1.8);
color = vec4( vec3(0.8, 0.4, 1.0) * f, f);
}

void main() {
vec2 pos = FlutterFragCoord().xy;
vec2 uv = pos / uSize;
vec4 color;

fragment(uv, pos, color, 0.015);


vec4 color2;
fragment(uv, pos, color2, -0.08 );

fragColor = color + color2;
}
45 changes: 45 additions & 0 deletions examples/games/crystal_ball/shaders/ground.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#version 460 core

precision highp float;

#include <flutter/runtime_effect.glsl>

uniform vec2 uSize;
uniform float uWaterLevel;
uniform float uTime;

uniform sampler2D tGameCanvas;

out vec4 fragColor;

vec4 fragment(vec2 uv) {
vec4 waterColor = vec4(1.0);
vec2 reflectedUv = uv.xy;
if (uv.y >= uWaterLevel) {
// invert y to equivalent position above water
reflectedUv.y = 2.0 * uWaterLevel - reflectedUv.y;
// magnify the reflection
reflectedUv.y = uWaterLevel + (reflectedUv.y - uWaterLevel) * 3;
// add horizontal waves
reflectedUv.x = reflectedUv.x +(sin((uv.y-uWaterLevel/1)+ uTime *1.0)*0.01);
// add vertical waves
reflectedUv.y = reflectedUv.y + cos(1./(uv.y-uWaterLevel)+ uTime *1.0)*0.03;

// Magnification can create uv outside of [0,1] range
if (reflectedUv.y <=0) {
return vec4(0.0);
}

// fade out reflection
waterColor = vec4(1.0);
waterColor.rgb *= 1 - ((uv.y-uWaterLevel) / (1.0-uWaterLevel));
}

return texture(tGameCanvas, reflectedUv) * waterColor;
}

void main() {
vec2 pos = FlutterFragCoord().xy;
vec2 uv = pos / uSize;
fragColor = fragment(uv);
}
117 changes: 117 additions & 0 deletions examples/games/crystal_ball/shaders/platforms.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#version 460 core

precision highp float;

#include <flutter/runtime_effect.glsl>

uniform vec2 uSize;
uniform vec4[18] platformsAB;
uniform vec3[18] colorsL;
uniform vec3[18] colorsR;
uniform float[18] glowGamas;


//uniform sampler2D tTexture;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used?


out vec4 fragColor;

const float PI = 3.1415926;

float getLuma(vec3 color) {
vec3 weights = vec3(0.299, 0.587, 0.114);
return dot(color, weights);
}

float random(float x) {
return fract(sin(x) * 43758.5453);
}

vec3 addNoiseToGradient(vec3 colorL, vec3 colorR, float distanceToA, float distanceToB) {

// Calculate the interpolation factor with noise
float t = distanceToA / (distanceToA + distanceToB);

float luma = getLuma(mix(colorL, colorR, t));
float noiseFactor = 0.1 * (pow(luma, luma));

// Add noise to the interpolation factor
t += (random(gl_FragCoord.x) - 0.5) * noiseFactor;

// Ensure the factor is within the [0, 1] range
t = clamp(t, 0.0, 1.0);

// Interpolate the colors with the noisy factor
vec3 gradient = mix(colorL, colorR, t);


return gradient;
}

vec4 processPlatform(vec2 uv, vec2 a, vec2 b, vec3 colorL, vec3 colorR, float glowGama) {
float distanceToA = distance(a, uv);
float distanceToB = distance(b, uv);

vec3 gradient = addNoiseToGradient(colorL, colorR, distanceToA, distanceToB);

float gama = glowGama;

if (uv.y > a.y) {
gama = 3.0;
}

float light = acos(dot(normalize(a - uv), normalize(b - uv))) / PI;

light = clamp(light, 0.0, 1.0);

if (uv.y > a.y) {
light *= 0.7;
}
gama = clamp(gama, 0.0, 100.0);

vec3 col = pow(light, gama) * gradient;

float alpha = pow(light* 0.1, gama);

return vec4(col, alpha);
}

void fragment(vec2 uv, vec2 pos, inout vec4 color) {
color = vec4(0, 0, 0, 0);

for (int i = 0; i < 18; i++) {
vec4 platform = platformsAB[i];

vec2 a = platform.xy;
vec2 b = platform.zw;

if (a==b) {
continue;
}

vec3 colorL = colorsL[i];
vec3 colorR = colorsR[i];

color.rgba += processPlatform(uv, a, b, colorL, colorR, glowGamas[i]);
}
}

void main() {
vec2 pos = FlutterFragCoord().xy;
vec2 uv = pos / uSize;
vec4 color;

fragment(uv, pos, color);

// post process
float mdf = 0.07;
float noise = (fract(sin(dot(uv, vec2(12.9898, 78.233)*2.0)) * 43758.5453));
float luma = getLuma(color.rgb);

if (luma > 0.01) {
float llm = smoothstep(0.0, 0.4, luma);
mdf *= llm * 0.3;
color += noise * mdf;
}

fragColor = color;
}
Loading
Loading