Recréer des effets de lentille avec des shaders WebGPU 2025 — Guide d’optimisation pour appareils basse consommation
Publié: 29 sept. 2025 · Temps de lecture: 5 min · Par la rédaction Unified Image Tools
En 2025 WebGPU est stable dans les principaux navigateurs, ce qui évite de recourir à des frameworks WebGL lourds pour les effets visuels. Le défi pour les développeurs web est d’offrir lens flare, profondeur de champ et glow sans sacrifier la performance ou la batterie. Ce guide détaille des pipelines compute + render capables de tenir 60 fps sur appareils basse consommation ainsi que les techniques d’optimisation indispensables.
TL;DR
- Rendu en deux étapes : (1) compute pass pour détecter les sources lumineuses, (2) render pass pour la composition + blur.
- Downsampling par tuiles : calculez le flare à 1/4 de résolution et interpolez via
mix
lors de la composition. - Échantillonnage adaptatif : ajustez le nombre d’échantillons en fonction de
navigator.gpu.getPreferredCanvasFormat()
et de la Battery API. - Télémétrie GPU/CPU : transmettez les timestamp-query WebGPU à performance-guardian pour suivre le coût par frame.
- Accessibilité : respectez
prefers-reduced-motion
et préparez un fallback image statique.
Vue d’ensemble du pipeline
graph LR
A[Texture source] --> B[Compute Shader : détection de points lumineux]
B --> C[Compute Shader : accumulation par tuiles]
C --> D[Render Pass : bokeh & bloom]
D --> E[Post-traitement : tone mapping]
E --> F[Sortie canvas / texture]
Initialisation 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
est indisponible, retombez sur des timers CPU.
Shader d’extraction des sources lumineuses
// 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);
}
Exposez params.threshold
via un slider pour ajuster le rendu.
Accumulation par tuiles
// 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);
}
Choisissez des tuiles de 16 à 32 ; réduisez davantage la résolution sur matériel limité pour économiser la batterie.
Composition dans le 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 }
}]
})
Le fragment shader mélange les textures de bloom et de 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;
}
Optimisation énergie et performance
- Échelle de résolution : calculez la résolution de rendu avec
device.limits.maxTextureDimension2D
etwindow.devicePixelRatio
. - Contrôle des workgroups : batterie < 20 % ⇒ divisez le nombre de groupes par deux.
- Mise à jour des uniformes : ne poussez pas d’uniformes à chaque frame si la UI est inchangée.
- Timestamps : utilisez
writeTimestamp
et alertez si le GPU dépasse 5 ms.
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 })
passEncoder.writeTimestamp(querySet, 0)
// ... opérations de rendu ...
passEncoder.writeTimestamp(querySet, 1)
Accessibilité et fallback
Si prefers-reduced-motion: reduce
est actif, servez un PNG statique à la place du canvas animé.
.hero-visual {
background-image: url('/images/hero-static.png');
}
@media (prefers-reduced-motion: no-preference) {
.hero-visual {
background-image: none;
canvas { display: block; }
}
}
Mettez en cache l’actif statique dans votre PWA pour garantir une expérience hors-ligne stable.
Monitoring et expérimentation
Suivez ces métriques avec performance-guardian :
Métrique | Objectif | Action |
---|---|---|
Temps GPU par frame | < 6 ms | Ajuster la taille des workgroups |
Drain batterie (moyenne 5 min) | < 2% | Réduire l’échelle de résolution |
Variation LCP | ±100 ms | Basculer vers l’image statique |
CTR | +5 % | Déployer l’effet si la conversion progresse |
Checklist
- [ ] Fallback prévu pour les environnements sans WebGPU.
- [ ] Compute et render pass sont découplés.
- [ ] Les coûts GPU sont visibles via
timestamp-query
. - [ ]
prefers-reduced-motion
est respecté. - [ ] La Battery API ajuste la qualité en mode basse consommation.
- [ ] CTR et LCP sont suivis via RUM.
Résumé
WebGPU permet de produire des effets de lentille sophistiqués avec des shaders légers. Combinez calculs par tuiles et mise à l’échelle de la résolution pour conserver 60 fps même sur du matériel modeste. Mesurez l’impact visuel, la batterie et le LCP afin de déployer des effets équilibrant esthétique et performance.
Outils associés
Séquence en animation
Transformer des séquences d'images en GIF/WEBP/MP4 avec FPS ajustable.
Générateur de sprite sheet
Assembler des images en sprite sheet et exporter CSS/JSON avec données de frames.
Gardien des performances
Modélise les budgets de latence, suit les dépassements de SLO et exporte des preuves pour les revues d'incident.
Image Compressor
Batch compress with quality/max-width/format. ZIP export.
Articles liés
Boucles d'animation réactives à l'audio 2025 — Synchroniser visuels et son en direct
Guide pratique pour créer des boucles animées qui réagissent à l'audio sur le web et en app. Aborde pipeline d'analyse, accessibilité, performance et QA automatisée.
Effets ambiants contextualisés 2025 — Sensoriques environnementaux et garde-fous de performance
Workflow moderne pour ajuster les effets ambiants web/app à partir de la lumière, du son et du regard tout en respectant les budgets de sécurité, d’accessibilité et de performance.
Optimisation héro réactive au regard 2025 — Recomposer le hero en temps réel avec la télémétrie oculaire
Flux de travail pour capter des signaux d’eye-tracking et ajuster l’image hero à la volée. Couvre l’instrumentation, les modèles d’inférence, la conformité et l’intégration aux tests A/B.
Orchestration des effets ambiants holographiques 2025 — Synchroniser retail immersif et espaces virtuels
Orchestration unifiée des hologrammes, de la lumière et des capteurs pour aligner boutiques physiques et expériences virtuelles. Couvre contrôle des capteurs, gestion des presets et gouvernance.
Parallaxe légère et micro-interactions 2025 — Design compatible GPU
Guide de mise en œuvre pour livrer des effets de parallaxe et des micro-interactions sans dégrader les Core Web Vitals. Comprend patterns CSS/JS, cadres de mesure et tactiques d’A/B testing.
Comment créer des boucles transparentes 2025 — éliminer les bordures dans GIF/WEBP/APNG
Procédures de conception, composition et encodage pour rendre moins visibles les joints des animations en boucle. Prévenir les défaillances dans les animations UI courtes et les présentations héroïques tout en gardant la légèreté.