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
ewindow.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étrica | Meta | Ação |
---|---|---|
Tempo de frame na GPU | < 6 ms | Ajuste o tamanho dos workgroups |
Dreno de bateria (média de 5 min) | < 2% | Reduza a escala de resolução |
Variação de LCP | ±100 ms | Considere 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.
Ferramentas relacionadas
Sequência para animação
Transforme sequências de imagens em GIF/WEBP/MP4 com FPS ajustável.
Gerador de sprite sheet
Combine quadros em uma sprite sheet e exporte CSS/JSON com dados de quadros.
tools.performanceGuardian
toolDescriptions.performanceGuardian
Compressor de Imagem
Comprimir em lote com qualidade/largura máxima/formato. Exporta ZIP.
Artigos relacionados
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.
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 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.
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.
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.
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.