Recrear efectos de lente con shaders WebGPU 2025 — Guía de optimización para dispositivos de baja potencia
articles.published: 29 sept 2025 · articles.readingTime: 5 articles.minutes · articles.byEditorial
En 2025 WebGPU está estable en los principales navegadores, eliminando la necesidad de frameworks WebGL pesados para efectos visuales. La pregunta para los desarrolladores web es cómo ofrecer lens flare, profundidad de campo y glow sin destruir el rendimiento ni la batería. Esta guía repasa pipelines de compute + render que mantienen 60 fps en dispositivos de baja potencia junto con las tácticas de optimización necesarias.
TL;DR
- Renderizado en dos etapas: divide en (1) compute pass para extraer bloqueadores de luz y (2) render pass para composición + blur.
- Submuestreo por tiles: calcula el flare a un cuarto de la resolución y usa
mix
durante la composición para interpolar. - Muestreo adaptativo: ajusta el número de muestras con
navigator.gpu.getPreferredCanvasFormat()
y las señales de la Battery API. - Telemetría GPU/CPU: envía datos de timestamp-query de WebGPU a performance-guardian para vigilar el coste por frame.
- Salvaguardas de accesibilidad: respeta
prefers-reduced-motion
y prepara un fallback de imagen estática.
Vista general del pipeline
graph LR
A[Textura de origen] --> B[Compute Shader: detección de puntos brillantes]
B --> C[Compute Shader: acumulación por tiles]
C --> D[Render Pass: bokeh y bloom]
D --> E[Post-procesado: tone mapping]
E --> F[Salida a canvas / textura]
Inicialización de WebGPU
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'low-power' })
const device = await adapter?.requestDevice({
requiredFeatures: ['timestamp-query'],
requiredLimits: { maxTextureDimension2D: 4096 }
})
const context = canvas.getContext('webgpu')!
context.configure({
device,
format: navigator.gpu.getPreferredCanvasFormat(),
alphaMode: 'premultiplied'
})
Si timestamp-query
no está disponible, vuelve a temporizadores en CPU.
Shader de extracción de puntos brillantes
// shaders/bright-spot.wgsl
@group(0) @binding(0) var<storage, read> inputImage: array<vec4<f32>>;
@group(0) @binding(1) var<storage, read_write> brightMask: array<f32>;
@compute @workgroup_size(16, 16)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let idx = global_id.y * textureWidth + global_id.x;
if (idx >= arrayLength(&inputImage)) { return; }
let color = inputImage[idx];
let luminance = dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114));
brightMask[idx] = select(0.0, luminance, luminance > params.threshold);
}
Expón params.threshold
mediante un control UI para que diseño pueda ajustar la estética.
Acumulación por tiles
// shaders/flare-accumulate.wgsl
@group(0) @binding(0) var<storage, read> brightMask: array<f32>;
@group(0) @binding(1) var<storage, read_write> flareTiles: array<vec4<f32>>;
@compute @workgroup_size(8, 8)
fn main(@builtin(workgroup_id) group_id: vec3<u32>) {
let tileIndex = group_id.y * tileCountX + group_id.x;
var sum = vec4<f32>(0.0);
for (var y = 0u; y < TILE_SIZE; y = y + 1u) {
for (var x = 0u; x < TILE_SIZE; x = x + 1u) {
let idx = (group_id.y * TILE_SIZE + y) * textureWidth + (group_id.x * TILE_SIZE + x);
sum += vec4<f32>(brightMask[idx]);
}
}
flareTiles[tileIndex] = sum / f32(TILE_SIZE * TILE_SIZE);
}
Usa tiles de 16–32; en hardware limitado reduce la resolución para ahorrar energía.
Composición en el render pass
// pipeline/render.ts
const flarePipeline = device.createRenderPipeline({
vertex: { module: device.createShaderModule({ code: quadVert }) },
fragment: {
module: device.createShaderModule({ code: flareFrag }),
targets: [{ format: contextFormat }]
},
primitive: { topology: 'triangle-list' }
})
const pass = commandEncoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'load',
storeOp: 'store',
clearValue: { r: 0, g: 0, b: 0, a: 1 }
}]
})
El shader de fragmento mezcla las texturas de bloom y bokeh.
// shaders/flare.frag.wgsl
@group(0) @binding(0) var flareSampler: sampler;
@group(0) @binding(1) var flareTexture: texture_2d<f32>;
@group(0) @binding(2) var blurTexture: texture_2d<f32>;
@fragment
fn main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
let uv = pos.xy / vec2<f32>(canvasSize);
let flare = textureSample(flareTexture, flareSampler, uv);
let blur = textureSample(blurTexture, flareSampler, uv);
return mix(flare, blur, params.blurMix) * params.intensity;
}
Optimización de energía y rendimiento
- Escala de resolución: deriva la resolución de render con
device.limits.maxTextureDimension2D
ywindow.devicePixelRatio
. - Control de workgroups: con batería < 20% reduce a la mitad los grupos de trabajo.
- Actualizaciones de uniformes: omite subidas por frame excepto cuando cambie la UI.
- Lectura de timestamps: usa
writeTimestamp
y alerta si el tiempo GPU supera 5 ms.
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 })
passEncoder.writeTimestamp(querySet, 0)
// ... operaciones de render ...
passEncoder.writeTimestamp(querySet, 1)
Accesibilidad y fallback
Si prefers-reduced-motion: reduce
está activo, reemplaza el canvas animado por un PNG estático.
.hero-visual {
background-image: url('/images/hero-static.png');
}
@media (prefers-reduced-motion: no-preference) {
.hero-visual {
background-image: none;
canvas { display: block; }
}
}
Cachea el recurso estático en la PWA para una experiencia consistente offline.
Monitoreo y experimentos
Sigue estos indicadores con performance-guardian:
Métrica | Objetivo | Acción |
---|---|---|
Tiempo por frame GPU | < 6 ms | Ajustar tamaño de workgroups |
Consumo de batería (promedio 5 min) | < 2% | Bajar la escala de resolución |
Variación de LCP | ±100 ms | Evaluar fallback estático |
CTR | +5% | Escalar el efecto si mejora la conversión |
Checklist
- [ ] Hay fallback para entornos sin WebGPU.
- [ ] Los compute y render pass están desacoplados.
- [ ] Los costes GPU son visibles mediante
timestamp-query
. - [ ] Se respeta
prefers-reduced-motion
. - [ ] La Battery API reduce calidad en modo de bajo consumo.
- [ ] CTR y LCP se supervisan con RUM.
Resumen
WebGPU permite efectos de lente sofisticados con shaders ligeros. Combina cálculos por tiles y escalado de resolución para mantener 60 fps incluso en equipos modestos. Mide el impacto visual junto con batería y LCP para lanzar efectos que equilibren impacto estético y rendimiento.
Herramientas relacionadas
Secuencia a animación
Convierte secuencias de imágenes en GIF/WEBP/MP4 con FPS ajustable.
Generador de hoja de sprites
Combina fotogramas en una hoja de sprites y exporta CSS/JSON con datos de fotogramas.
Guardián del rendimiento
Modela presupuestos de latencia, rastrea incumplimientos de SLO y exporta evidencias para revisiones de incidentes.
Compresor de imágenes
Compresión por lotes con calidad/ancho máximo/formato. Exporta ZIP.
articles.related
Animaciones en bucle reactivas al audio 2025 — Sincronizar visuales con el sonido en vivo
Guía práctica para crear animaciones en bucle que responden a la entrada de audio en web y apps. Incluye pipeline de análisis, accesibilidad, rendimiento y QA automatizado.
Activos de Animación para Web Moderna — Diseño y Optimización 2025
Uso diferenciado de GIF/WebP/MP4/APNG/Lottie/SpriteSheet por casos. Comparación de tamaño, calidad, compatibilidad y facilidad de implementación, mostrando rutas óptimas desde producción hasta distribución.
Efectos Ambientales Contextuales 2025 — Sensores ambientales con límites de rendimiento y accesibilidad
Flujo moderno para ajustar efectos ambientales en web y apps usando luz, audio y mirada sin romper presupuestos de seguridad, accesibilidad y desempeño.
Optimización de héroe sensible a la mirada 2025 — Reconfigura el hero en tiempo real con telemetría ocular
Flujo de trabajo para capturar señales de eye-tracking y ajustar la imagen hero al instante. Cubre instrumentación, modelos de inferencia, cumplimiento normativo e integración con pruebas A/B.
Orquestación de efectos ambientales holográficos 2025 — Sincronizar retail inmersivo y espacios virtuales
Orquestación unificada de hologramas, iluminación y sensores para alinear tiendas físicas con entornos virtuales. Incluye control de sensores, gestión de presets y gobernanza.
Parallax ligero y microinteracciones 2025 — Diseño compatible con GPU
Guía para implementar efectos de parallax y microinteracciones sin sacrificar Core Web Vitals. Incluye patrones CSS/JS, marcos de medición y tácticas de pruebas A/B.