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 dan window.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:

MetrikTargetAksi
GPU frame time< 6 msAtur ulang ukuran workgroup
Pengurasan baterai (rata-rata 5 menit)< 2%Turunkan skala resolusi
Variasi LCP±100 msPertimbangkan 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.

Artikel terkait

Animasi

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

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.

Efek

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.

Efek

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.

Efek

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.

Animasi

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.