Basic knowledge
Gaussian Blur, also known as Gaussian Smoothing, is a classic algorithm for blurring images. To put it simply, the Gaussian blur algorithm is a process of performing a weighted average operation on the entire image. The value of each pixel is obtained by weighted averaging of itself and other pixel values in the field.
Before formally entering the Gaussian blur algorithm, you first need to understand the convolution operation. Here is a brief description of the convolution operation process of the matrix. Readers interested in the detailed convolution operation can refer to the relevant information.
There is a matrix to process:
Assuming that the matrix is processed with a 3*3 convolution kernel, the convolution kernel is:
First, flip the convolution kernel by 180°, and the result is the same as the original convolution kernel. Then, align the center of the convolution kernel with the corresponding element of the matrix to be processed, such as the first element in the upper left corner, and fill in 0 where there are no elements. The corresponding elements are multiplied and added together to get the result:
0*0.1+0*0.1+0*0.1+0*0.1+1*0.2+2*0.1+0*0.1+5*0.1+6*0.1=1.5
The value of the first element after the convolution operation is 1.5. Similarly, all other values on the matrix are processed to the final result:
This is a simple matrix convolution operation process, which is an application of the Sliding Window Algorithm.
Gaussian blur uses such a convolution operation, and it is called “Gaussian” blur because the calculation of the convolution kernel is applied to the Gaussian distribution function:
(Density function of onedimensional Gaussian normal distribution)
(Density function plot of onedimensional Gaussian normal distribution)
The Gaussian distribution formula in the twodimensional space is further obtained as:
Normalize it to get:
Here v denotes column, u, v ∈ {ω, ω+1, …… , ω1, ω}
S is the normalization constant:
The diagram is:
For a 3*3 convolution kernel, the relative coordinates of each position can be expressed as:
After substituting into the formula, we can get a normalized 3*3 Gaussian convolution kernel:
For an image, all pixels are convolved with a Gaussian convolution kernel. The resulting image looks like the image is blurred, so it’s called Gaussian blur.
Let the output image be Y, the input image be X, the data in the ith row and the jth column are represented as X(i,j) and Y(i,j), then the size is (2ω+1)*(2ω+1). The calculated result of the Gaussian kernel with standard deviation σ is:
According to this expression, place the center of the Gaussian kernel at the position (i, j) of the input image, multiply each value of the Gaussian kernel with the value at the corresponding position of the input image, and perform (2ω+1)*(2ω +1) multiplications, and then (2ω+1)*(2ω+1)1 additions, the time complexity is O(ω^2).
For a 1024*1024 size image, if you want to perform a blurring operation on the entire image, using a 33*33 size convolution kernel, you need to perform 1024*1024*33*33≈1.14 billion texture reads, while this is undoubtedly very timeconsuming.
Unity Implementation
According to the above algorithm, we implement it in Unity:
Create a new Shader to implement the calculation formula of the twodimensional Gaussian kernel:
float gauss(float x, float y, float sigma)
{
return 1.0f / (2.0f * PI * sigma * sigma) * exp((x * x + y * y) / (2.0f * sigma * sigma));
}
The vertex shader selects the vert_img function implemented in UnityCG.cginc, whose main function is to convert vertices from model space to clipping space:
v2f_img vert_img( appdata_img v )
{
v2f_img o;
UNITY_INITIALIZE_OUTPUT(v2f_img, o);
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.pos = UnityObjectToClipPos (v.vertex);
o.uv = v.texcoord;
return o;
}
Set the Gaussian kernel size:
#ifdef MEDIUM_KERNEL
#define KERNEL_SIZE 35
#elif BIG_KERNEL
#define KERNEL_SIZE 127
#else
#define KERNEL_SIZE 7
#endif
Implement a fragment shader that weights each pixel with the pixels in its area, where the weight is obtained by the above gauss function.
float4 frag(v2f_img i) : COLOR
{
float4 o = 0;
float sum = 0;
float2 uvOffset;
float weight;
for (int x = KERNEL_SIZE / 2; x <= KERNEL_SIZE / 2; ++x)
for (int y = KERNEL_SIZE / 2; y <= KERNEL_SIZE / 2; ++y)
{
// Calculate the offset
uvOffset = i.uv;
uvOffset.x += x * _MainTex_TexelSize.x;
uvOffset.y += y * _MainTex_TexelSize.y;
// Determine the weights
weight = gauss(x, y, _Sigma);
o += tex2D(_MainTex, uvOffset) * weight;
sum += weight;
}
o *= (1.0f / sum);
return o;
}
That’s all for today’s sharing. Of course, life is boundless but knowing is boundless. In the long development cycle, these problems you see maybe just the tip of the iceberg. We have already prepared more technical topics on the UWA Q&A website, waiting for you to explore and share them together. You are welcome to join us, who love progress. Maybe your method can solve the urgent needs of others, and the “stone” of other mountains can also attack your “jade”.
YOU MAY ALSO LIKE!!!
UWA Website: https://en.uwa4d.com
UWA Blogs: https://blog.en.uwa4d.com
UWA Product: https://en.uwa4d.com/feature/got
You may also like

Dynamic Resolution in Games from Principle to Application
January 4, 2023 
Introduction of the Raytracing Technology Part 2
December 21, 2022