diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 7d0d76459..c6445bea5 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -281,6 +281,12 @@ updates: interval: "daily" labels: - "autosubmit" + - package-ecosystem: "pub" + directory: "simple_sdf/" + schedule: + interval: "daily" + labels: + - "autosubmit" - package-ecosystem: "pub" directory: "simple_shader/" schedule: diff --git a/README.md b/README.md index c7ad27a04..2e2f645d0 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Googler's, you can freely add samples to the [flutter/demos] repository. * [`navigation_and_routing`] - A sample that shows how to use [go_router] API to handle common navigation scenarios. * [`pedometer`] - A demo of a plugin that leverages FFIgen & JNIgen to call platform APIs directly from Dart code. * [`platform_design`] - This sample project shows a Flutter app that maximizes application code reuse while adhering to different design patterns on Android and iOS. +* [`simple_sdf`] - A simple [Flutter fragment shaders] sample project showing how to draw Signed Distance Functions with the FragmentShader API. * [`simple_shader`] - A simple [Flutter fragment shaders] sample project. * [`testing_app`] - A sample app that shows different types of testing in Flutter. * [`web_embedding`] - This directory contains examples of how to embed Flutter in web apps (without iframes). @@ -123,6 +124,7 @@ If you run into a bug in one of the samples, please file an issue in the [`navigation_and_routing`]: ./navigation_and_routing [`pedometer`]: ./pedometer [`platform_design`]: ./platform_design +[`simple_sdf`]: ./simple_sdf [`simple_shader`]: ./simple_shader [`testing_app`]: ./testing_app [`web_embedding`]: ./web_embedding diff --git a/pubspec.yaml b/pubspec.yaml index e425740e3..a4a3adbf4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ workspace: - platform_channels - platform_design - platform_view_swift + - simple_sdf - simple_shader - testing_app - tool diff --git a/simple_sdf/.gitignore b/simple_sdf/.gitignore new file mode 100644 index 000000000..24476c5d1 --- /dev/null +++ b/simple_sdf/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/simple_sdf/README.md b/simple_sdf/README.md new file mode 100644 index 000000000..564c2cdde --- /dev/null +++ b/simple_sdf/README.md @@ -0,0 +1,9 @@ +# `simple_sdf` + +A simple [Flutter fragment shaders][] sample project showing how to draw Signed Distance Functions with the FragmentShader API. + +Use `flutter create --no-overwrite .` to initialize the project. + + [Flutter fragment shaders]: https://docs.flutter.dev/development/ui/advanced/shaders + +![Screenshot of the `simple_sdf` app](screenshot.png) diff --git a/simple_sdf/analysis_options.yaml b/simple_sdf/analysis_options.yaml new file mode 100644 index 000000000..13d6fe105 --- /dev/null +++ b/simple_sdf/analysis_options.yaml @@ -0,0 +1 @@ +include: package:analysis_defaults/flutter.yaml diff --git a/simple_sdf/lib/main.dart b/simple_sdf/lib/main.dart new file mode 100644 index 000000000..4a1bf38c4 --- /dev/null +++ b/simple_sdf/lib/main.dart @@ -0,0 +1,59 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_shaders/flutter_shaders.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Simple SDF Demo', + theme: ThemeData(colorSchemeSeed: Colors.blue), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Simple SDF Demo')), + body: ShaderBuilder( + assetKey: 'shaders/SDF.frag', + (context, shader, child) => CustomPaint( + size: MediaQuery.of(context).size, + painter: ShaderPainter(shader: shader), + ), + child: const Center(child: CircularProgressIndicator()), + ), + ); + } +} + +class ShaderPainter extends CustomPainter { + ShaderPainter({required this.shader}); + ui.FragmentShader shader; + + @override + void paint(Canvas canvas, Size size) { + shader.setFloat(0, size.width); + shader.setFloat(1, size.height); + + final paint = Paint()..shader = shader; + canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/simple_sdf/pubspec.yaml b/simple_sdf/pubspec.yaml new file mode 100644 index 000000000..4aebd30f4 --- /dev/null +++ b/simple_sdf/pubspec.yaml @@ -0,0 +1,23 @@ +name: simple_sdf +description: Using a shader, simply. +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_shaders: ^0.1.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true + shaders: + - shaders/SDF.frag diff --git a/simple_sdf/screenshot.png b/simple_sdf/screenshot.png new file mode 100644 index 000000000..405769e3d Binary files /dev/null and b/simple_sdf/screenshot.png differ diff --git a/simple_sdf/shaders/SDF.frag b/simple_sdf/shaders/SDF.frag new file mode 100644 index 000000000..327222662 --- /dev/null +++ b/simple_sdf/shaders/SDF.frag @@ -0,0 +1,62 @@ +#version 460 core + +#include + +precision mediump float; + +uniform vec2 resolution; +out vec4 fragColor; + +vec3 pink = vec3(255, 105, 180) / 255; + +// dot2 and sdHeart from https://iquilezles.org/articles/distfunctions2d/ +// +// The MIT License +// Copyright © 2015 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: The above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +// THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// https://www.youtube.com/c/InigoQuilez +// https://iquilezles.org + +float dot2(vec2 v) { return dot(v, v); } +float sdHeart(in vec2 p) { + p.x = abs(p.x); + + if (p.y + p.x > 1.0) + return sqrt(dot2(p - vec2(0.25, 0.75))) - sqrt(2.0) / 4.0; + return sqrt(min(dot2(p - vec2(0.00, 1.00)), + dot2(p - 0.5 * max(p.x + p.y, 0.0)))) * + sign(p.x - p.y); +} + +void main() { + vec2 st = FlutterFragCoord().xy / resolution.xy; + // Remap coordinates. + // Flutter normalized coordinates have range [0,1] but sdHeart expects [-1,1]. + st = (st - vec2(0.5)) * vec2(2.0); + // Center the heart. + // sdHeart is written such that the bottom point is at (0,0) and it's about 1 + // unit tall. + st.y -= 0.5; + // Invert Y coordinate. + st.y *= -1; + + // Calculate the color of this pixel according to the heart SDF, using + // smoothstep to anti-alias the edges. + vec3 color = vec3(0.0); + color = mix(pink, color, smoothstep(0.01, 0.02, sdHeart(st))); + + fragColor = vec4(color, 1); +} diff --git a/simple_sdf/test/widget_test.dart b/simple_sdf/test/widget_test.dart new file mode 100644 index 000000000..794023811 --- /dev/null +++ b/simple_sdf/test/widget_test.dart @@ -0,0 +1,9 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:simple_sdf/main.dart'; + +void main() { + testWidgets('Smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + }); +}