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 y window.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étricaObjetivoAcción
Tiempo por frame GPU< 6 msAjustar tamaño de workgroups
Consumo de batería (promedio 5 min)< 2%Bajar la escala de resolución
Variación de LCP±100 msEvaluar 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.

articles.related

Animación

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.

Animación

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

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.

Efectos

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.

Efectos

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.

Efectos

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.