Recriando efeitos de lente com shaders de imagem WebGPU 2025 — Guia de otimização para dispositivos de baixo consumo

Publicado: 29 de set. de 2025 · Tempo de leitura: 5 min · Pela equipe editorial da Unified Image Tools

Em 2025 o WebGPU está estável nos principais navegadores, eliminando a necessidade de stacks WebGL pesados para publicar efeitos visuais. A pergunta para engenheiros web é como entregar tratamentos de lente ricos (flares, profundidade de campo, glow) sem destruir desempenho ou bateria. Este guia percorre pipelines de compute + render que mantêm 60 fps em dispositivos de baixo consumo, além das táticas de otimização que sustentam o resultado.

TL;DR

  • Renderização em duas etapas: separe em (1) um compute pass para extrair bloqueadores de luz e (2) um render pass para composição + blur.
  • Downsampling baseado em tiles: calcule flares em um quarto da resolução e interpole com mix durante a composição.
  • Amostragem adaptativa: ajuste contagens de amostra usando navigator.gpu.getPreferredCanvasFormat() e sinais da Battery API.
  • Telemetria GPU/CPU: envie dados de timestamp-query do WebGPU para o performance-guardian para monitorar o custo de cada frame.
  • Salvaguardas de acessibilidade: respeite prefers-reduced-motion e ofereça um fallback com imagem estática.

Visão geral do pipeline

graph LR
    A[Textura de origem] --> B[Compute Shader: Detecção de pontos brilhantes]
    B --> C[Compute Shader: Acúmulo de tiles de flare]
    C --> D[Render Pass: Bokeh & Bloom]
    D --> E[Pós-processamento: Tone Mapping]
    E --> F[Saída em canvas / textura]

Inicializando o 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'
})

Se timestamp-query não estiver disponível, faça fallback para cronômetros de CPU.

Shader para extrair pontos brilhantes

// 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);
}

Exponha params.threshold em um controle deslizante para que designers ajustem o estilo rapidamente.

Acúmulo por tile

// 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);
}

Use tiles entre 16 e 32; em hardware modesto, reduza ainda mais para economizar energia.

Composição no 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 }
  }]
})

O fragment shader mistura texturas de bloom e 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;
}

Ajustes de energia e desempenho

  • Escala de resolução: derive a resolução do render usando device.limits.maxTextureDimension2D e window.devicePixelRatio.
  • Controle de workgroup: quando a bateria cair abaixo de 20%, reduza a contagem de workgroups pela metade.
  • Atualizações de uniform: evite enviar uniformes a cada frame se a UI não mudou.
  • Leituras de timestamp: use writeTimestamp e alerte se o tempo de GPU ultrapassar 5 ms.
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 })
passEncoder.writeTimestamp(querySet, 0)
// ... operações de render ...
passEncoder.writeTimestamp(querySet, 1)

Acessibilidade e fallbacks

Se prefers-reduced-motion: reduce estiver ativo, apresente um PNG estático em vez do canvas animado.

.hero-visual {
  background-image: url('/images/hero-static.png');
}

@media (prefers-reduced-motion: no-preference) {
  .hero-visual {
    background-image: none;
    canvas { display: block; }
  }
}

Armazene o asset estático no cache da PWA para manter a experiência offline consistente.

Monitoramento e experimentação

Acompanhe estas métricas com o performance-guardian:

MétricaMetaAção
Tempo de frame na GPU< 6 msAjuste o tamanho dos workgroups
Dreno de bateria (média de 5 min)< 2%Reduza a escala de resolução
Variação de LCP±100 msConsidere fallback para imagem estática
CTR+5%Graduar o efeito se a conversão subir

Checklist

  • [ ] Existe fallback para ambientes sem WebGPU.
  • [ ] Compute e render pass estão desacoplados.
  • [ ] Os custos de GPU são visíveis via timestamp-query.
  • [ ] prefers-reduced-motion é respeitado.
  • [ ] A Battery API reduz a qualidade em baixa energia.
  • [ ] CTR e LCP são monitorados via RUM.

Resumo

O WebGPU permite construir efeitos de lente sofisticados com shaders leves. Combine computação baseada em tiles e escalonamento de resolução para manter 60 fps em dispositivos modestos. Meça os visuais junto ao impacto em bateria e LCP para equilibrar encanto e desempenho nos lançamentos em produção.

Artigos relacionados

Animação

Animações em loop reativas ao áudio 2025 — Sincronize visuais com som ao vivo

Guia prático para criar loops animados que respondem a áudio em web e apps. Cobre pipeline de análise, acessibilidade, performance e QA automatizado.

Animação

Otimização de Animação UX 2025 — Diretrizes de Design para Melhorar Experiência e Reduzir Bytes

Graduação do GIF, uso adequado de vídeo/WebP/AVIF animados, design de loop e fluxo de movimento, guia de implementação que equilibra performance e acessibilidade.

Efeitos

Efeitos Ambientes Contextuais 2025 — Sensorização do Entorno com Guardrails de Performance

Workflow moderno para ajustar efeitos ambientes em web e apps segundo luz, áudio e rastreamento de olhar, mantendo limites de performance, segurança e acessibilidade.

Efeitos

Otimização de hero responsiva ao olhar 2025 — Reorquestre o hero em tempo real com telemetria ocular

Fluxo de trabalho para capturar sinais de eye-tracking e ajustar a imagem hero instantaneamente. Cobre instrumentação, modelos de inferência, conformidade e integração com testes A/B.

Efeitos

Orquestração de efeitos ambientais holográficos 2025 — Sincronizando retail imersivo e espaços virtuais

Orquestração unificada de hologramas, iluminação e sensores para alinhar lojas físicas a experiências virtuais. Abrange controle de sensores, gestão de presets e governança.

Efeitos

Parallax e microinterações leves 2025 — Design de experiência compatível com GPUs

Guia de implementação para entregar efeitos visuais ricos sem sacrificar Core Web Vitals. Cobre padrões CSS/JS, estruturas de medição e táticas de testes A/B para parallax e microinterações.