Implementation effect

1. Fuzzy mean value

The so-called fuzzy, is to let the image is not clear, so let the picture is not clear what methods

  • Reduce image

    It is easy to understand how to shrink the image. When we reduce the original 1080*960 image to 540*480, that is, half of the original size, but when we display it, we still let it display in 1080*960 size, at this time the image looks more blurred than the original

  • The pixel is the average of the surrounding pixels

    When a picture of a person is very hard to see, we might say that the person’s eyes and nose are plastered together, which means that the pixels are smooth, rather than sharp, as in hd

Below, we are using both methods to blur the image

The pixel is the average of the surrounding pixels

Look at the table below

If this form of value is the value of the pixel, so as you can see, the center pixel value is 2, the surrounding pixels value is 1 (of course, these values are the custom, you can also customize other values), the next thing to do to the center pixel blur, using fuzzy mean value, will add up all the pixels, then divided by the total number, the result is in the end


Center point pixel = ( 1 + 1 + 1 + . . . + 2 + . . . + 1 + 1 + 1 ) / 9 = 1 Center point pixel = (1 + 1 + 1 +… + 2 +… Plus 1 plus 1 plus 1 over 9 is 1

As you can see, the pixel value of the center point is changed from 2 to 1, which completes the blur of one pixel. For the entire graph, you need to traverse every element and do the same for each element

In fact, blurRadius and blurOffset are also involved in the operation of blur. In the picture above, the blurRadius is 1, and the blur step is also 1. The larger the blurRadius and blur step are, the blurrier the image will be

OpenGLES implementation

Next, OpenGLES is used to implement mean blur

As we all know, OpenGLES ‘chip shader deals with each pixel, so you can actually blur each pixel in the chip shader

Vertex shader

attribute vec4 aPos;
attribute vec2 aCoordinate;
varying vec2 vCoordinate;
void main(a){
    vCoordinate = aCoordinate;
    gl_Position = aPos;
}
Copy the code

Vertex shaders are more conventional and do nothing special

Chip shader

precision mediump float;
uniform sampler2D uSampler;
varying vec2 vCoordinate;
uniform int uBlurRadius;
uniform vec2 uBlurOffset;

// Boundary value processing
vec2 clampCoordinate(vec2 coordinate) {
    return vec2(clamp(coordinate.x, 0.0.1.0), clamp(coordinate.y, 0.0.1.0));
}

void main(a){
    vec4 sourceColor = texture2D(uSampler, vCoordinate);

    if (uBlurRadius <= 1) {
        gl_FragColor = sourceColor;
        return;
    }

    vec3 finalColor = sourceColor.rgb;

    for (int i = 0; i < uBlurRadius; i++) {
        finalColor += texture2D(uSampler, clampCoordinate(vCoordinate + uBlurOffset * float(i))).rgb;
        finalColor += texture2D(uSampler, clampCoordinate(vCoordinate - uBlurOffset * float(i))).rgb;
    }

    finalColor /= float(2 * uBlurRadius + 1);

    gl_FragColor = vec4(finalColor, sourceColor.a);
}
Copy the code

Chip shaders are relatively complex, so let’s take our time

There are two uniform variables, which are

  • uBlurRadius

    A variable of type int that represents the blur radius

  • uBlurOffset

    A vec2 type variable, with two components x and y, represents the horizontal and vertical fuzzy steps

The clampCoordinate function is custom defined in the chip shader. This function is used to keep the texture coordinate within the range of 0 to 1

Now let’s look at the main function

  • vec4 sourceColor = texture2D(uSampler, vCoordinate);

    Gets the pixel value of the current sampling point

  • Determine whether the blur radius is less than or equal to 1. If yes, do not blur and return directly

  • Create a new VEC4 variable, finalColor, to represent the final pixel value

  • The for loop retrieves the surrounding pixel values based on the blur radius and blur step size

  • FinalColor eventually needs to be divided by the number of pixels

Through the above steps, we have completed the mean blur

For those of you who may have noticed that I only did one for loop here, there should be two nested for loops

for (int i = 0; i < uBlurRadius; i++) {
	for (int j = 0; j < uBlurRadius; j++) { ...... }}Copy the code

UBlurOffset = VEC2 (blurOffsetW, 0.0), vec2(blurOffsetW, 0.0); UBlurOffset = VEC2 (0.0, blurOffsetH)

If you use two for loops, the total number of loops will be uBlurRadius * uBlurRadius. If you use two for loops, the total number of loops will be uBlurRadius + uBlurRadius. Rendering efficiency can be greatly improved, especially when the blur radius is large

You can check out MeanBlurFilter on GitHub

Implementation effect

uBlurRadius = 30

uBlurOffset = 1

Note that the blur effect is mediocre when we increase the blur step to

uBlurRadius = 30

uBlurOffset = 5

Then the following results are obtained

Ah, it took a long time, and here it is, here it is.

Emmm, don’t panic, let’s analyze why this result appears first

When uBlurOffset is equal to 1, the effect is not so good, and when uBlurOffset is equal to 5, the effect is even worse, because

The closer the peripheral pixels are to the current pixel, the greater the difference between them. However, when calculating, we did not take this situation into account and directly calculated the average value, which resulted in a poor rendering effect

So, is there a way to solve this? Well, of course there is. Let’s first understand the normal distribution

2. Normal distribution

Normal distribution, also known as “Normal distribution”, also known as Gaussian distribution, was first obtained by Abraham de Moivre in the asymptotic formula of binomial distribution. C.F. Gauss derived it from another perspective while studying measurement errors. P.S. Laplace and gauss studied its properties. Is a probability distribution that is very important in mathematics, physics, and engineering, and has great influence in many aspects of statistics.

The important thing is the formula for the normal distribution, the formula for the density of the normal distribution (μ = 0, the mean is 0)


f ( x ) = 1 2 PI. sigma e x p ( x 2 2 sigma 2 ) F (x) = \ frac {1} {\ SQRT \ {2} PI sigma} exp (- \ frac {x ^ 2} {2 sigma ^ 2})

Normally distributed image

So now that we have this formula, what do we do

Remember the blur radius, the blur radius is the range of x, for example, uBlurRadius = 10, then x is in the range of 0 to 9

The larger the value of x is, the smaller the final calculation result will be. In other words, the farther away from the central pixel, the smaller the relationship will be, and vice versa

Careful people have discovered that the formula contains a variable called sigma, which actually represents the standard deviation

Standard Deviation (Standard Deviation) is the arithmetic square root of the arithmetic mean from the square of the mean (i.e., the variance), expressed as σ. Standard deviation is also known as standard deviation, or experimental standard deviation, and is most commonly used in probability statistics as a measure of the degree of statistical distribution.

Standard deviation is the arithmetic square root of variance. Standard deviation reflects the degree of dispersion of a data set. Two sets of data with the same mean may not have the same standard deviation.

From the point of view of the image, the bigger σ, the flatter the image of the normal distribution, the smaller σ, the normal distribution will be concentrated in the center, and the higher

Let’s start with the implementation of Gaussian blur and ground-glass

Gaussian blur

The normal distribution, also known as the Gaussian distribution, using the density function of the normal distribution to do fuzzy processing, is called the Gaussian blur

In the previous mean blur, we added the surrounding pixels and averaged them. The Gaussian blur also needs to add and average the surrounding pixels, but with a weighted average

  • First of all, according to the normally distributedDensity functionAnd calculate all the values within the blur radiusThe weight
  • Because to make sure that the ownership readds to one, you need to divide each weight into oneThe total weight
  • When calculating fuzziness, the sampled pixel value needs to be multiplied by the correspondingThe weight

OpenGLES implementation

Vertex shader

attribute vec4 aPos;
attribute vec2 aCoordinate;
varying vec2 vCoordinate;
void main(a){
    vCoordinate = aCoordinate;
    gl_Position = aPos;
}
Copy the code

Like mean fuzziness, vertex shaders are not treated in any special way

Chip shader

precision mediump float;
uniform sampler2D uSampler;
varying vec2 vCoordinate;
// Blur radius
uniform int uBlurRadius;
// Blur the step size
uniform vec2 uBlurOffset;
/ / total weight
uniform float uSumWeight;
// PI
const float PI = 3.1415926;

// Boundary value processing
vec2 clampCoordinate(vec2 coordinate) {
    return vec2(clamp(coordinate.x, 0.0.1.0), clamp(coordinate.y, 0.0.1.0));
}

// Calculate the weight
float getWeight(int i) {
    float sigma = float(uBlurRadius) / 3.0;
    return (1.0 / sqrt(2.0 * PI * sigma * sigma)) * exp(-float(i * i) / (2.0 * sigma * sigma)) / uSumWeight;
}

void main(a){
    vec4 sourceColor = texture2D(uSampler, vCoordinate);

    if (uBlurRadius <= 1) {
        gl_FragColor = sourceColor;
        return;
    }

    float weight = getWeight(0);

    vec3 finalColor = sourceColor.rgb * weight;

    for (int i = 1; i < uBlurRadius; i++) {
        weight = getWeight(i);
        finalColor += texture2D(uSampler, clampCoordinate(vCoordinate - uBlurOffset * float(i))).rgb * weight;
        finalColor += texture2D(uSampler, clampCoordinate(vCoordinate + uBlurOffset * float(i))).rgb * weight;
    }

    gl_FragColor = vec4(finalColor, sourceColor.a);
}
Copy the code

At first glance, the pixel shader for Gaussian blur looks about the same as the pixel shader for mean blur. If you read it carefully, you will find that there is a slight difference in the for loop, which is the pixel value sampled, multiplied by the corresponding weight

It should be noted that the GLSL, cannot pass us array, and when we need to change the blur radius, have to calculate weight gaussian blur, so here the author is divided into two parts, the Java part according to the fuzzy radius calculated total weight value to GLSL, fragment shader, according to the for loop, calculate the corresponding weight value, That would satisfy our needs

The calling code is not posted here, but you can view GaussianBlurFilter on GitHub

Implementation effect

uBlurRadius = 30

uBlurOffset = 1

As you can see, the Gaussian blur looks more natural and better than the mean blur

Gaussian blur, so how to achieve the ground-glass effect, in fact, only need to modify some parameters, such as increasing the blur radius, blur step size

Four, frosted glass

With gaussian blur implemented above, we can achieve the frosted glass effect by increasing the blur step, for example

uBlurRadius = 30

uBlurOffset = 5

Of course, we can do this by increasing the blur radius

So the question is, can the blur radius always be increased? The answer is no, because depending on the performance of different models, if you increase the blur radius too much, the picture will be stuck, especially when rendering in real time

So is there a solution? The answer is yes. We can reduce the texture size by several times before we blur it, because the blur itself does not need the details of the image. When we shrink it, the blur effect will not be affected

GitHub

Welcome to Star MediaDemo