若是使用过PS之类的图像处理软件,相信对于模糊滤镜不会陌生,图像处理软件提供了众多的模糊算法。高斯模糊是其中的一种。html
在咱们的智慧楼宇的项目中,要求对楼宇实现楼宇发光的效果。 好比以下图所示的简单楼宇效果:web
楼宇发光效果须要用的算法之一就是高斯模糊。算法
高斯模糊算法是计算机图形学领域中一种使用普遍的技术, 是一种图像空间效果,用于对图像进行模糊处理,建立原始图像的柔和模糊版本。
使用高斯模糊的效果,结合一些其余的算法,还能够产生发光,光晕,景深,热雾和模糊玻璃效果。微信
图像模糊的原理,简单而言,就是针对图像的每个像素,其颜色取其周边像素的平均值。不一样的模糊算法,对周边的定义不同,平均的算法也不同。 好比以前写#过的一篇文章,webgl实现径向模糊,就是模糊算法中的一种。函数
在理解高斯模糊以前,咱们先理解比较容易的均值模糊。所谓均值模糊
其原理就是取像素点周围(上下左右)像素的平均值(其中也会包括自身)。以下图所示:
webgl
能够看出,对于某个像素点,当搜索半径为1的时候,影响其颜色值的像素是9个像素(包括本身和周边的8个像素)。假设每一个像素对于中心像素的影响都是同样的,那么每一个像素的影响度就是1/9。以下图所示:
this
上面这个3*3的影响度的数字矩阵,一般称之为卷积核。code
那么最终中心点的值的求和以下图所示:
最终的值是:orm
(8 * 1 + 1 * 2 / (8 + 1) ) = 10/9
当计算像素的颜色时候,对于像素的RGB每个通道都进行的上述平均计算便可。htm
上面的计算过程就是一种卷积滤镜。所谓卷积滤镜,通俗来讲,就是一种组合一组数值的算法。
若是搜索半径变成2,则会变成25个像素的平均,搜索半径越大,就会越模糊。像素个数与搜索半径的关系以下:
(1 + r * 2)的平方 // r = 1,结果为9,r=2,结果为25,r=3 结果为49.
一般 NxN会被称之卷积核的大小。好比3x3,5x5。
在均值模糊的计算中,参与的每一个像素,对中心像素的贡献值都是同样的,这是均值模糊的特色。也就是,每一个像素的权重都是同样的。
若是使用简单平均,显然不是很合理,由于图像都是连续的,越靠近的点关系越密切,越远离的点关系越疏远。所以,加权平均更合理,距离越近的点权重越大,距离越远的点权重越小。
正态分布整好知足上述的的分布需求,以下图所示:
能够看出,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。
在计算平均值的时候,咱们只须要将"中心点"做为原点,其余点按照其在正态曲线上的位置,分配权重,就能够获得一个加权平均值。
高斯函数是描述正态分布的数学公式。公式以下:
其中,μ是x的均值,能够理解为正态分布的中心位置,σ是x的方差。由于计算平均值的时候,中心点就是原点,因此μ等于0。
若是是二维,则有:
能够看出二维高斯函数中,x和y相对是独立的。也就是说:
G(x,y) = G(x) + G(y)
这个特性的好处是,能够把二维的高斯函数,拆解成两个独立的一维高斯函数。能够提升效率。实际上,高斯模糊运用的一维高斯函数,而不是使用二维。
高斯模糊的原理和前面介绍的均值模糊的原理基本上同样,只是均值模糊在计算平均值的时候,周边像素的权重都是同样的。而高斯模糊下,周边像素的权重值却使用高斯函数进行计算,这也是高斯模糊的之因此被称为高斯模糊的缘由。
好比当σ取值为则模糊半径为1的权重矩阵以下:
这9个点的权重总和等于0.4787147,若是只计算这9个点的加权平均,还必须让它们的权重之和等于1,所以上面9个值还要分别除以0.4787147,获得最终的权重矩阵。
了解了高斯模糊的基本原理以后,来看看高斯模糊在webgl中基本渲染流程:
上面第二部,施加高斯模糊算法,通常又会分红两步:
对于第一步,首先是渲染到纹理对象,这输入渲染到纹理的知识,此处再也不赘述,大体大代码结构以下:
···
frameBuffer.bind();
renderScene();
frameBuffer.unbind();
···
把renderScene放到frameBuffer.bind以后,会把场景绘制到frameBuffer关联的纹理对象上面。
而后是第二步,执行高斯模糊算法进行
pass(params={},count = 1,inputFrameBuffer){ let {options,fullScreen } = this; inputFrameBuffer = inputFrameBuffer || this.inputFrameBuffer; let {gl,gaussianBlurProgram,verticalBlurFrameBuffer,horizontalBlurFrameBuffer} = this; let {width,height} = options; gl.useProgram(gaussianBlurProgram); if(width == null){ width = verticalBlurFrameBuffer.width; height = verticalBlurFrameBuffer.height; } verticalBlurFrameBuffer.bind(); fullScreen.enable(gaussianBlurProgram,true); gl.activeTexture(gl.TEXTURE0 + inputFrameBuffer.textureUnit); // 激活gl.TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, inputFrameBuffer.colorTexture); // 绑定贴图对象 gl.uniform1i(gaussianBlurProgram.uColorTexture, inputFrameBuffer.textureUnit); gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]); gl.uniform2fv(gaussianBlurProgram.uDirection,[0,1]); // 垂直方向 gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 3); gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5); gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0); fullScreen.draw(); verticalBlurFrameBuffer.unbind(); if(horizontalBlurFrameBuffer){ // renderToScreen horizontalBlurFrameBuffer.bind(gl); } gl.activeTexture(gl.TEXTURE0 + verticalBlurFrameBuffer.textureUnit); // 激活gl.TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, verticalBlurFrameBuffer.colorTexture); // 绑定贴图对象 gl.uniform1i(gaussianBlurProgram.uColorTexture, verticalBlurFrameBuffer.textureUnit); gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]); gl.uniform2fv(gaussianBlurProgram.uDirection,[1,0]); // 水平方向 gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 2); gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5); gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0); fullScreen.draw(); if(horizontalBlurFrameBuffer){ horizontalBlurFrameBuffer.unbind(); } if(count > 1){ this.pass(params,count - 1,this.horizontalBlurFrameBuffer); } return horizontalBlurFrameBuffer; }
其中inputFrameBuffer 是第一步渲染时候的frameBuffer对象,做为输入参数传递过来。 而后开始执行垂直方向的高斯模糊算法,
verticalBlurFrameBuffer.bind(); fullScreen.enable(gaussianBlurProgram,true); gl.activeTexture(gl.TEXTURE0 + inputFrameBuffer.textureUnit); // 激活gl.TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, inputFrameBuffer.colorTexture); // 绑定贴图对象 gl.uniform1i(gaussianBlurProgram.uColorTexture, inputFrameBuffer.textureUnit); gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]); gl.uniform2fv(gaussianBlurProgram.uDirection,[0,1]); // 垂直方向 gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 3); gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5); gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0); fullScreen.draw(); verticalBlurFrameBuffer.unbind();
在以后执行水平方向的模糊算法:
if(horizontalBlurFrameBuffer){ // renderToScreen horizontalBlurFrameBuffer.bind(gl); } gl.activeTexture(gl.TEXTURE0 + verticalBlurFrameBuffer.textureUnit); // 激活gl.TEXTURE0 gl.bindTexture(gl.TEXTURE_2D, verticalBlurFrameBuffer.colorTexture); // 绑定贴图对象 gl.uniform1i(gaussianBlurProgram.uColorTexture, verticalBlurFrameBuffer.textureUnit); gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]); gl.uniform2fv(gaussianBlurProgram.uDirection,[1,0]); // 水平方向 gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 2); gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5); gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0); fullScreen.draw(); if(horizontalBlurFrameBuffer){ horizontalBlurFrameBuffer.unbind(); }
shader 代码分红两部分,一个顶点着色器代码:
const gaussianBlurVS = ` attribute vec3 aPosition; attribute vec2 aUv; varying vec2 vUv; void main() { vUv = aUv; gl_Position = vec4(aPosition, 1.0); } `;
另一个是片元着色器代码:
const gaussianBlurFS = ` precision highp float; precision highp int; #define HIGH_PRECISION #define SHADER_NAME ShaderMaterial #define MAX_KERNEL_RADIUS 49 #define SIGMA 11 varying vec2 vUv; uniform sampler2D uColorTexture; uniform vec2 uTexSize; uniform vec2 uDirection; uniform float uExposure; uniform bool uUseLinear; uniform float uRadius; float gaussianPdf(in float x, in float sigma) { return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; } void main() { vec2 invSize = 1.0 / uTexSize; float fSigma = float(SIGMA); float weightSum = gaussianPdf(0.0, fSigma); vec4 diffuseSum = texture2D( uColorTexture, vUv).rgba * weightSum; float radius = uRadius; for( int i = 1; i < MAX_KERNEL_RADIUS; i ++ ) { float x = float(i); if(x > radius){ break; } float gaussianPdf(x, fSigma),t = x; vec2 uvOffset = uDirection * invSize * t; vec4 sample1 = texture2D( uColorTexture, vUv + uvOffset).rgba; vec4 sample2 = texture2D( uColorTexture, vUv - uvOffset).rgba; diffuseSum += (sample1 + sample2) * w; weightSum += 2.0 * w; } vec4 result = vec4(1.0) - exp(-diffuseSum/weightSum * uExposure); gl_FragColor = result; } `
最终渲染的效果以下,案例中渲染的是一个球体的线框:
目前项目中用到的主要是发光楼宇的效果。 下面是几个案例图,分享给你们看看:
固然还有更多的应用场景,读者能够自行探索。
http://www.ruanyifeng.com/blog/2012/11/gaussian_blur.html
若是对可视化感兴趣,能够和我交流,微信541002349. 另外关注公众号“ITMan彪叔” 能够及时收到更多有价值的文章。