From what I see you are not doing Gaussian blur at all...
Gaussian blur on image is convolution between image and Gaussian weighted matrix of resolution 1+2*r
where r
is radius of your blur. So outputted color should be weighted sum of all pixels up to distance of r
from the targeted pixel.
What you are doing is just weighted sum of 9 pixels regardless of the radius that is wrong (in my optinion) as you should sum up ~6.28*r*r
pixels instead. So I would expect 2 nested for loops instead ...
Here a small GLSL example I just busted together:
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
in vec2 pos; // screen position <-1,+1>
out vec4 gl_FragColor; // fragment output color
uniform sampler2D txr; // texture to blur
uniform float xs,ys; // texture resolution
uniform float r; // blur radius
//---------------------------------------------------------------------------
void main()
{
float x,y,xx,yy,rr=r*r,dx,dy,w,w0;
w0=0.3780/pow(r,1.975);
vec2 p;
vec4 col=vec4(0.0,0.0,0.0,0.0);
for (dx=1.0/xs,x=-r,p.x=0.5+(pos.x*0.5)+(x*dx);x<=r;x++,p.x+=dx){ xx=x*x;
for (dy=1.0/ys,y=-r,p.y=0.5+(pos.y*0.5)+(y*dy);y<=r;y++,p.y+=dy){ yy=y*y;
if (xx+yy<=rr)
{
w=w0*exp((-xx-yy)/(2.0*rr));
col+=texture2D(txr,p)*w;
}}}
gl_FragColor=col;
}
//---------------------------------------------------------------------------
output for r=5
:

I normalized the weight so the brightness does not change too much from the original texture regardless of selected r
... However for r=5
is the error biggest, all other radiuses seem much better... That might be because of aliasing of the circumference with the circle inside condition...
You should also add some edge cases handling (when convolution is done near edges of texture) as there the number of summed pixels is different so the coefficients should be scaled accordingly.
To describe the code above: the 2 loops just loop through all pixels up to radius r
from pos
in texture
and convert from pixels x,y
and NDC pos
to texture coordinate p
. Then for each position the Gaussian weight is computed and then its used for the weighted sum. After all this the resulting color is outputted.
[Edit1] 2 pass approach
I ported this to 2 pass rendering (first integrate horizontal lines, then vertical) I got this output (r=5
):

Here Vertex shader:
//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
layout(location=0) in vec4 vertex;
out vec2 pos; // screen position <-1,+1>
void main()
{
pos=vertex.xy;
gl_Position=vertex;
}
//---------------------------------------------------------------------------
Here Fragment shader:
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
in vec2 pos; // screen position <-1,+1>
out vec4 gl_FragColor; // fragment output color
uniform sampler2D txr; // texture to blur
uniform float xs,ys; // texture resolution
uniform float r; // blur radius
uniform int axis;
//---------------------------------------------------------------------------
void main()
{
float x,y,rr=r*r,d,w,w0;
vec2 p=0.5*(vec2(1.0,1.0)+pos);
vec4 col=vec4(0.0,0.0,0.0,0.0);
w0=0.5135/pow(r,0.96);
if (axis==0) for (d=1.0/xs,x=-r,p.x+=x*d;x<=r;x++,p.x+=d){ w=w0*exp((-x*x)/(2.0*rr)); col+=texture2D(txr,p)*w; }
if (axis==1) for (d=1.0/ys,y=-r,p.y+=y*d;y<=r;y++,p.y+=d){ w=w0*exp((-y*y)/(2.0*rr)); col+=texture2D(txr,p)*w; }
gl_FragColor=col;
}
//---------------------------------------------------------------------------
And just to be sure also CPU side C++/VCL/OpenGL code (old api to keep it simple):
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
GLuint txr_img,txr_scr;
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(prog_id);
glUniform1i(glGetUniformLocation(prog_id,"txr" ),0);
glUniform1f(glGetUniformLocation(prog_id,"xs" ),xs);
glUniform1f(glGetUniformLocation(prog_id,"ys" ),ys);
glUniform1f(glGetUniformLocation(prog_id,"r" ),15.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,txr_img);
glUniform1i(glGetUniformLocation(prog_id,"axis"),0);
glBegin(GL_QUADS);
glColor3f(1,1,1);
glVertex2f(-1.0,-1.0);
glVertex2f(-1.0,+1.0);
glVertex2f(+1.0,+1.0);
glVertex2f(+1.0,-1.0);
glEnd();
glBindTexture(GL_TEXTURE_2D,txr_scr);
glUniform1i(glGetUniformLocation(prog_id,"axis"),1);
BYTE *dat=new BYTE[xs*ys*4];
if (dat!=NULL)
{
glReadPixels(0,0,xs,ys,GL_BGRA,GL_UNSIGNED_BYTE,dat);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, xs, ys, 0, GL_BGRA, GL_UNSIGNED_BYTE, dat);
delete[] dat;
}
glBegin(GL_QUADS);
glColor3f(1,1,1);
glVertex2f(-1.0,-1.0);
glVertex2f(-1.0,+1.0);
glVertex2f(+1.0,+1.0);
glVertex2f(+1.0,-1.0);
glEnd();
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D,0);
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
gl_init(Handle);
// textures
Byte q;
unsigned int *pp;
int xs,ys,x,y,adr,*txr;
union { unsigned int c32; Byte db[4]; } c;
Graphics::TBitmap *bmp=new Graphics::TBitmap; // new bmp
// image texture
bmp->LoadFromFile("texture.bmp"); // load from file
bmp->HandleType=bmDIB; // allow direct access to pixels
bmp->PixelFormat=pf32bit; // set pixel to 32bit so int is the same size as pixel
xs=bmp->Width; // resolution should be power of 2
ys=bmp->Height;
txr=new int[xs*ys]; // create linear framebuffer
for(adr=0,y=0;y<ys;y++)
{
pp=(unsigned int*)bmp->ScanLine[y];
for(x=0;x<xs;x++,adr++)
{
// rgb2bgr and copy bmp -> txr[]
c.c32=pp[x];
q =c.db[2];
c.db[2]=c.db[0];
c.db[0]=q;
txr[adr]=c.c32;
}
}
glGenTextures(1,&txr_img);
glEnable(GL_TEXTURE_2D); // copy it to gfx card
glBindTexture(GL_TEXTURE_2D,txr_img);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, xs, ys, 0, GL_RGBA, GL_UNSIGNED_BYTE, txr);
glDisable(GL_TEXTURE_2D);
delete[] txr;
delete bmp;
// screen texture
glGenTextures(1,&txr_scr);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,txr_scr);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
glDisable(GL_TEXTURE_2D);
int hnd,siz; char vertex[4096],fragment[4096];
hnd=FileOpen("blur.glsl_vert",fmOpenRead); siz=FileSeek(hnd,0,2); FileSeek(hnd,0,0); FileRead(hnd,vertex ,siz); vertex [siz]=0; FileClose(hnd);
hnd=FileOpen("blur.glsl_frag",fmOpenRead); siz=FileSeek(hnd,0,2); FileSeek(hnd,0,0); FileRead(hnd,fragment,siz); fragment[siz]=0; FileClose(hnd);
glsl_init(vertex,fragment);
hnd=FileCreate("GLSL.txt"); FileWrite(hnd,glsl_log,glsl_logs); FileClose(hnd);
ClientWidth=xs;
ClientHeight=ys;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
glDeleteTextures(1,&txr_img);
glDeleteTextures(1,&txr_scr);
gl_exit();
glsl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
gl_resize(ClientWidth,ClientHeight);
gl_draw();
}
//---------------------------------------------------------------------------
The gl_simple.h
can be found here: