Page 1 of 1

Trying to make a blur shader, but having issues.

Posted: Sat Jan 11, 2014 4:40 pm
by ninjacharlie
I'm trying to write a blur shader (and eventually to make it a bloom shader), but the resulting blur is really lumpy:

Image

Here's the code in it's current state:

Code: Select all

int samples = 10;
		int sampledist = 1;
		float rt_h = 720; // render target height
		float rt_w = 1280; // render target height

		vec4 effect(vec4 col, Image tex, vec2 texcoord, vec2 screencoord) 
		{ 
				vec3 final = vec3(1.0, 0.0, 0.0);
				final = Texel(tex, texcoord.xy).rgb;
				for (int i=-samples; i<samples; i++) 
				{
						for (int j=-samples; j<samples; j++) 
						{
								final += Texel(tex, texcoord.xy + vec2(i*sampledist, j*sampledist)/vec2(rt_w, rt_h)).rgb * .01;
						}
				}
				return vec4(final, 1.0);
		}
What am I doing wrong?

Re: Trying to make a blur shader, but having issues.

Posted: Tue Jan 14, 2014 1:26 pm
by Germanunkol
You're only calculating the average of the pixels below and above (and left and right) of the current image.
You could visualize it like this: (shown here only in x-direction, but it's the same in y-direction):
Image
Every pixel around the current pixel in each direction and up to a distance of 10 (in your implementation) is used with the same weight (0.1).

While that works for blurring a bit, it's not a great result.
A better filter would be the gaussian-filter or gaussian blur (called so because of the gaussian function it uses):
Image
As you can see, the weights have changed - the further away a pixel is, the less it influences the currrent pixel.
The weights can easily be pre-calculated and you can find many tables on the internet (see the code below for the one I use). Otherwise you'd have to calculate a gauss every frame, which is a waste of GPU power because it's constant anyways.
There's one more optimization step - and an important one, too:
Usually, gaussian blurs use two passes. This means that first, you blurr the whole image in x direction and draw the result to a canvas, then you blurr that in y direction and draw it to the screen.

Here's an example:
(Note: blurSize is not given in pixels, here. Instead it is given in the GPU's screen space, ranging from 0 to 1. So to blur exactly 9 pixels, you need to set blurSize to 9/screenWidth or 9/screenHeigth.)
Edit: Changed code to run in 0.9.0 instead of 0.8.0

Code: Select all


img = love.graphics.newImage("grid.png")

function love.load()

	canvas = love.graphics.newCanvas( )

	blur1 = love.graphics.newShader [[

		vec4 effect(vec4 color, Image texture, vec2 vTexCoord, vec2 pixel_coords)
		{
			vec4 sum = vec4(0.0);
			number blurSize = 0.005;
			//number d = distance(vTexCoord, mousePos/screenSize);
			//number blurSize = clamp(1/d/screenSize.x, 0, 1.0);
			// blur in y (vertical)
			// take nine samples, with the distance blurSize between them
			sum += texture2D(texture, vec2(vTexCoord.x - 4.0*blurSize, vTexCoord.y)) * 0.05;
			sum += texture2D(texture, vec2(vTexCoord.x - 3.0*blurSize, vTexCoord.y)) * 0.09;
			sum += texture2D(texture, vec2(vTexCoord.x - 2.0*blurSize, vTexCoord.y)) * 0.12;
			sum += texture2D(texture, vec2(vTexCoord.x - blurSize, vTexCoord.y)) * 0.15;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y)) * 0.16;
			sum += texture2D(texture, vec2(vTexCoord.x + blurSize, vTexCoord.y)) * 0.15;
			sum += texture2D(texture, vec2(vTexCoord.x + 2.0*blurSize, vTexCoord.y)) * 0.12;
			sum += texture2D(texture, vec2(vTexCoord.x + 3.0*blurSize, vTexCoord.y)) * 0.09;
			sum += texture2D(texture, vec2(vTexCoord.x + 4.0*blurSize, vTexCoord.y)) * 0.05;
			
			
			return sum;
		}
		]]
	blur2 = love.graphics.newShader [[
		
		vec4 effect(vec4 color, Image texture, vec2 vTexCoord, vec2 pixel_coords)
		{
			vec4 sum = vec4(0.0);
			number blurSize = 0.005;

			// blur in y (vertical)
			// take nine samples, with the distance blurSize between them
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y - 4.0*blurSize)) * 0.05;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y - 3.0*blurSize)) * 0.09;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y - 2.0*blurSize)) * 0.12;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y- blurSize)) * 0.15;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y)) * 0.16;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y + blurSize)) * 0.15;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y + 2.0*blurSize)) * 0.12;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y + 3.0*blurSize)) * 0.09;
			sum += texture2D(texture, vec2(vTexCoord.x, vTexCoord.y + 4.0*blurSize)) * 0.05;

			return sum;
		}
		]]
	love.graphics.setBackgroundColor(0,0,0)
