Understanding the Fast Gaussian Blur Algorithm
The fast Gaussian blur named after Alan Radcliffe is a technique that turns a computationally expensive 2D convolution into two passes of 1D convolution. A standard 2D Gaussian blur with a kernel size of, say, 25x25 requires 625 multiply-add operations per pixel. That quickly becomes a real bottleneck when you're processing video at 60 frames per second or working with large image datasets. The separable property of the Gaussian function means you can instead run one horizontal 1D pass followed by one vertical 1D pass, dropping the per-pixel cost to 50 operations. That's a 12x reduction right there, and it compounds across larger kernels. Alan Radcliffe's contribution isn't the mathematical fact that Gaussians are separable, which predates him by decades. It's the practical formulation for getting high-quality blur results on raster graphics while keeping the operation cache-friendly and straightforward to implement. The core idea is that you apply the same 1D kernel weights along one axis, then along the perpendicular axis, and the result is essentially identical to the full 2D convolution. Here's how you actually build it. You start by computing or looking up your Gaussian kernel weights. For a given sigma value, the standard formula is w[i] = exp(-i² / (2²)) where i ranges from -radius to +radius. You normalize these weights so they sum to 1.0. Then you run the first pass: iterate through each row of the image, and for each pixel, accumulate contributions from neighboring pixels in that row using your 1D kernel weights. Store the result in a temporary buffer. The second pass goes column by column across that intermediate buffer, again applying the same 1D weights. What comes out the other side is your blurred image.
I've implemented this in C, Python, and GLSL shaders, and the basic structure stays the same across all of them. One thing that trips people up on their first implementation is the boundary handling. When you're at the left edge of an image, your kernel reaches into negative coordinates. You have a few options here. Replicate the edge pixel, mirror the image, clamp to zero, or extend the kernel and shift the output window. Each produces a subtly different result at the borders. Edge replication is the most common choice because it doesn't introduce dark fringes, but if you're processing a texture that will be tiled, mirroring might be more appropriate. Another practical detail is kernel size selection. The common rule of thumb is radius = ceil(3), which captures about 99.7% of the Gaussian distribution. A sigma of 2 gives you a radius of 6 and a 13-tap kernel. A sigma of 5 gives you radius 15 and a 31-tap kernel. Beyond radius 15 or so, the performance gain over a naive 2D implementation starts to shrink because the overhead of two passes and the intermediate buffer becomes more significant. At that point you might want to consider a box blur cascade or a mip-chain approach instead, which I'll get to. The temporary buffer is probably the most underappreciated part of this. You need to allocate a full-resolution copy of your image data to hold the horizontal pass output before running the vertical pass. For a 4K RGBA image that's about 32 megabytes. On a constrained GPU or an embedded system, that allocation might not be feasible. One workaround I used was to process the image in tiles with a slight overlap region. You split the image into, say, 512x512 tiles, process each tile horizontally with a 32-pixel overlap on each side to account for kernel extent, then process vertically across the tile grid with the same overlap strategy. It adds a bit of complexity to the code but eliminates the need for a full-resolution intermediate buffer.
If you're doing this on the GPU, the same principle applies but the implementation shifts to shader code. A typical approach uses two full-screen fragment shaders: one for the horizontal pass and one for the vertical pass, with a framebuffer object binding between them. Texture sampling with bilinear filtering already does something similar under the hood, but you're controlling the exact kernel weights rather than relying on whatever the hardware gives you. This matters when you need a specific sigma value that doesn't map cleanly to integer pixel distances. There's also a trick worth knowing about when you need multiple blur passes with different sigma values. Instead of recomputing the kernel every time, you can precompute a table of weights indexed by sigma and radius. Or better yet, if your application calls for a series of progressively larger blurs, you can use the recursive filter approach. A causal and anti-causal IIR filter can approximate a Gaussian to within a few percent using just four coefficients regardless of kernel size. The tradeoff is that the approximation introduces slight asymmetry and a small amount of ringing near high-contrast edges. For most visual applications that's invisible, but if you're doing scientific image processing where accuracy matters, stick with the separable FIR approach. One more thing that people don't always consider: the order of the two passes doesn't matter mathematically. Horizontal then vertical produces the same result as vertical then horizontal. But on some architectures, processing rows first is significantly faster because of how memory is laid out and how cache lines prefetch. On GPUs, the memory access pattern is more complex due to texture fetch units, and the optimal order might differ. If performance is critical, benchmark both directions with your specific hardware.
Get the Full Details

The algorithm itself is simple enough that you can write a correct implementation in an afternoon. Getting it to run well at scale requires attention to memory layout, boundary conditions, and the specific constraints of your target platform. That's really what the method is about, not any single clever insight, but the disciplined application of the separability property across real rendering pipelines.