【问题标题】:OpenGl blurringOpenGL模糊
【发布时间】:2020-11-14 19:19:40
【问题描述】:

我正在使用 libgdx,想制作一个普通的高斯模糊后处理效果。遵循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

【问题讨论】:

    标签: opengl glsl shader


    【解决方案1】:

    这不是由于缺少线性插值造成的。此着色器仅沿每个轴进行 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 行类似。

    【讨论】:

      【解决方案2】:

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

      图像上的高斯模糊是图像与分辨率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 个循环只是循环遍历从 postexture 到半径 r 的所有像素,并从像素 x,y 和 NDC pos 转换纹理坐标p。然后对于每个位置计算高斯权重,然后将其用于加权和。完成所有这些后,输出颜色。

      [Edit1] 2 pass 方法

      我将它移植到 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;
          }
      //---------------------------------------------------------------------------
      

      这里片段着色器:

      //---------------------------------------------------------------------------
      // 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 可以在这里找到:

      【讨论】:

      • 这个问题并不清楚,但是 OP 正在做什么 高斯模糊(或至少是近似值)。诀窍在于它在 x 和 y 方向上是可分离的,因此您应用着色器两次:一次使用 dir = (0, 1),一次使用 dir = (1, 0)。这将纹理提取的数量从 n² 减少到 2n,因此效率更高,尤其是对于大型内核。另见:rastergrid.com/blog/2010/09/…
      • @Thomas 好点,但如果情况仍然需要 2 个循环(但不再嵌套)并且 OP 没有......而且内核很可能不再是圆形的,而是方形的。 ..略有偏差,但我认为这对模糊图像没什么大不了...
      • 是的,很抱歉我对算法本身的不清楚。也非常感谢有关如何执行完整迭代的示例。
      • @dudusaw 我添加了 2 pass 版本,还修复了系数中有一个小错字...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-04
      • 1970-01-01
      • 2016-07-08
      • 2020-05-21
      • 2012-08-14
      • 2015-04-23
      • 2023-04-02
      相关资源
      最近更新 更多