高斯模糊的算法可以看阮一峰写的这篇文章,高斯模糊之所以叫高斯模糊,是因为它运用了高斯的正态分布的密度函数:
一维形式:
二维形式:
相关计算步骤:
我们在 shader 中还原一个拼多多版简易版的高斯模糊算法:
void main() {
vec2 st = gl_FragCoord / iResulution;
vec4 color = vec4(0.0);
const int coreSize = 3; // 取 3x3 像素网格
vec2 texelOffset = vec2(1.)/vec2(375., 667.); // 每个素纹的间距
float kernel[9]; // 卷积核,每个像素点的权重
kernel[0] = 1.; kernel[1] = 2.; kernel[2] = 1.;
kernel[3] = 2.; kernel[4] = 4.; kernel[5] = 2.;
kernel[6] = 1.; kernel[7] = 2.; kernel[8] = 1.;
int index = 0;
for(int y=0; yfor(int x = 0; x// 这是核心,依次取9个像素点的色值
vec4 currentColor = texture2D(inputImageTexture, st + vec2(float(-1+x)*texelOffset.x, float(-1+y)*texelOffset.y));
// 将颜色值和权重相乘,就是卷积运算
if (index == 0) { color += currentColor * kernel[0]; }
else if (index == 1) { color += currentColor * kernel[1]; }
else if (index == 2) { color += currentColor * kernel[2]; }
else if (index == 3) { color += currentColor * kernel[3]; }
else if (index == 4) { color += currentColor * kernel[4]; }
else if (index == 5) { color += currentColor * kernel[5]; }
else if (index == 6) { color += currentColor * kernel[6]; }
else if (index == 7) { color += currentColor * kernel[7]; }
else if (index == 8) { color += currentColor * kernel[8]; }
index++;
}
}
// 除以权重总和
color /= 16.0;
gl_FragColor=color;
}
复制代码
效果不明显,因为我们的权重没有很准确,而且我们的图片尺寸比较大(750x1334),如果只对 3x3 的网格进行模糊,效果不够明显,下面优化下:
void main() {
vec2 st = gl_FragCoord / iResulution;
vec4 color = vec4(0.0);
const int coreSize = 3;
// 把素纹间隔放大,这里做法比较粗暴。合理的做法是把 3x3 变成 9x9 或更大
vec2 texelOffset = vec2(2.)/vec2(375., 667.);
// 代入高斯正态分布函数计算出来的权重值
float kernel[9];
kernel[0] = .0947416; kernel[1] = .118318; kernel[2] = .0947416;
kernel[3] = .118318; kernel[4] = .147761; kernel[5] = .118318;
kernel[6] = .0947416; kernel[7] = .118318; kernel[8] = .0947416;
int index = 0;
for(int y=0; yfor(int x = 0; xvec4 currentColor = texture2D(inputImageTexture, st + vec2(float(-1+x)*texelOffset.x, float(-1+y)*texelOffset.y));
if (index == 0) { color += currentColor * kernel[0]; }
else if (index == 1) { color += currentColor * kernel[1]; }
else if (index == 2) { color += currentColor * kernel[2]; }
else if (index == 3) { color += currentColor * kernel[3]; }
else if (index == 4) { color += currentColor * kernel[4]; }
else if (index == 5) { color += currentColor * kernel[5]; }
else if (index == 6) { color += currentColor * kernel[6]; }
else if (index == 7) { color += currentColor * kernel[7]; }
else if (index == 8) { color += currentColor * kernel[8]; }
index++;
}
}
// 上面的权重已经进行了加权平均,所以这一步不需要了
// color /= 16.0;
gl_FragColor = color;
}
复制代码
只要我们把 3x3 的网格放大,如 9x9 / 16x16,或者直接放大像素间距,都可以增加模糊效果。
然而上面的实现方式性能是比较差的。因为遍历的成本太高了。通常拆成两个一维向量,这样时间复杂度就由NxNxWxH
下降为2xNxWxH
(W为图像的宽,H为图像的高)。
我们以 5x5 的卷积核,垂直方向取样为例:
void main() {
vec2 iResolution = vec2(375., 667.);
float offset[6];
offset[1] = 0.; offset[2] = 1.; offset[3] = 2.; offset[4] = 3.; offset[5] = 4.;
float weight[6];
weight[1] = 0.2270270270; weight[2] = 0.1945945946; weight[3] = 0.1216216216;
weight[4] = 0.0540540541; weight[5] = 0.0162162162;
vec4 color = texture2D(inputImageTexture, vec2(gl_FragCoord)/iResolution) * weight[0];
for (int i=1; i<=5; i++) {
color +=
texture2D(inputImageTexture, (vec2(gl_FragCoord)+vec2(0.0, offset[i]))/iResolution)
* weight[i];
color +=
texture2D(inputImageTexture, (vec2(gl_FragCoord)-vec2(0.0, offset[i]))/iResolution)
* weight[i];
}
gl_FragColor = color;
}
复制代码
水平方向也是同理的,不过上述方式依旧需要进行 9 次的texture2D()
纹理采样操作,这篇文章中介绍了一种线性采样的方式,通过对权重和间距的处理,把 9 次纹理采样操作减少到 5 次:
void main() {
vec2 iResolution = vec2(375., 667.);
float offset[4];
offset[1] = 0.; offset[2] = 1.3846153846; offset[3] = 3.2307692308;
float weight[4];
weight[1] = 0.2270270270; weight[2] = 0.3162162162; weight[3] = 0.0702702703;
vec4 color = texture2D(inputImageTexture, vec2(gl_FragCoord)/iResolution) * weight[0];
// 垂直
for (int i=1; i<=3; i++) {
color +=
texture2D(inputImageTexture, (vec2(gl_FragCoord)+vec2(0.0, offset[i]))/iResolution)
* weight[i];
color +=
texture2D(inputImageTexture, (vec2(gl_FragCoord)-vec2(0.0, offset[i]))/iResolution)
* weight[i];
}
vec4 color2 = texture2D(inputImageTexture, vec2(gl_FragCoord)/iResolution) * weight[0];
// 水平
for (int i=1; i<=3; i++) {
color2 +=
texture2D(inputImageTexture, (vec2(gl_FragCoord)+vec2(offset[i], 0.0))/iResolution)
* weight[i];
color2 +=
texture2D(inputImageTexture, (vec2(gl_FragCoord)-vec2(offset[i], 0.0))/iResolution)
* weight[i];
}
gl_FragColor = mix(color, color2, .5);
}
复制代码
肉眼是无法区分出差异的,但性能会提升:
让图片更加模糊的另外一种方式是通过对 framebuffer 多次应用模糊函数来加强模糊效果。
当然,关于高斯模糊的实现,还有很多其他的实现方式:
// 来自 https://github.com/Jam3/glsl-fast-gaussian-blur
// 只包含一个方向,需要自己叠加
// 3x3
vec4 blur5(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
vec4 color = vec4(0.0);
vec2 off1 = vec2(1.3333333333333333) * direction;
color += texture2D(image, uv) * 0.29411764705882354;
color += texture2D(image, uv + (off1 / resolution)) * 0.35294117647058826;
color += texture2D(image, uv - (off1 / resolution)) * 0.35294117647058826;
return color;
}
// 5x5
vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
vec4 color = vec4(0.0);
vec2 off1 = vec2(1.3846153846) * direction;
vec2 off2 = vec2(3.2307692308) * direction;
color += texture2D(image, uv) * 0.2270270270;
color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162;
color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162;
color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703;
color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703;
return color;
}
// 7x7
vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
vec4 color = vec4(0.0);
vec2 off1 = vec2(1.411764705882353) * direction;
vec2 off2 = vec2(3.2941176470588234) * direction;
vec2 off3 = vec2(5.176470588235294) * direction;
color += texture2D(image, uv) * 0.1964825501511404;
color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;
color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;
color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;
color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;
color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;
color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;
return color;
}
复制代码
// 来自 https://www.shadertoy.com/view/XdfGDH
// 正态分布概率密度函数
float normpdf(in float x, in float sigma) {
return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma;
}
vec3 gaussianblur(int size, sampler2D texture, vec2 resolution) {
//declare stuff
const int mSize = size;
const int kSize = (mSize-1)/2;
float kernel[mSize];
vec3 final_colour = vec3(0.0);
//create the 1-D kernel
float sigma = 7.0;
float Z = 0.0;
for (int j = 0; j <= kSize; ++j)
{
kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
}
//get the normalization factor (as the gaussian has been clamped)
for (int j = 0; j < mSize; ++j)
{
Z += kernel[j];
}
//read out the texels
for (int i=-kSize; i <= kSize; ++i)
{
for (int j=-kSize; j <= kSize; ++j)
{
final_colour += kernel[kSize+j]*kernel[kSize+i]*texture2D(texture, (gl_FragCoord.xy+vec2(float(i),float(j))) / resolution.xy).rgb;
}
}
return final_colour/(Z*Z);
}
复制代码
// 来自:https://gl-transitions.com/editor/LinearBlur
vec4 blur(vec2 _uv, sampler2D texture) {
float disp = 0.;
float intensity = .2;
const int passes = 6;
vec4 c1 = vec4(0.0);
disp = intensity*(0.5-distance(0.5, .1));
for (int xi=0; xifloat x = float(xi) / float(passes) - 0.5;
for (int yi=0; yifloat y = float(yi) / float(passes) - 0.5;
vec2 v = vec2(x, y);
float d = disp;
c1 += texture2D(texture, _uv + d*v);
}
}
c1 /= float(passes*passes);
return c1;
}
复制代码
下一篇 Shader 运动模糊 。
相关链接:
- www.ruanyifeng.com/blog/2012/1…
- blog.csdn.net/fansongy/ar…
- www.geeks3d.com/20100909/sh…
- rastergrid.com/blog/2010/0…