PhotoShop和特效相机中有许多特效的滤镜。片元着色器时基于片元为单位执行的,完全可以实现特殊的滤镜效果。要想实现这些滤镜效果还需要简单的了解《数字图像处理》中的图像卷积与滤波的一些知识。
作者:憨豆酒(YinDou),联系我[email protected],熟悉图形学,图像处理领域,本章的源代码可在此仓库中找到: https://github.com/douysu/person-summary 如果大家发现错误以及不合理之处,还希望多多指出。
zouxy09的博客中的内容很详细,已下为他的博客中的内容
大家主要看三部分,源像素值,卷积内核的值,最终的像素值,看如何进行乘积的。(图片右上角有计算过程)
1)滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2。
2)滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。当然了,这不是硬性要求了。
3)如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
4)对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。
然后看看使用不同的卷积内核对图片的影响吧。
#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为纹理坐标偏移量的步进,在着色器中控制着滤波器处理区域的大小。
//卷积内核中各个位置的值
float kernelValues[9]=float[9] (
0.0,1.0,0.0,
1.0,-4.0,1.0,
0.0,1.0,0.0
);
float kernelValues[9]=float[9] (
0.0,-1.0,0.0,
-1.0,5.0,-1.0,
0.0,-1.0,0.0
);
float kernelValues[9]=float[9] (
2.0,0.0,2.0,
0.0,0.0,0.0,
3.0,0.0,-6.0
);
可以多次模糊也是可以的
float kernelValues[9]=float[9] (
0.0,1.0,0.0,
1.0,1.0,1.0,
0.0,1.0,0.0
);
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。
对于片元着色器中的边界处理我还没有理解清楚,希望清楚的人可以解答。
这里只是简单的介绍了几种卷积内核,还有更多的卷积内核,不同的卷积内核实现的效果也不同,我只是把这些知识使用片元着色器实现了,有什么问题可以互相讨论,我的知识水平也有限,有什么问题希望大家指出。