[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-shader-personalizzati-in-flutter-effetti-grafici-avanzati-con-glsl-e-fragmentprogram":3,"comments-article-shader-personalizzati-in-flutter-effetti-grafici-avanzati-con-glsl-e-fragmentprogram":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},88,"Shader personalizzati in Flutter: effetti grafici avanzati con GLSL e FragmentProgram","shader-personalizzati-in-flutter-effetti-grafici-avanzati-con-glsl-e-fragmentprogram","Come scrivere shader GLSL in Flutter con FragmentProgram: sfondi animati, effetti su widget esistenti con AnimatedSampler, gestione delle uniform e consigli di performance con Impeller.","## Perché gli shader in Flutter\n\nQuando serve un gradiente animato, un effetto dissolvenza, un blur creativo o una transizione \"liquida\", la strada classica è comporre widget e animazioni. Funziona, ma a un certo punto si scontra con un limite: ogni pixel dell'effetto viene calcolato dalla CPU o richiede molti layer di composizione.\n\nDal rilascio dell'API `FragmentProgram` (stabile da Flutter 3.7) è possibile scrivere **fragment shader in GLSL** e farli eseguire direttamente dalla GPU. Il risultato è codice grafico estremamente veloce, indipendente dalla complessità della UI, che con **Impeller** viene per giunta precompilato in fase di build — quindi senza il classico jank da compilazione shader che affliggeva Skia.\n\nIn questa guida vediamo come integrare uno shader in un progetto Flutter, come passargli dati dall'app, come applicarlo a un widget esistente e quali trappole evitare.\n\n## Cos'è un fragment shader\n\nUn fragment shader è un piccolo programma che viene eseguito **una volta per ogni pixel** della superficie da disegnare. Riceve in input la posizione del pixel e (opzionalmente) dei parametri chiamati *uniform*, e deve restituire un colore RGBA.\n\nIn Flutter il contratto è semplice:\n\n- il file ha estensione `.frag` e usa GLSL ES 3.0 (`#version 460 core` nella sintassi accettata dal compilatore di Flutter);\n- deve includere `flutter\u002Fruntime_effect.glsl`, che fornisce la funzione `FlutterFragCoord()`;\n- deve dichiarare un output `out vec4 fragColor`.\n\n## Il primo shader: gradiente animato\n\nCreiamo `shaders\u002Fgradient.frag`:\n\n```glsl\n#version 460 core\n#include \u003Cflutter\u002Fruntime_effect.glsl>\n\nprecision mediump float;\n\nuniform vec2 uSize;   \u002F\u002F dimensioni dell'area di disegno\nuniform float uTime;  \u002F\u002F tempo in secondi\nuniform vec4 uColorA;\nuniform vec4 uColorB;\n\nout vec4 fragColor;\n\nvoid main() {\n  \u002F\u002F coordinate normalizzate: (0,0) in alto a sinistra, (1,1) in basso a destra\n  vec2 uv = FlutterFragCoord().xy \u002F uSize;\n\n  float wave = 0.5 + 0.5 * sin(uv.x * 6.2831 + uTime);\n  float t = clamp(uv.y * 0.6 + wave * 0.4, 0.0, 1.0);\n\n  vec3 color = mix(uColorA.rgb, uColorB.rgb, t);\n  fragColor = vec4(color, 1.0);\n}\n```\n\nIl file va dichiarato nel `pubspec.yaml`, nella sezione `flutter`, sotto la chiave `shaders`:\n\n```yaml\nflutter:\n  uses-material-design: true\n  shaders:\n    - shaders\u002Fgradient.frag\n```\n\nA questo punto, in fase di build, il compilatore `impellerc` traduce il GLSL nel formato intermedio richiesto dalla piattaforma di destinazione (Metal, Vulkan, OpenGL). Nessuna compilazione a runtime.\n\n## Caricare ed eseguire lo shader\n\nIl caricamento è asincrono e va fatto una sola volta, tipicamente in `initState` o in un provider:\n\n```dart\nimport 'dart:ui' as ui;\n\nFuture\u003Cui.FragmentShader> loadShader() async {\n  final program = await ui.FragmentProgram.fromAsset('shaders\u002Fgradient.frag');\n  return program.fragmentShader();\n}\n```\n\nLo shader si usa come un normale `Shader` all'interno di un `Paint`, quindi il posto naturale è un `CustomPainter`:\n\n```dart\nclass GradientPainter extends CustomPainter {\n  GradientPainter({\n    required this.shader,\n    required this.time,\n    required this.colorA,\n    required this.colorB,\n    required Listenable repaint,\n  }) : super(repaint: repaint);\n\n  final ui.FragmentShader shader;\n  final double time;\n  final Color colorA;\n  final Color colorB;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    shader\n      ..setFloat(0, size.width)   \u002F\u002F uSize.x\n      ..setFloat(1, size.height)  \u002F\u002F uSize.y\n      ..setFloat(2, time)         \u002F\u002F uTime\n      ..setFloat(3, colorA.red \u002F 255)\n      ..setFloat(4, colorA.green \u002F 255)\n      ..setFloat(5, colorA.blue \u002F 255)\n      ..setFloat(6, colorA.alpha \u002F 255)\n      ..setFloat(7, colorB.red \u002F 255)\n      ..setFloat(8, colorB.green \u002F 255)\n      ..setFloat(9, colorB.blue \u002F 255)\n      ..setFloat(10, colorB.alpha \u002F 255);\n\n    canvas.drawRect(Offset.zero & size, Paint()..shader = shader);\n  }\n\n  @override\n  bool shouldRepaint(GradientPainter oldDelegate) => true;\n}\n```\n\n### Attenzione all'ordine delle uniform\n\nQuesto è l'errore più comune per chi inizia. `setFloat` non accetta il *nome* della uniform ma un **indice progressivo di float**: un `vec2` occupa due slot, un `vec4` ne occupa quattro. Se aggiungi una uniform in mezzo al file GLSL, tutti gli indici successivi slittano e l'effetto si rompe in modo silenzioso.\n\nDue accorgimenti utili:\n\n- documentare gli indici con un commento accanto a ogni `setFloat`, come nell'esempio;\n- oppure usare il pacchetto [`flutter_shaders`](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fflutter_shaders), che offre `setFloatUniforms` con un'API a callback sequenziale molto più leggibile:\n\n```dart\nimport 'package:flutter_shaders\u002Fflutter_shaders.dart';\n\nshader.setFloatUniforms((uniforms) {\n  uniforms\n    ..setSize(size)\n    ..setFloat(time)\n    ..setColor(colorA)\n    ..setColor(colorB);\n});\n```\n\n## Animare lo shader\n\nPer far avanzare il tempo serve un ticker. Un `AnimationController` in modalità `repeat()` è la soluzione più semplice, e passandolo come `repaint` al `CustomPainter` si evita di ricostruire l'intero widget tree a ogni frame:\n\n```dart\nclass AnimatedGradient extends StatefulWidget {\n  const AnimatedGradient({super.key});\n\n  @override\n  State\u003CAnimatedGradient> createState() => _AnimatedGradientState();\n}\n\nclass _AnimatedGradientState extends State\u003CAnimatedGradient>\n    with SingleTickerProviderStateMixin {\n  late final AnimationController _controller = AnimationController(\n    vsync: this,\n    duration: const Duration(seconds: 6),\n  )..repeat();\n\n  ui.FragmentShader? _shader;\n\n  @override\n  void initState() {\n    super.initState();\n    loadShader().then((s) {\n      if (mounted) setState(() => _shader = s);\n    });\n  }\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    _shader?.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final shader = _shader;\n    if (shader == null) return const SizedBox.shrink();\n\n    return AnimatedBuilder(\n      animation: _controller,\n      builder: (context, _) => CustomPaint(\n        painter: GradientPainter(\n          shader: shader,\n          time: _controller.value * 6.2831,\n          colorA: const Color(0xFF3A1C71),\n          colorB: const Color(0xFFFFAF7B),\n          repaint: _controller,\n        ),\n        size: Size.infinite,\n      ),\n    );\n  }\n}\n```\n\nNota il `_shader?.dispose()`: `FragmentShader` alloca risorse native e va rilasciato, altrimenti si accumulano leak nelle schermate che entrano ed escono di frequente.\n\n## Applicare uno shader a un widget esistente\n\nLa parte davvero interessante arriva quando lo shader non genera pixel dal nulla, ma **trasforma il contenuto già disegnato** da un sottoalbero di widget: dissolvenze, effetti di scioglimento, distorsioni su una card o su un'immagine.\n\nPer farlo serve un `sampler2D`, cioè una texture in ingresso:\n\n```glsl\n#version 460 core\n#include \u003Cflutter\u002Fruntime_effect.glsl>\n\nprecision mediump float;\n\nuniform vec2 uSize;\nuniform float uAmount;      \u002F\u002F 0 = originale, 1 = effetto pieno\nuniform sampler2D uTexture; \u002F\u002F il widget renderizzato\n\nout vec4 fragColor;\n\nvoid main() {\n  vec2 uv = FlutterFragCoord().xy \u002F uSize;\n  vec4 color = texture(uTexture, uv);\n\n  float gray = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));\n  fragColor = vec4(mix(color.rgb, vec3(gray) * color.a, uAmount), color.a);\n}\n```\n\nAttenzione: i colori che arrivano dal sampler sono **premoltiplicati per l'alpha**. Se ignori questo dettaglio, i bordi antialiasati e le zone trasparenti mostreranno aloni scuri o chiari. Nell'esempio il grigio viene rimoltiplicato per `color.a` proprio per restare coerente.\n\nLato Dart, catturare il sottoalbero come immagine è scomodo a mano; `flutter_shaders` mette a disposizione `AnimatedSampler`, che lo fa in modo efficiente sfruttando il layer di rendering:\n\n```dart\nAnimatedSampler(\n  (ui.Image image, Size size, Canvas canvas) {\n    shader\n      ..setFloat(0, size.width)\n      ..setFloat(1, size.height)\n      ..setFloat(2, amount)\n      ..setImageSampler(0, image);\n\n    canvas.drawRect(Offset.zero & size, Paint()..shader = shader);\n  },\n  child: const ProductCard(),\n)\n```\n\nGli indici dei sampler sono **separati** da quelli dei float: `setImageSampler(0, ...)` si riferisce al primo `sampler2D` dichiarato nel file, indipendentemente da quante uniform float ci sono prima.\n\n## Consigli di performance\n\nGli shader girano sulla GPU, ma non sono gratis: il codice viene eseguito milioni di volte al secondo. Alcune regole pratiche:\n\n- **Limita l'area di disegno.** Uno shader su tutto lo schermo costa molto più di uno shader su una card di 300x200. Applicalo solo dove serve.\n- **Evita i loop lunghi e i branch pesanti.** Cicli con molte iterazioni (tipici degli effetti di blur o noise fatti a mano) possono far crollare i frame sui dispositivi di fascia bassa.\n- **Usa `precision mediump float`** quando l'alta precisione non serve: su molte GPU mobile è sensibilmente più veloce.\n- **Isola il repaint** con un `RepaintBoundary` attorno al `CustomPaint`, così l'animazione dello shader non forza il ridisegno del resto della schermata.\n- **Un sampler in più costa.** `AnimatedSampler` implica il rendering del sottoalbero in una texture off-screen a ogni frame: se il child è statico, valuta di renderizzarlo una volta sola in una `ui.Image` e riutilizzarla.\n- **Profila con DevTools**, in particolare la timeline dei frame in modalità profile su un dispositivo reale: l'emulatore non è indicativo per il carico GPU.\n\n## Supporto e limiti attuali\n\n- **Mobile e desktop con Impeller**: pieno supporto, con shader precompilati al momento della build.\n- **Web**: gli shader funzionano con il renderer CanvasKit\u002Fskwasm; il vecchio renderer HTML non li supporta. Verifica sempre sul target reale.\n- **GLSL supportato**: è un sottoinsieme. Non sono disponibili vertex shader personalizzati, array di uniform di dimensione dinamica, `uniform` di tipo intero o operazioni tra frame (non c'è stato persistente). Tutto ciò che ti serve va passato come uniform a ogni frame.\n- **Debug**: non esiste un debugger passo-passo. Il flusso di lavoro più produttivo è prototipare l'effetto in un editor online come Shadertoy o Book of Shaders e poi adattarlo alla sintassi Flutter (sostituendo `fragCoord`\u002F`iResolution` con `FlutterFragCoord()` e `uSize`).\n\n## Quando (non) usarli\n\nGli shader sono lo strumento giusto per gradienti animati, effetti di rivelazione, distorsioni, transizioni tra schermate e \"glow\" personalizzati che con i widget richiederebbero decine di layer. Non sono la scelta giusta per animazioni di layout, micro-interazioni sui pulsanti o effetti già coperti da `AnimatedContainer`, `ShaderMask` con gradiente standard o `BackdropFilter`: in quei casi l'API di alto livello è più leggibile, più testabile e ugualmente performante.\n\nUsati con parsimonia, però, i fragment shader sono uno dei modi più efficaci per dare a un'app Flutter un'identità visiva che difficilmente si ottiene componendo solo widget standard.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F3d6cca91-5e3b-4774-9d1b-8ac3a2a35655.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1673526759337-c4d4c4c8bc72?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODg1ODA4OTJ8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Gradient Wallpapers","https:\u002F\u002Funsplash.com\u002F@gradientwallpapers","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-blurry-image-of-a-blue-and-purple-background-rlh8o7OaOU0",null,"published","2026-09-05T04:01:33+00:00","Shader GLSL in Flutter: guida a FragmentProgram","Guida pratica agli shader personalizzati in Flutter: FragmentProgram, uniform, AnimatedSampler, effetti su widget e consigli di performance con Impeller.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1789120580399]