Skip to main content
gamedev.net
🔒 Locked

Blurring

Started by Pete_ Dec 7, 2005 at 9:57 AM 6 replies 2.7k views
Original Post
Pete_
Pete_
Hi, I have been trying to do a full-screen blur (ideally like the Gaussian Blur filter in Photoshop) without much luck. I've searched around alot and have not found anything that seems to work at a practical speed... I know it's a pretty common effect, so I must be missing something. I have tried the blur shader from Delphi3d's HDR sample, but couldn't get it to work with reasonable quality at a more than a few FPS. Eventually I came up with a modified version of the Nehe #36 algorithm. I have the part of the image I want to blur in an FBO texture, bind it, set up blending with glBlendFunc(GL_SRC_ALPHA, GL_ONE), and render it many times in a increasing circle pattern:
glBegin(GL_QUADS);
	for(float radius = 0.0f; radius <= 0.1f; radius += 0.01f)
	{
		for(float angle = 0.0f; angle <= 2.0f*pi; angle += pi/32.0f)
		{
			glColor4f(1.0f, 1.0f, 1.0f, 0.005f);
			
			float offset_x = radius*cos(angle);
			float offset_y = radius*sin(angle);
			
			glTexCoord2f(0.0f, 1.0f);
			glVertex2f(-1.0f+offset_x, -1.0f+offset_y);
			
			glTexCoord2f(0.0f, 0.0f);
			glVertex2f(-1.0f+offset_x, 1.0f+offset_y);
			
			glTexCoord2f(1.0f, 0.0f);
			glVertex2f(1.0f+offset_x, 1.0f+offset_y);
			
			glTexCoord2f(1.0f, 1.0f);
			glVertex2f(1.0f+offset_x, -1.0f+offset_y);
		}
	}
glEnd();
Of course, this is VERY slow since I have to render it so many times to create a decent-quality blur: Image I have tried downsampling to about 128x128 and using GL_LINEAR filtering. This looks decent, but in motion and composited with another image it's terrible: Image Are there some tricks for this that I'm missing? [smile] It only needs to run on a QuadroFX 1400Go and GeForce6600 and higher nVidia cards on the latest FreeBSD and Windows drivers, so any fancy shader (I am using GLSL) or extension tricks should be OK. (It would be a nice bonus to work on ATi's ~X1600+ cards, but not needed.) Thanks, - Pete
Red_falcon
Red_falcon
Have my glsl shader atm not here. Its on the notebook. But take the texture coordinates and manipulate them like so:

vec4 tex = texture2D(texture, gl_TexCoord[0].xy);
tex += texture2D(texture, gl_TexCoord[0].xy + 0.001);
tex += texture2D(texture, gl_TexCoord[0].xy - 0.001);
tex += texture2D(texture, gl_TexCoord[0].xy + 0.002);
tex += texture2D(texture, gl_TexCoord[0].xy - 0.002);

gl_FragColor = tex /5.0;

I wrote it of the head, so sorry if there are mistakes, but if you want to look at the hlsl version, then look at http://www.facewound.com/tutorials/shader1/ at multisample paragraph
-----"Master! Apprentice! Heartborne, 7th Seeker Warrior! Disciple! In me the Wishmaster..." Wishmaster - Nightwish
zedzeek
zedzeek
Red_falcon has the idea, though u might wanna use more samples to get a better guassian blur eg 5x5=25 samples per pixel should give ok results and not be to slow
_DarkWIng_
_DarkWIng_
I used something like Red_falcon's idea in my demo and it works just fine. You also have to remember that you can separate 2D Gauss to 2x 1D. Implementing this gives a nice performance bonus. (for 16x16 kernel you only need 32 samples.. quite a save)
You should never let your fears become the boundaries of your dreams.
Pete_
Pete_
Thanks for your replies!

That looks like a pretty similar method to what the Delphi3d shader did (although that one used loops instead of writing it out, along with a few other additions). Here is what I came up with:
uniform sampler2D tex;void main(){	vec4 color = vec4(0.0, 0.0, 0.0, 0.0);		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+-0.024));		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+-0.016));		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+-0.008));		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+0));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+0));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+0));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+0));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+0));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+0));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+0));		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+0.008));		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+0.016));		color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.024, gl_TexCoord[0].t+0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.016, gl_TexCoord[0].t+0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+-0.008, gl_TexCoord[0].t+0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0, gl_TexCoord[0].t+0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t+0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t+0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t+0.024));			gl_FragColor = color/49;}

