OpenGl 模糊

dud*_*saw 10 opengl shader glsl

我正在使用 libgdx,想要制作一个常见的高斯模糊后处理效果。按照指南,我遇到了纹理过滤的一些问题。这里是图像:

实际图像:

实际图像

半径 = 1 时模糊:

半径 = 1 时模糊

半径 = 5 进行模糊处理:

半径 = 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;
}
Run Code Online (Sandbox Code Playgroud)

全班

Spe*_*tre 7

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

图像上的高斯模糊是图像和分辨率的高斯加权矩阵之间的卷积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;
    }
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

输出r=5

1次r=5

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

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

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

[编辑1] 2遍方法

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

2次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;
    }
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

这里是片段着色器:

//---------------------------------------------------------------------------
// 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;
    }
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

并且为了确保 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();
    }
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

可以gl_simple.h在这里找到:

  • 从问题中尚不清楚,但 OP 正在做的事情是高斯模糊(或至少是近似值)。诀窍在于它在 x 和 y 方向上是可分离的,因此您应用着色器两次:一次使用“dir = (0, 1)”,一次使用“dir = (1, 0)”。这将纹理获取的数量从 n² 减少到 2n,因此效率更高,尤其是对于大型内核。另请参阅:http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ (2认同)

Tho*_*mas 5

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

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

如果这变得太慢,要进行优化,您可以利用本文中的线性插值技术。由于线性插值允许您以 1 的价格计算两个相邻纹理像素之间的任意加权平均值,因此您可以获得仅执行 5 次纹理提取而不是 9 次的等效滤波器,或者使用 9 次纹理提取来获得大小为 17 的内核。从下采样图像金字塔中采样也是一种可能性。

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

vec2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)
Run Code Online (Sandbox Code Playgroud)

你可以简单地写:

tc - 4.0*blur*dir
Run Code Online (Sandbox Code Playgroud)

其他 7 行也类似。