OpenGl 模糊

OpenGl blurring

我正在使用 libgdx 并想制作一个普通的高斯模糊 post 处理效果。按照 this 指南,我遇到了一些纹理过滤问题。图片在这里:

实际图片:

模糊半径 = 1:

模糊半径 = 5:

在最简单的情况下,我只有一个透明的帧缓冲区对象,其中渲染了一些对象。然后我需要对此应用一些着色器效果,基本上只是模糊然后将结果渲染到屏幕上。我也想调整模糊半径,但如果我将半径设置为大于 1,它就会变得非常粗糙。猜想它应该与一些线性过滤一起使用,但它不在这里。所以我只需要使用软模糊和可配置半径应用相同的效果,也许还有一些其他着色器方面的选项。而且我还尝试将线性过滤显式分配给 FBO 纹理,这不会改变任何东西。

片段着色器:

//"in" attributes from our vertex shader
varying vec4 vColor;
varying vec2 vTexCoord;

//declare uniforms
uniform sampler2D u_texture;
uniform float resolution;
uniform float radius;
uniform vec2 dir;

void main() {
    //this will be our RGBA sum
    vec4 sum = vec4(0.0);
    
    //our original texcoord for this fragment
    vec2 tc = vTexCoord;
    
    //the amount to blur, i.e. how far off center to sample from 
    //1.0 -> blur by one pixel
    //2.0 -> blur by two pixels, etc.
    float blur = radius/resolution; 
    
    //the direction of our blur
    //(1.0, 0.0) -> x-axis blur
    //(0.0, 1.0) -> y-axis blur
    float hstep = dir.x;
    float vstep = dir.y;
    
    //apply blurring, using a 9-tap filter with predefined gaussian weights
    
    sum += texture2D(u_texture, vec2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)) * 0.0162162162;
    sum += texture2D(u_texture, vec2(tc.x - 3.0*blur*hstep, tc.y - 3.0*blur*vstep)) * 0.0540540541;
    sum += texture2D(u_texture, vec2(tc.x - 2.0*blur*hstep, tc.y - 2.0*blur*vstep)) * 0.1216216216;
    sum += texture2D(u_texture, vec2(tc.x - 1.0*blur*hstep, tc.y - 1.0*blur*vstep)) * 0.1945945946;
    
    sum += texture2D(u_texture, vec2(tc.x, tc.y)) * 0.2270270270;
    
    sum += texture2D(u_texture, vec2(tc.x + 1.0*blur*hstep, tc.y + 1.0*blur*vstep)) * 0.1945945946;
    sum += texture2D(u_texture, vec2(tc.x + 2.0*blur*hstep, tc.y + 2.0*blur*vstep)) * 0.1216216216;
    sum += texture2D(u_texture, vec2(tc.x + 3.0*blur*hstep, tc.y + 3.0*blur*vstep)) * 0.0540540541;
    sum += texture2D(u_texture, vec2(tc.x + 4.0*blur*hstep, tc.y + 4.0*blur*vstep)) * 0.0162162162;

    gl_FragColor = vColor * sum;
}

完整class

据我所知,您根本没有进行高斯模糊...

图像上的高斯模糊是图像与分辨率高斯加权矩阵之间的卷积 1+2*r,其中 r 是模糊的半径。所以输出的颜色应该是所有像素的加权总和,距离目标像素 r

你所做的只是 9 像素的加权总和,而不管错误的半径(在我的选择中),因为你应该总结 ~6.28*r*r 像素。所以我希望有 2 个嵌套的 for 循环...

这是我刚刚拼凑的一个 GLSL 小例子:

//---------------------------------------------------------------------------
// 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;
    }
//---------------------------------------------------------------------------

r=5的输出:

我对权重进行了归一化处理,因此无论选择什么,亮度都不会从原始纹理改变太多 r...但是 r=5 是最大的错误,所有其他半径似乎都好得多...这可能是因为圆周与圆内条件混叠...

您还应该添加一些边缘情况处理(当在纹理边缘附近进行卷积时),因为总和像素的数量不同,因此系数应该相应地缩放。

为了描述上面的代码: 2 个循环只是循环遍历所有像素,从 pos in texture 到半径 r 和从像素 x,y 和 NDC pos 转换为纹理坐标 p。然后为每个位置计算高斯权重,然后将其用于加权和。所有这些之后,输出的结果颜色。

[Edit1] 2 遍方法

我将其移植到 2 pass 渲染(首先整合水平线,然后整合垂直线)我得到了这个输出 (r=5):

此处顶点着色器:

//---------------------------------------------------------------------------
// 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;
    }
//---------------------------------------------------------------------------

这里片段着色器:

//---------------------------------------------------------------------------
// 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;
    }
//---------------------------------------------------------------------------

并且还要确保 CPU 侧 C++/VCL/OpenGL 代码(旧 api 保持简单):

//---------------------------------------------------------------------------
#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();
    }
//---------------------------------------------------------------------------

gl_simple.h可以在这里找到:

这不是由于缺少线性插值造成的。此着色器沿每个轴仅执行 9 次纹理提取。你不能指望只采样 9 次就可以用更大的内核获得平滑的模糊,因为你跳过了很多可能包含重要信息的像素。只有 radius = 1 有效。

对于较大的模糊,您要么需要更大的内核,要么多次应用较小的内核。

要在速度太慢的情况下进行优化,您可以利用 this article 中的线性插值技术。因为线性插值允许您以一个的价格计算两个相邻纹素之间的任意加权平均值,所以您可以获得一个等效的过滤器,它只执行 5 次纹理提取而不是 9 次,或者使用 9 次纹理提取来获得大小为 17 的内核。巧妙地从下采样图像的金字塔中采样也是可能的。

顺便说一下,而不是这个冗长的东西:

vec2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)

你可以简单地写:

tc - 4.0*blur*dir

其他 7 行类似。