Escribir y usar fragment shaders
Cómo crear y usar fragment shaders para crear efectos visuales personalizados en tu aplicación Flutter.
Los shaders personalizados se pueden usar para proporcionar efectos gráficos enriquecidos más allá de los proporcionados por el SDK de Flutter. Un shader es un programa creado en un lenguaje pequeño, similar a Dart, conocido como GLSL, y ejecutado en la GPU del usuario.
Los shaders personalizados se añaden a un proyecto de Flutter
listándolos en el archivo pubspec.yaml,
y se obtienen usando la API FragmentProgram.
Añadir shaders a una aplicación
#Los shaders, en forma de archivos GLSL con la extensión .frag,
deben declararse en la sección shaders del archivo pubspec.yaml
de tu proyecto.
La herramienta de línea de comandos de Flutter compila el shader
a su formato de backend adecuado,
y genera los metadatos necesarios en tiempo de ejecución.
El shader compilado se incluye luego en la aplicación al igual que un asset.
flutter:
shaders:
- shaders/myshader.frag
Al ejecutarse en modo debug, los cambios en un programa de shader activan la recompilación y actualizan el shader durante el Hot Reload o Hot Restart.
Los shaders de paquetes se añaden a un proyecto
con el prefijo packages/$pkgname en el nombre del programa de shader
(donde $pkgname es el nombre del paquete).
Cargar shaders en tiempo de ejecución
#Para cargar un shader en un objeto FragmentProgram en tiempo de ejecución,
usa el constructor FragmentProgram.fromAsset.
El nombre del asset es el mismo que la ruta al shader
proporcionada en el archivo pubspec.yaml.
void loadMyShader() async {
var program = await FragmentProgram.fromAsset('shaders/myshader.frag');
}
El objeto FragmentProgram se puede usar para crear
una o más instancias de FragmentShader.
Un objeto FragmentShader representa un programa fragment
junto con un conjunto particular de uniforms (parámetros de configuración).
Los uniforms disponibles dependen de cómo se haya definido el shader.
void updateShader(Canvas canvas, Rect rect, FragmentProgram program) {
var shader = program.fragmentShader();
shader.setFloat(0, 42.0);
canvas.drawRect(rect, Paint()..shader = shader);
}
API Canvas
#Los fragment shaders se pueden usar con la mayoría de las APIs de Canvas
estableciendo Paint.shader.
Por ejemplo, al usar Canvas.drawRect
el shader se evalúa para todos los fragmentos dentro del rectángulo.
Para una API como Canvas.drawPath
con un trazo de línea,
el shader se evalúa para todos los fragmentos dentro de la línea del trazo.
Algunas APIs, como Canvas.drawImage, ignoran el valor del shader.
void paint(Canvas canvas, Size size, FragmentShader shader) {
// Draws a rectangle with the shader used as a color source.
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..shader = shader,
);
// Draws a stroked rectangle with the shader only applied to the fragments
// that lie within the stroke.
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()
..style = PaintingStyle.stroke
..shader = shader,
)
}
API ImageFilter
#Los fragment shaders también se pueden usar con la API
ImageFilter.
Esto permite usar fragment shaders personalizados con la
clase ImageFiltered
o la clase BackdropFilter
para aplicar shaders al contenido ya renderizado.
ImageFilter
proporciona un constructor, ImageFilter.shader,
para crear un ImageFilter
con un fragment shader personalizado.
Widget build(BuildContext context, FragmentShader shader) {
return ClipRect(
child: SizedBox(
width: 300,
height: 300,
child: BackdropFilter(
filter: ImageFilter.shader(shader),
child: Container(
color: Colors.transparent,
),
),
),
);
}
Al usar ImageFilter
con BackdropFilter, se puede usar un
ClipRect para limitar el área afectada por el ImageFilter. Sin un
ClipRect
el BackdropFilter
se aplicará a toda la pantalla.
Los fragment shaders de ImageFilter reciben algunos uniforms automáticamente desde el
motor. El valor de sampler2D en el índice 0 se establece en la imagen de entrada del filtro, y
los valores de float en los índices 0 y 1 se establecen en el ancho y alto de la imagen.
Tu shader debe especificar este constructor para aceptar estos valores (por ejemplo, un
sampler2D y un vec2), pero no debes establecerlos desde tu código Dart.
Al dirigirse a OpenGLES, las coordenadas y de la textura se invertirá, por lo que el fragment shader debe des-invertir las UV al muestrear desde texturas proporcionadas por el motor.
#version 460 core
#include <flutter/runtime_effect.glsl>
out vec4 fragColor;
// These uniforms are automatically set by the engine.
uniform vec2 u_size;
uniform sampler2D u_texture;
void main() {
vec2 uv = FlutterFragCoord().xy / u_size;
#ifdef IMPELLER_TARGET_OPENGLES
// When sampling from u_texture on OpenGLES the y-coordinates will be flipped.
uv.y = 1.0 - uv.y;
#endif
vec4 color = texture(u_texture, uv);
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
fragColor = vec4(vec3(gray), color.a);
}
Creación de shaders
#Los fragment shaders se escriben como archivos fuente GLSL.
Por convención, estos archivos tienen la extensión .frag.
(Flutter no admite vertex shaders,
que tendrían la extensión .vert).
Cualquier versión de GLSL desde la 460 hasta la 100 es compatible,
aunque algunas características disponibles están restringidas.
El resto de los ejemplos de este documento utilizan la versión 460 core.
Los shaders están sujetos a las siguientes limitaciones cuando se usan con Flutter:
- UBOs y SSBOs no son compatibles
sampler2Des el único tipo de sampler compatible- Solo la versión de dos argumentos de
texture(sampler y uv) es compatible - No se pueden declarar entradas varying adicionales
- Se ignoran todas las sugerencias de precisión al dirigirse a Skia
- Los enteros sin signo y los booleanos no son compatibles
Uniforms
#Un programa fragment se puede configurar definiendo
valores de uniform en la fuente del shader GLSL
y luego estableciendo estos valores en Dart para
cada instancia de fragment shader.
Los uniforms de punto flotante con los tipos GLSL float, vec2, vec3 y
vec4
se establecen usando el método FragmentShader.setFloat
o
FragmentShader.getUniformFloat.
Los valores de sampler GLSL, que utilizan el
tipo sampler2D, se establecen usando el método FragmentShader.setImageSampler
o
FragmentShader.getImageSampler.
El índice correcto para cada valor de uniform se determina por el orden en que se
definen los valores de uniform en el programa fragment. Para tipos de datos compuestos por
múltiples floats, como un vec4, debes llamar a FragmentShader.setFloat
o UniformFloatSlot.set
una vez para cada valor.
Por ejemplo, dadas las siguientes declaraciones de uniforms en un programa fragment GLSL:
uniform float uScale;
uniform sampler2D uTexture;
uniform vec2 uMagnitude;
uniform vec4 uColor;
El código Dart correspondiente para inicializar estos valores de uniform es el siguiente:
class Foobar {
late final UniformFloatSlot _scale;
late final List<UniformFloatSlot> _magnitude;
late final List<UniformFloatSlot> _color;
late final ImageSamplerSlot _texture;
void setUp(FragmentShader shader) {
_scale = shader.getUniformFloat('uScale');
_magnitude = List<UniformFloatSlot>.generate(2, (int index) {
return shader.getUniformFloat('uMagnitude', index);
});
_color = List<UniformFloatSlot>.generate(4, (int index) {
return shader.getUniformFloat('uColor', index);
});
_texture = shader.getImageSampler('uTexture');
}
void update(Color color, Image image) {
_scale.set(23);
_magnitude[0].set(114);
_magnitude[1].set(83);
_color[0].set(color.r * color.a);
_color[1].set(color.g * color.a);
_color[2].set(color.b * color.a);
_color[3].set(color.a);
_texture.set(image);
}
}
Al usar FragmentShader.setFloat
ten en cuenta que los índices no cuentan el
uniform sampler2D. Este uniform se establece por separado con
FragmentShader.setImageSampler, con el índice comenzando de nuevo en 0.
Cualquier uniform flotante que se deje sin inicializar tendrá un valor por defecto de 0.0.
Los datos de reflexión generados por el compilador de shaders de Flutter se pueden auditar con los siguientes comandos para ver aspectos como los desplazamientos (offsets) de los uniforms.
cd $FLUTTER
# Generate the .sl file.
`find bin/ -name impellerc` \
--runtime-stage-metal \
--iplr \
--input=path/to/myshader.frag \
--sl=foo.sl \
--spirv=foo.spirv \
--include=engine/src/flutter/impeller/compiler/shader_lib/ \
--input-type=frag
# Convert the .sl file to .json
flatc \
--json \
./engine/src/flutter/impeller/runtime_stage/runtime_stage.fbs \
-- ./foo.sl
# View results
cat foo.json
Posición actual
#El shader tiene acceso a un valor varying que contiene las coordenadas locales para
el fragmento particular que se está evaluando. Usa esta función para calcular
efectos que dependan de la posición actual, a la cual se puede acceder
importando la librería flutter/runtime_effect.glsl y llamando a la
función FlutterFragCoord. Por ejemplo:
#include <flutter/runtime_effect.glsl>
void main() {
vec2 currentPos = FlutterFragCoord().xy;
}
El valor devuelto por FlutterFragCoord es distinto de gl_FragCoord.
gl_FragCoord proporciona las coordenadas del espacio de la pantalla y generalmente debe
evitarse para garantizar que los shaders sean consistentes entre backends. Al dirigirse a un
backend de Skia, las llamadas a gl_FragCoord se reescriben para acceder a las coordenadas
locales, pero esta reescritura no es posible con Impeller.
Colores
#No existe un tipo de datos integrado para los colores. En su lugar, comúnmente se
representan como un vec4 donde cada componente corresponde a uno de los canales de
color RGBA.
La salida única fragColor espera que el valor de color esté normalizado para estar
en el rango de 0.0 a 1.0 y que tenga alpha premultiplicado. Esto es
diferente de los colores típicos de Flutter que usan una codificación de valores de 0-255 y
tienen alpha sin premultiplicar.
Samplers
#Un sampler proporciona acceso a un objeto Image de dart:ui. Esta imagen se puede
obtener ya sea de una imagen decodificada o de parte de la aplicación usando
Scene.toImageSync
o Picture.toImageSync.
Ejemplo de uso de samplers en GLSL
##include <flutter/runtime_effect.glsl>
uniform vec2 uSize;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec2 uv = FlutterFragCoord().xy / uSize;
fragColor = texture(uTexture, uv);
}
Por defecto, la imagen usa TileMode.clamp
para determinar cómo se comportan los valores fuera
del rango de [0, 1]. La personalización del modo mosaico (tile mode) no es compatible
y debe ser emulada en el shader.
Ejemplo de toImageSync
#
class SDFPainter {
SDFPainter(this.sdfShader, this.renderShader);
FragmentShader sdfShader;
FragmentShader renderShader;
Image? _sdf;
bool isDirty = false;
double radius = 0.5;
void paint(Canvas canvas, Size size) {
if (_sdf == null || isDirty) {
final recorder = PictureRecorder();
final subCanvas = Canvas(recorder);
final paint = Paint()..shader = sdfShader;
sdfShader.setFloat(0, size.width);
sdfShader.setFloat(1, size.height);
sdfShader.setFloat(2, radius);
subCanvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
final picture = recorder.endRecording();
_sdf = picture.toImageSync(size.width.toInt(), size.height.toInt());
isDirty = false;
}
renderShader.setFloat(0, size.width);
renderShader.setFloat(1, size.height);
renderShader.setImageSampler(0, _sdf!);
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..shader = renderShader,
);
}
}
Consideraciones de rendimiento
#Al dirigirse al backend de Skia, cargar el shader podría ser costoso ya que debe compilarse al shader específico de la plataforma adecuado en tiempo de ejecución. Si tienes la intención de usar uno o más shaders durante una animación, considera precargar en caché (precaching) los objetos del programa fragment antes de iniciar la animación.
Puedes reutilizar un objeto FragmentShader a través de los fotogramas; esto es más eficiente
que crear un nuevo FragmentShader para cada fotograma.
Para obtener una guía más detallada sobre cómo escribir shaders eficientes, consulta Writing efficient shaders en GitHub.
Otros recursos
#Para más información, aquí tienes algunos recursos.
- The Book of Shaders por Patricio Gonzalez Vivo y Jen Lowe
- Shader toy, un entorno colaborativo de pruebas para shaders
simple_shader, un proyecto de ejemplo sencillo de fragment shaders en Flutterflutter_shaders, un paquete que simplifica el uso de fragment shaders en Flutter
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.