Questions About Blur Effect

Started by
7 comments, last by TheChubu 8 years, 3 months ago

1. I implemented Gaussian Blur and trying to turn the entire scene blurry gradually (not all of sudden)

Now I'm getting weird edges:

[attachment=30128:blur_effect.png]

Here is what I'm doing in Pixel Shader:


// Pixel Shader
const float Pixels[13] =
{
    -6,
    -5,
    -4,
    -3,
    -2,
    -1,
     0,
     1,
     2,
     3,
     4,
     5,
     6
};

const float BlurWeights[13] =
{
    0.002216,
    0.008764,
    0.026995,
    0.064759,
    0.120985,
    0.176033,
    0.199471,
    0.176033,
    0.120985,
    0.064759,
    0.026995,
    0.008764,
    0.002216,
};

float blur = 20.0f; // The higher the more blurry
float4 color = float4(0.0f, 0.0f, 0.0f, 1.0f);

// Horizontal
float pixelWidth = (1.0f / screenWidth) * blur;
for (int i = 0; i < 13; i++) 
{
     color += Tex.Sample(SampleType, IN.UV + float2(Pixels[i] * pixelWidth, 0.0f)) * BlurWeights[i];
}  

// Vertical
float pixelHeight = (1.0f / screenHeight) * blur;
for (int i = 0; i < 13; i++) 
{
    color += Tex.Sample(SampleType, IN.UV + float2(0.0f, Pixels[i] * pixelWidth)) * BlurWeights[i];
}

return color / 2;

2. Should I do the blur effect in two different shader passes (one for Horizontal and one for Vertical) or both in one single function call like the above code?

What if I'm doing Horizontal and vertical calculation in the same Pixel Shader function, is that a good way?

3. In the above example I'm not doing any down sample, If I do down sample and the blur is not too high, I see undesired jagged edges, which make things look unrealistic when I do gradual blur.

4. In the above example the kernel size is 13, when should I increase or decrease that number?

Advertisement

You need to do this as two separate passes, one horizontal then one vertical (or vice versa). What you currently have is blurring each pixel in a cross shape instead of as the full area around the pixel.

In other words, your current setup is blurring like below for each pixel, where the middle pixel (P) is the current one. Separating these into two passes and blurring the first pass into a temporary buffer to be used as the input for the second pass will give you a blur that includes the pixels in the area surrounding the center point, also.


        *
        *
        *
        *
********P*******
        *
        *
        *
        *

@WFP: Thanks! Now it's better, however I have few questions:

1. If I don't do any down sample something looks weird (click on the image below to make it larger and see inside the red squares), how do I get rid of that without doing down sample?

[attachment=30141:blur_2.png]

2. If I want to do gradual blur, how should I do it?

What I was thinking about is that I down sample the screen to quarter of it's size and then apply blur effect, then gradually change the variable float blur; inside the shader from 0.1 to 2.0f, what do you think? (I want to make sure that the scene will look smooth all the time)

3. When should I change the kernel? What is the difference?

I'm not positive but my first guess is that it's because of the additional space you're taking between blur taps. Try it by just taking neighboring pixels and not using the below value. You can just set it to 1.0f for testing before removing it.

float blur = 20.0f; // The higher the more blurry

I'm on mobile at the moment, so I'll try to get to your other questions later if no one else has yet.

@WFP: If I remove float blur = 20.0f; How can I increase/decrease the blur?

You need to recalculate the weights and pixel locations such that there aren't any source pixels that don't contribute to the blurred destination. So if you have a NxN pixel wide blur kernel, you might sample N different locations with N weights, one for each source pixel. In your case:

float pixelWidth = (1.0f / screenWidth) * blur;

pixelWidth is a misnomer because this variable doesn't represent the width of a pixel, it represents the texture space distance you step over.

const float Pixels[13]

This means your kernel is only suited for a 13x13 blur, and you can't do more than that unless you're going to get fancy with filtering.

One way to do a variable strength blur is to set up a filter kernel (i.e. array of weights) big enough for the maximum blur quantity you need.

You can pass that weights array as a parameter to the shader, and change the blur amount by adjusting the weights, and padding with zeros.

Making some numbers up, you could set up a 5 tap blur, with weights of say { 0.1, 0.2, 0.4, 0.2, 0.1 } and convert it to a 3 tap blur by setting the weights as say {0, 0.25, 0.5, 0.25, 0}

Obviously you'd need to calculate those weights on the CPU and upload them to the shader, and you'd probably want much more than a 5 tap shader.

Adam_42's way to increase the blur is one approach. Another approach is to simply run the result of the first blur pass through the blur technique again. In pseudo-code it would look something like this:


int numPasses = 3;
for(int i = 0; i < numPasses; ++i)
{
    setInputImage(imageToBlur);
    setRenderTarget(tempBuffer);
    blurVertical();
    setInputImage(tempBuffer);
    setRenderTarget(imageToBlur);
    blurHorizontal();
}

If you were using a 7-tap blur in your shader and ran it through that loop 3 times, you would have the same result as running a 21-tap blur.

This Intel article its a good resource, discusses blurring techniques, optimizations and other considerations:

https://software.intel.com/en-us/blogs/2014/07/15/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

This topic is closed to new replies.

Advertisement