【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理

说在开头:

PhotoShop和特效相机中有许多特效的滤镜。片元着色器时基于片元为单位执行的,完全可以实现特殊的滤镜效果。要想实现这些滤镜效果还需要简单的了解《数字图像处理》中的图像卷积与滤波的一些知识。

作者:憨豆酒(YinDou),联系我[email protected],熟悉图形学,图像处理领域,本章的源代码可在此仓库中找到: https://github.com/douysu/person-summary 如果大家发现错误以及不合理之处,还希望多多指出。

  • 我的Github
  • 我的CSDN
参考内容:
  • 《OpenGL ES 3.X 游戏开发 下卷》
  • zouxy09的博客:http://blog.csdn.net/zouxy09/article/details/49080029
    我将两者的精华内容进行了结合,同时结合了我自己的一些知识和着色器的相关知识。

图像卷积的原理

zouxy09的博客中的内容很详细,已下为他的博客中的内容
【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第1张图片
大家主要看三部分,源像素值,卷积内核的值,最终的像素值,看如何进行乘积的。(图片右上角有计算过程)

滤波器的规则要求:

1)滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2。
2)滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。当然了,这不是硬性要求了。
3)如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
4)对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。

运行效果及代码

然后看看使用不同的卷积内核对图片的影响吧。

平滑过滤

【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第2张图片

片元着色器代码(SmoothFilter.frag)
#version 400
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (binding = 1) uniform sampler2D sTexture;//纹理采样器
layout (location = 0) in vec2 vTextureCoord;//纹理坐标
layout (location = 0) out vec4 outColor;//输出片元颜色
void main() {
    //纹理偏移量单位步进
    const float stStep = 512.0;
    const float scaleFactor = 1.0/9.0;//给出最终求和时的加权因子(为调整亮度)
     //给出卷积内核中各个元素对应像素相对于待处理像素的纹理坐标偏移量
    vec2 offsets[9]=vec2[9](
          vec2(-1.0,-1.0),vec2(0.0,-1.0),vec2(1.0,-1.0),
          vec2(-1.0,0.0),vec2(0.0,0.0),vec2(1.0,0.0),
          vec2(-1.0,1.0),vec2(0.0,1.0),vec2(1.0,1.0)
    );
    //卷积内核中各个位置的值
         float kernelValues[9]=float[9] (
                1.0,1.0,1.0,
                1.0,1.0,1.0,
                1.0,1.0,1.0
         );
     //最终的颜色值
     vec4 sum=vec4(0,0,0,0);
     //颜色求和
     for(int i=0;i<9;i++){
        sum=sum+kernelValues[i]*scaleFactor*texture(sTexture, vTextureCoord+offsets[i]/stStep);
     }
     outColor=sum;
}

颜色求和部分的代码稍微不好理解。
1、首先kernelValues[i]*scaleFactor,scaleFactor为卷积内核中数的和的倒数,也就是1/9,这么做的目的是为了保持图片的亮度与原来的亮度不变,这个数大于1时,处理后的图片变亮,小于1处理后的图片变暗。
2、然后是texture(sTexture, vTextureCoord+offsets[i]/stStep),这里让纹理坐标的偏移量除以stStep,stStep为纹理坐标偏移量的步进,在着色器中控制着滤波器处理区域的大小。

边缘检测

片元着色器代码(EdgeDetection.frag)只给出卷积内核的值
//卷积内核中各个位置的值
      float kernelValues[9]=float[9] (
                  0.0,1.0,0.0,
                  1.0,-4.0,1.0,
                  0.0,1.0,0.0
           );

锐化效果

【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第3张图片

片元着色器代码(Sharpen.frag)只给出卷积内核的值
 float kernelValues[9]=float[9] (
                 0.0,-1.0,0.0,
                 -1.0,5.0,-1.0,
                 0.0,-1.0,0.0
          );

浮雕效果

片元着色器代码(SmoothFilter.frag)只给出卷积内核的值
  float kernelValues[9]=float[9] (
                 2.0,0.0,2.0,
                 0.0,0.0,0.0,
                 3.0,0.0,-6.0
          );

均值模糊

【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第4张图片

片元着色器代码(MeanBlur.frag)只给出卷积内核的值

可以多次模糊也是可以的

float kernelValues[9]=float[9] (
            0.0,1.0,0.0,
            1.0,1.0,1.0,
            0.0,1.0,0.0
     );

高斯模糊

【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第5张图片

片元着色器代码(GaussianBlur.frag)只给出卷积内核的值
 const float scaleFactor = 1.0/209.0;//给出最终求和时的加权因子(为调整亮度)
    //卷积内核中各个位置的值
     float kernelValues[9]=float[9] (
            16.0,26.0,16.0,
            26.0,41.0,26.0,
            16.0,26.0,16.0
     );

这里的加权因子应该为卷积内核所有值的和的倒数。也就是1/209。

数字图像处理中卷积边界的处理

他的博客中总结的很好。
【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第6张图片
【Shader特效8】着色器滤镜、图像卷积与滤波、数字图像处理_第7张图片

片元着色器中的边界处理

对于片元着色器中的边界处理我还没有理解清楚,希望清楚的人可以解答。

最后

这里只是简单的介绍了几种卷积内核,还有更多的卷积内核,不同的卷积内核实现的效果也不同,我只是把这些知识使用片元着色器实现了,有什么问题可以互相讨论,我的知识水平也有限,有什么问题希望大家指出。

你可能感兴趣的:(着色器特效)