Generates sample offsets and weights for a two-pass Gaussian blur GLSL shader that uses linear texture filtering to sample two weighted pixels using a single texture read.

# How to use it?

OFFSETS are offsets in pixels from the destination pixel to the input sample pixels (along the current blurring axis, i.e. horizontal or vertical).

WEIGHTS are the corresponding weights, i.e. how much contribution each input sample gives to the output value. They are already normalized — their sum is 1.

Here’s an example GLSL function that does the blurring:

# How does it work?

A two-dimantional Gaussian filter uses weights in the form of $\dpi{110}\exp\left(-\frac{x^2+y^2}{\sigma^2}\right)$, sampling the input texture in a $\dpi{110}(2N+1)\times(2N+1)$ square (in the $\dpi{110}[-N..N]\times[-N..N]$ range around the current pixel), making a total of $\dpi{110}(2N+1)^2$ texture reads in a single fragment shader invocation.

A separable filter makes use of the observation that $\dpi{110}\exp\left(-\frac{x^2+y^2}{\sigma^2}\right)=\exp\left(-\frac{x^2}{\sigma^2}\right)\cdot\exp\left(-\frac{y^2}{\sigma^2}\right)$, which means that we can blur horizontally over the range $\dpi{110}[-N..N]$ around the current pixel, and then blur the result vertically to get the final blur (or blur vertically first and horizontally after that, doesn’t matter). This cuts down the number of texture reads to $\dpi{110}2N+1$ per pass, meaning $\dpi{110}4N+2$ in total (for both the horizontal and vertical passes).

Using linear filtering for the input texture, we can further reduce the number of required texture reads. Say, we want to to read two neighbouring pixels p[i] and p[i+1] (I’m using 1D indexing because we’re talking about separable blur, so all pixels read in a single shader invocation are in the same row or column) with weights $\dpi{110}w_0$ and $\dpi{110}w_1$. The total contribution of these two pixels is $\dpi{110}w_0 p_i + w_1 p_{i+1}$. Rewriting it as a lerp, we get $\dpi{110}w_0 p_i + w_1 p_{i+1} = (w_0 + w_1) \text{lerp}\left(p_i, p_{i+1}, \frac{w_1}{w_0+w_1}\right)$, meaning we can sample at location $\dpi{110}i+\frac{w_1}{w_0+w_1}$ with a total weight of $\dpi{110}w_0+w_1$, and thanks to linear filtering this will evaluate to the total contribution of two pixels, at the expense of a single texture read. This lowers the number of texture reads to $\dpi{110}N+1$ per pass, meaning a total of $\dpi{110}2N+2$ per the full blur.

To learn about small sigma correction, see this post by Bart Wronski.