end

function love.draw()

	love.graphics.setCanvas(canvas)
    -- LOOK AT THE PRETTY COLORS!
    love.graphics.setShader(blur1)
    love.graphics.draw(img, 0, 0)
    --love.graphics.rectangle('fill', 10,10, love.graphics.getWidth()-20, love.graphics.getHeight()-20)

	love.graphics.setCanvas()
	
    
    --love.graphics.draw(img, 0, 0)
    --love.graphics.draw(canvas, 0, love.graphics.getHeight()/2)
    love.graphics.setShader(blur2)
    --love.graphics.draw(canvas, love.graphics.getWidth()/2, love.graphics.getHeight()/2)
    love.graphics.draw(canvas, 0,0)
end
P.S. I ripped the weights from some online-example which I cannot find any more. Shame on me for not citing it properly.
But the weigths are, I guess, a commonly known fact, since all they really are is evaluations of the gaussian function. Still - bad me.

Re: Trying to make a blur shader, but having issues.

Posted: Tue Jan 14, 2014 4:30 pm
by vrld
Adding to what Germanunkol wrote:

1) Your method is called mean or box blur and while it looks fine in most cases, Gaussian blur tends to look more "natural".

2) Your implementation is wrong though. Specifically

Code: Select all

for (int i=-samples; i<samples; i++) 
    {
        for (int j=-samples; j<samples; j++) 
Iterates both i and k from -samples to samples-1 (-10 to 9), so you end up adding 20*20 = 400 values. However, you normalize by multiplying with 0.01 = 1/100 (which would be correct if you added up 10*10 values). This explains the white "core" of the ring. To fix it, either normalize by 0.0025, or iterate from -samples/2 to samples/2.

3) The weights of Gaussian blur are calculated by the scaring looking \(G(x) = \frac{1}{\sqrt{2*\pi}\sigma}\exp\left(-\frac{x^2}{2\sigma^2}\right)\) (one dimension), or for two dimensions \[G(x,y) = \frac{1}{2*\pi\sigma^2}\exp\left(-\frac{x^2+y^2}{2\sigma^2}\right)\]
Here, \(\sigma\) regulates the "width" of the Gaussian: larger values will result in more, smaller values in less blurring. Since it's more intuitive to think in terms of pixels, a good guideline is to choose \(\sigma = \frac{\text{blur-radius}}{3}\).

Re: Trying to make a blur shader, but having issues.

Posted: Tue Jan 14, 2014 5:06 pm
by Germanunkol
vrld wrote: ... so you end up adding 20*20 = 400 values. However, you normalize by multiplying with 0.01 = 1/100 (which would be correct if you added up 10*10 values). This explains the white "core" of the ring. To fix it, either normalize by 0.0025, or iterate from -samples/2 to samples/2.
Good catch!
I didn't notice that, I forgot the algorithm was moving from -10 to 10...
Then it might be enough to just fix that, instead of changing to gaussian. You should at least try it.

Re: Trying to make a blur shader, but having issues.

Posted: Tue Jan 14, 2014 6:07 pm
by ninjacharlie
Germanunkol, thanks for the info I didn't understand the whole weight thing before. I don't think the example works correctly. It only blurs vertically.

vrld, what do you mean by normalize by .0025? I tried multiplying by .025 instead of .1 (.0025 doesn't show anything):

Code: Select all

final += Texel(tex, texcoord.xy + vec2(i*sampledist, j*sampledist)/vec2(rt_w, rt_h)).rgb * .025;

Re: Trying to make a blur shader, but having issues.

Posted: Tue Jan 14, 2014 6:41 pm
by Germanunkol
ninjacharlie wrote:Germanunkol, thanks for the info I didn't understand the whole weight thing before. I don't think the example works correctly. It only blurs vertically.
Hm, I can't confirm that. Make sure to change the blur-size in both shaders, if you change it.

Here's an example:
Gaussian.love
(89.73 KiB) Downloaded 525 times
Press x to disable shader in x direction and y to disable the one in y direction.

Re: Trying to make a blur shader, but having issues.

Posted: Tue Jan 14, 2014 11:02 pm
by ninjacharlie
Germanunkol, great, that works. Thank you, I'll have a look through it.