I wrote a program to generate that of course: ;)
#include <iostream>#include <ostream>#include <sstream>int main(int argc, char* argv[]){	if(argc != 4)		std::cerr << "usage: blurgen width height offset" << std::endl;		std::cout << "uniform sampler2D tex;" << std::endl;	std::cout << "" << std::endl;	std::cout << "void main()" << std::endl;	std::cout << "{" << std::endl;	std::cout << "\tvec4 color = vec4(0.0, 0.0, 0.0, 0.0);" << std::endl;	std::cout << "\t" << std::endl;		int width, height;	float offset;	std::istringstream widths(argv[1]);	widths >> width;	std::istringstream heights(argv[2]);	heights >> height;	std::istringstream offsets(argv[3]);	offsets >> offset;		int y = -height/2;	for(int i = 0; i < height; ++i)	{		int x = -width/2;		for(int j = 0; j < width; ++j)		{			float xo = x*offset;			float yo = y*offset;			std::cout << "\tcolor += texture2D(tex, vec2(gl_TexCoord[0].s+" << xo << ", gl_TexCoord[0].t+" << yo << "));" << std::endl;			++x;		}		std::cout << "\t" << std::endl;		++y;	}		std::cout << "\t" << std::endl;	std::cout << "\tgl_FragColor = color/" << width*height << ";" << std::endl;	std::cout << "}" << std::endl;}

This works really well at around 3x3 with small offsets, but unfortunatly when it's scaled up much more it has either severe speed or quality problems. [sad] At 7x7, offset 0.008 it is big and good enough: Image, but that gets 1-2 FPS on a QFX1400Go.

Is there some trick with that technique I am missing to make that faster, but still with that size and quality? Or some other technique I could try?

EDIT: I just saw your post DarkWing after I replied, thanks, I'm trying it now!

Thanks,
- Pete
Pete_
Pete_
DarkWing: thank you thank you thank you! [grin] It's up to ~16-20FPS now with your 15x15 `fake' blur and looks great combined with the other image (yes, that's the beginnings of yet another HDR rendering implementation, the blur's for the >1.0 color parts as seen near the right-hand side). (I tried 7x7 again but only got about 24FPS, so for the large quality increase I didn't figure it was worth going lower than 15x15.)

So, my full blur technique is now:
- Render non-blurred portion of image to FBO 0
- Render to-be-blurred portion of image to FBO 1
- Render FBO 1, blurred horizontally, to FBO 2
- Copy FBO 0 to screen
- Blend FBO 2, blurred vertically, to screen
(I could render the non-blurred part directly to the screen, but I will be doing extra stuff with it later)

Horizontal blur shader:
uniform sampler2D tex;void main(){	vec4 color = vec4(0.0, 0.0, 0.0, 0.0);		color += texture2D(tex, vec2(gl_TexCoord[0].s-0.028, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s-0.024, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s-0.020, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s-0.016, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s-0.012, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s-0.008, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s-0.004, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.004, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.008, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s+0.012, gl_TexCoord[0].t));		color += texture2D(tex, vec2(gl_TexCoord[0].s+0.016, gl_TexCoord[0].t));		color += texture2D(tex, vec2(gl_TexCoord[0].s+0.020, gl_TexCoord[0].t));		color += texture2D(tex, vec2(gl_TexCoord[0].s+0.024, gl_TexCoord[0].t));		color += texture2D(tex, vec2(gl_TexCoord[0].s+0.028, gl_TexCoord[0].t));			gl_FragColor = color/15;}

Vertical blur shader:
uniform sampler2D tex;void main(){	vec4 color = vec4(0.0, 0.0, 0.0, 0.0);		color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.028));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.024));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.020));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.016));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.012));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-0.004));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.004));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.008));	color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.012));		color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.016));		color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.020));		color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.024));		color += texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+0.028));			gl_FragColor = color/15;}


The blurring is still taking up about half of the rendering time, 0.027 seconds out of 0.051, so it could still stand to be sped up a bit more; if anyone has any more suggestions on how to optimize it a bit more I would really appreciate it!

Thanks,
- Pete
_DarkWIng_
_DarkWIng_
I'm glad to hear it worked so fine for you.

One more thing you can do is to blur it twice (or even more times) with smaller kernel. Something like 5x1 + 1x5 + 5x1 + 1*5 + ... You can just ping-pong the buffers.

And actually separating 2D Gauss in 2x 1D is not fake but mathematically correct.
You should never let your fears become the boundaries of your dreams.
Ademan555
Ademan555
Well, you could very easily attach both the base texture and the "to be blurred" texture to the same FBO (as the second color attachment) and save yourself a pass (make sure you enable the second color attachment though)

cheers, hope that helps
-Dan
When General Patton died after World War 2 he went to the gates of Heaven to talk to St. Peter. The first thing he asked is if there were any Marines in heaven. St. Peter told him no, Marines are too rowdy for heaven. He then asked why Patton wanted to know. Patton told him he was sick of the Marines overshadowing the Army because they did more with less and were all hard-core sons of bitches. St. Peter reassured him there were no Marines so Patton went into Heaven. As he was checking out hi

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.