Efek lensa dengan shader WebGPU 2025 — Panduan optimasi untuk perangkat low-power
Diterbitkan: 29 Sep 2025 · Waktu baca: 4 mnt · Redaksi Unified Image Tools
WebGPU sudah stabil di browser utama pada 2025, jadi kita tak perlu lagi framework WebGL besar untuk efek gambar. Tantangan tim web: bagaimana menghadirkan lens flare, depth of field, dan glow tanpa membakar performa atau baterai. Panduan ini memaparkan pipeline compute + render yang menjaga 60 fps di perangkat low-power beserta taktik optimasinya.
TL;DR
- Rendering dua tahap: (1) compute pass untuk ekstraksi sumber cahaya, (2) render pass untuk komposisi + blur.
- Downsampling berbasis tile: hitung flare pada resolusi seperempat lalu interpolasi dengan
mix
saat komposisi. - Sampling adaptif: sesuaikan jumlah sampel lewat
navigator.gpu.getPreferredCanvasFormat()
dan sinyal Battery API. - Telemetri GPU/CPU: kirim data timestamp-query WebGPU ke performance-guardian untuk memantau biaya frame.
- Proteksi aksesibilitas: hormati
prefers-reduced-motion
dan siapkan fallback gambar statis.
Gambaran pipeline
graph LR
A[Texture sumber] --> B[Compute Shader: deteksi titik terang]
B --> C[Compute Shader: akumulasi tile flare]
C --> D[Render Pass: bokeh & bloom]
D --> E[Post processing: tone mapping]
E --> F[Keluaran canvas / texture]
Inisialisasi 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'
})
Jika timestamp-query
tak tersedia, kembali ke timer CPU.
Shader ekstraksi titik terang
// 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);
}
Ekspos params.threshold
lewat slider agar desainer dapat menyesuaikan efek.
Akumulasi 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);
}
Gunakan tile 16–32; di perangkat lemah turunkan resolusi untuk hemat daya.
Komposisi di 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 }
}]
})
Shader fragmen mencampur tekstur bloom dan 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;
}
Optimasi daya dan performa
- Skala resolusi: tentukan resolusi render dari
device.limits.maxTextureDimension2D
danwindow.devicePixelRatio
. - Pengaturan workgroup: saat baterai < 20%, kurangi jumlah workgroup setengahnya.
- Pembaruan uniform: hentikan update tiap frame bila UI tidak berubah.
- Timestamp: gunakan
writeTimestamp
dan beri peringatan jika waktu GPU > 5 ms.
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 })
passEncoder.writeTimestamp(querySet, 0)
// ... operasi render ...
passEncoder.writeTimestamp(querySet, 1)
Aksesibilitas dan fallback
Jika prefers-reduced-motion: reduce
, tampilkan PNG statis menggantikan canvas animasi.
.hero-visual {
background-image: url('/images/hero-static.png');
}
@media (prefers-reduced-motion: no-preference) {
.hero-visual {
background-image: none;
canvas { display: block; }
}
}
Simpan aset statis di cache PWA untuk menjaga tampilan saat offline.
Monitoring dan eksperimen
Pantau metrik berikut dengan performance-guardian:
Metrik | Target | Aksi |
---|---|---|
GPU frame time | < 6 ms | Atur ulang ukuran workgroup |
Pengurasan baterai (rata-rata 5 menit) | < 2% | Turunkan skala resolusi |
Variasi LCP | ±100 ms | Pertimbangkan fallback statis |
CTR | +5% | Naikkan efek bila konversi membaik |
Checklist
- [ ] Ada fallback untuk lingkungan tanpa WebGPU.
- [ ] Compute dan render pass terpisah.
- [ ] Biaya GPU terlihat lewat
timestamp-query
. - [ ]
prefers-reduced-motion
dihormati. - [ ] Battery API menurunkan kualitas di mode hemat energi.
- [ ] CTR dan LCP dimonitor lewat RUM.
Ringkasan
WebGPU memungkinkan efek lensa kompleks dengan shader ringan. Kombinasikan perhitungan berbasis tile dan skala resolusi untuk menjaga 60 fps di perangkat sederhana. Ukur dampak visual bersama baterai dan LCP agar perilisan produksi seimbang antara estetika dan performa.
Alat terkait
Urutan ke animasi
Ubah urutan gambar menjadi GIF/WEBP/MP4 dengan FPS yang dapat disesuaikan.
Generator sprite sheet
Gabungkan frame menjadi sprite sheet dan ekspor CSS/JSON dengan data frame.
tools.performanceGuardian
toolDescriptions.performanceGuardian
Kompresor gambar
Kompresi batch dengan kualitas/lebar maks/format. Ekspor ZIP.
Artikel terkait
Animasi loop reaktif audio 2025 — Sinkronkan visual dengan suara langsung
Panduan praktis membangun animasi loop yang merespons audio di web dan aplikasi. Mencakup pipeline analisis, aksesibilitas, performa, dan QA otomatis.
Efek Ambient Kontekstual 2025 — Sensor Lingkungan dan Guardrail Performa
Alur kerja modern untuk mengatur efek ambient web/aplikasi berdasarkan cahaya, audio, dan data pandangan. Panduan menegakkan batas performa, keamanan, dan aksesibilitas tanpa mengorbankan pengalaman.
Optimisasi hero responsif tatapan 2025 — Rekonfigurasi hero secara real time dengan telemetri eye-tracking
Alur kerja untuk menangkap sinyal eye-tracking dan menyesuaikan gambar hero seketika. Membahas instrumentasi, model inferensi, kepatuhan, serta integrasi dengan eksperimen A/B.
Orkestrasi efek ambient holografik 2025 — Menyatukan retail imersif dan ruang virtual
Orkestrasi terpadu hologram, pencahayaan, dan sensor untuk menyelaraskan toko fisik dengan pengalaman virtual. Mencakup kontrol sensor, manajemen preset, dan tata kelola.
Parallax dan mikro-interaksi ringan 2025 — Desain pengalaman ramah GPU
Panduan implementasi untuk menghadirkan efek visual kaya tanpa mengorbankan Core Web Vitals. Mencakup pola CSS/JS, kerangka pengukuran, dan taktik uji A/B untuk parallax dan mikro-interaksi.
Membuat Loop Mulus 2025 — Eliminasi praktis batas GIF/WEBP/APNG
Prosedur desain, komposisi, dan encoding untuk membuat sambungan animasi loop kurang mencolok. Mencegah kerusakan pada efek UI pendek dan hero sambil tetap ringan.