Gaussian Blur
Separable Gaussian blur
Quick Start
Loading...
Source
Loading...
Documentation
Gaussian Blur
Separable Gaussian blur using two render passes. Takes a source GPUTexture and writes the blurred result to a caller-provided target GPUTexture. Uses fragment shaders with hardware texture sampling for bilinear filtering and edge clamping.
Usage
import { createGaussianBlur } from './gaussian-blur';
const blur = createGaussianBlur(device, { format: 'rgba8unorm' });
const output = device.createTexture({
size: [1024, 768],
format: 'rgba8unorm',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
});
// Blur a source texture
blur.apply(sourceTexture, output, { radius: 8, sigma: 4.0 });
// output now contains the blurred result
// Clean up
blur.destroy();
API
createGaussianBlur(device, options?)
Returns a GaussianBlur instance.
| Option | Type | Default | Description |
|---|---|---|---|
format |
GPUTextureFormat |
'rgba8unorm' |
Texture format (must match source) |
blur.apply(source, target, options?)
Blurs the source texture and writes the result to the target texture.
source—GPUTextureto read from (must haveTEXTURE_BINDINGusage)target—GPUTextureto write to (must haveRENDER_ATTACHMENTusage)
| Option | Type | Default | Description |
|---|---|---|---|
radius |
number |
8 |
Blur radius in pixels (0–32) |
sigma |
number |
radius / 2 |
Gaussian standard deviation |
When radius is 0, the source is copied to the target without blurring.
blur.destroy()
Releases internal textures and buffers. Does not destroy source or target textures (you own them).
Algorithm
Separable Gaussian blur splits a 2D convolution into two 1D passes:
- Horizontal pass — blur along X, write to an intermediate texture
- Vertical pass — blur along Y from the intermediate, write to the target
This reduces the cost from O(radius²) to O(radius) per pixel. For a radius of 32, that's 65 samples per pixel per pass instead of 4225 for a single 2D pass.
Kernel weights are precomputed on the CPU using the Gaussian function exp(-x²/2σ²) and normalized so they sum to 1.0. Only radius + 1 weights are stored (center + one side) since the kernel is symmetric.
Why render passes instead of compute? Fragment shaders get hardware texture sampling (bilinear filtering, clamp-to-edge) for free. This simplifies edge handling — no manual bounds checking. It also demonstrates that modules aren't compute-only.
WGSL loading
The default import uses Vite's ?raw suffix:
import shaderSource from './gaussian-blur.wgsl?raw';
If you're not using a bundler, load via fetch:
const shaderSource = await fetch(new URL('./gaussian-blur.wgsl', import.meta.url)).then((r) =>
r.text()
);
Modifying
Replace with a box blur
Replace the computeKernel function in gaussian-blur.ts with uniform weights:
function computeBoxKernel(radius: number): Float32Array {
const size = radius + 1;
const kernel = new Float32Array(size);
const weight = 1.0 / (radius * 2 + 1);
kernel.fill(weight);
return kernel;
}
The shader doesn't change — it just reads different weights.
Add a bloom effect
Modify the fragment shader to threshold bright pixels before blurring:
let sample = textureSample(source_tex, source_sampler, in.uv);
let brightness = dot(sample.rgb, vec3f(0.2126, 0.7152, 0.0722));
if (brightness < threshold) { return vec4f(0.0); }
Then blend the blurred result additively with the original image.
Apply blur to a specific region
Pass a mask texture as an additional binding. In the fragment shader, multiply the blur weight by the mask value at each sample position.
Chain with other post-processing
Feed the blur output as the input to another module:
blur.apply(sourceTexture, blurredTexture, { radius: 8 });
otherEffect.apply(blurredTexture);
Switch to compute shaders
For very large radii or tiled/chunked processing, compute shaders can be more efficient because they allow shared memory tiling. Replace the render passes with compute dispatches that read from a storage texture and write to another. You'll need to handle edge clamping manually.
Further Reading
Resources on Gaussian blur, separable convolutions, and GPU image filtering.
Core Theory
Heckbert, "Filtering by Repeated Integration" (SIGGRAPH 1986) Foundational paper on efficient image filtering techniques, including the separability of Gaussian kernels that makes two-pass blur possible. https://dl.acm.org/doi/10.1145/15886.15921
Deriche, "Fast Algorithms for Low-Level Vision" (1990) Introduces recursive (IIR) Gaussian filtering that achieves O(1) cost per pixel regardless of kernel size. A useful alternative for very large radii. https://hal.inria.fr/inria-00074778
GPU Implementation
GPU Gems 3, Chapter 40: "Incremental Computation of the Gaussian" Practical GPU implementation techniques for Gaussian filtering, including incremental weight computation and shared memory optimizations. https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-40-incremental-computation-gaussian
Efficient Gaussian Blur with Linear Sampling (Rastergrid) Explains the technique of sampling between texels to halve the number of texture fetches in a Gaussian blur, leveraging hardware bilinear filtering. https://www.rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
Intel, "An Investigation of Fast Real-Time GPU-Based Image Blur Algorithms" Comprehensive comparison of GPU blur techniques: box blur, Gaussian, Kawase, dual filtering. Benchmarks and trade-offs for each approach. https://www.intel.com/content/www/us/en/developer/articles/technical/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html
Post-Processing Applications
Kawase, "Frame Buffer Postprocessing Effects in DOUBLE-S.T.E.A.L" (GDC 2003) Introduces the Kawase blur — a multi-pass approach using progressively larger sample offsets that approximates Gaussian blur with fewer passes. Common in game engines. https://www.gdcvault.com/play/1023163/Frame-Buffer-Postprocessing-Effects
Jimenez, "Next Generation Post Processing in Call of Duty: Advanced Warfare" (SIGGRAPH 2014) State of the art in real-time post-processing, including bloom, depth of field, and motion blur. Shows how Gaussian blur fits into a modern rendering pipeline. https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
Bloom
- Karis, "Real Shading in Unreal Engine 4" (SIGGRAPH 2013) Describes Unreal's bloom pipeline: threshold bright pixels, progressively downsample with blur, then composite. The Gaussian blur module can serve as the blur step. https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
General References
Wikipedia, "Gaussian blur" Clear mathematical description of the Gaussian function, separability proof, and relationship to the normal distribution. https://en.wikipedia.org/wiki/Gaussian_blur
WebGPU Specification — Render Passes The official spec for render pass encoding, color attachments, load/store operations, and fragment shader outputs used by this module. https://www.w3.org/TR/webgpu/#render-passes