Trying to make a blur shader, but having issues.

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
ninjacharlie
Prole
Posts: 5
Joined: Sat Jan 11, 2014 4:33 pm
Location: IN SPAAACCEEE
Contact:

Trying to make a blur shader, but having issues.

Post 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?
Germanunkol
Party member
Posts: 712
Joined: Fri Jun 22, 2012 4:54 pm
Contact:

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

Post 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.
trAInsported - Write AI to control your trains
Bandana (Dev blog) - Platformer featuring an awesome little ninja by Micha and me
GridCars - Our jam entry for LD31
Germanunkol.de
User avatar
vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

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

Post 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}\).
I have come here to chew bubblegum and kick ass... and I'm all out of bubblegum.

hump | HC | SUIT | moonshine
Germanunkol
Party member
Posts: 712
Joined: Fri Jun 22, 2012 4:54 pm
Contact:

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

Post 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.
trAInsported - Write AI to control your trains
Bandana (Dev blog) - Platformer featuring an awesome little ninja by Micha and me
GridCars - Our jam entry for LD31
Germanunkol.de
User avatar
ninjacharlie
Prole
Posts: 5
Joined: Sat Jan 11, 2014 4:33 pm
Location: IN SPAAACCEEE
Contact:

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

Post 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;
Germanunkol
Party member
Posts: 712
Joined: Fri Jun 22, 2012 4:54 pm
Contact:

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

Post 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 517 times
Press x to disable shader in x direction and y to disable the one in y direction.
trAInsported - Write AI to control your trains
Bandana (Dev blog) - Platformer featuring an awesome little ninja by Micha and me
GridCars - Our jam entry for LD31
Germanunkol.de
User avatar
ninjacharlie
Prole
Posts: 5
Joined: Sat Jan 11, 2014 4:33 pm
Location: IN SPAAACCEEE
Contact:

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

Post by ninjacharlie »

Germanunkol, great, that works. Thank you, I'll have a look through it.
Post Reply

Who is online

Users browsing this forum: Semrush [Bot] and 2 guests