webgl智慧楼宇发光效果算法系列之高斯模糊

简介: webgl智慧楼宇发光效果算法系列之高斯模糊高斯模糊简介高斯模糊的原理说明均值模糊正态分布高斯函数高斯模糊渲染流程渲染流程代码shader 代码应用案例参考文档

webgl智慧楼宇发光效果算法系列之高斯模糊


如果使用过PS之类的图像处理软件,相信对于模糊滤镜不会陌生,图像处理软件提供了众多的模糊算法。高斯模糊是其中的一种。


在我们的智慧楼宇的项目中,要求对楼宇实现楼宇发光的效果。比如如下图所示的简单楼宇效果:


微信图片_20220425132058.gif


楼宇发光效果需要用的算法之一就是高斯模糊。


高斯模糊简介


高斯模糊算法是计算机图形学领域中一种使用广泛的技术, 是一种图像空间效果,用于对图像进行模糊处理,创建原始图像的柔和模糊版本。


使用高斯模糊的效果,结合一些其他的算法,还可以产生发光,光晕,景深,热雾和模糊玻璃效果。


高斯模糊的原理说明


图像模糊的原理,简单而言,就是针对图像的每一个像素,其颜色取其周边像素的平均值。不同的模糊算法,对周边的定义不一样,平均的算法也不一样。比如之前写#过的一篇文章,webgl实现径向模糊,就是模糊算法中的一种。


均值模糊


在理解高斯模糊之前,我们先理解比较容易的均值模糊。所谓均值模糊

其原理就是取像素点周围(上下左右)像素的平均值(其中也会包括自身)。如下图所示:


微信图片_20220425132102.png


可以看出,对于某个像素点,当搜索半径为1的时候,影响其颜色值的像素是9个像素(包括自己和周边的8个像素)。假设每个像素对于中心像素的影响都是一样的,那么每个像素的影响度就是1/9。如下图所示:


微信图片_20220425132106.png


上面这个3*3的影响度的数字矩阵,通常称之为卷积核。

那么最终中心点的值的求和如下图所示:


微信图片_20220425132108.png


最终的值是:


1(8 *  1 + 1 * 2 / (8 + 1) ) = 10/9


当计算像素的颜色时候,对于像素的RGB每一个通道都进行的上述平均计算即可。

上面的计算过程就是一种卷积滤镜。所谓卷积滤镜,通俗来说,就是一种组合一组数值的算法。


如果搜索半径变成2,则会变成25个像素的平均,搜索半径越大,就会越模糊。像素个数与搜索半径的关系如下:


1(1 + r * 2)的平方 // r = 1,结果为9,r=2,结果为25,r=3 结果为49.


通常 NxN会被称之卷积核的大小。比如3x3,5x5。

在均值模糊的计算中,参与的每个像素,对中心像素的贡献值都是一样的,这是均值模糊的特点。也就是,每个像素的权重都是一样的。


正态分布


如果使用简单平均,显然不是很合理,因为图像都是连续的,越靠近的点关系越密切,越远离的点关系越疏远。因此,加权平均更合理,距离越近的点权重越大,距离越远的点权重越小。


正态分布整好满足上述的的分布需求,如下图所示:


微信图片_20220425132116.png


可以看出,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。

在计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。


高斯函数


高斯函数是描述正态分布的数学公式。公式如下:


微信图片_20220425132120.png


其中,μ是x的均值,可以理解为正态分布的中心位置,σ是x的方差。因为计算平均值的时候,中心点就是原点,所以μ等于0。


微信图片_20220425132123.png


如果是二维,则有:微信图片_20220425132129.png




可以看出二维高斯函数中,x和y相对是独立的。也就是说:


1G(x,y) = G(x) + G(y)


这个特性的好处是,可以把二维的高斯函数,拆解成两个独立的一维高斯函数。可以提高效率。实际上,高斯模糊运用的一维高斯函数,而不是使用二维。


高斯模糊


高斯模糊的原理和前面介绍的均值模糊的原理基本上一样,只是均值模糊在计算平均值的时候,周边像素的权重都是一样的。而高斯模糊下,周边像素的权重值却使用高斯函数进行计算,这也是高斯模糊的之所以被称为高斯模糊的原因。


比如当σ取值为则模糊半径为1的权重矩阵如下:


微信图片_20220425132135.png


这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。


微信图片_20220425132138.png


渲染流程


了解了高斯模糊的基本原理之后,来看看高斯模糊在webgl中基本渲染流程:

  1. 首先,按照正常流程把场景或者图像渲染到一个纹理对象上面,需要使用FrameBuffer功能。
  2. 对纹理对象进行施加高斯模糊算法,得到最终的高斯模糊的纹理对象。


上面第二部,施加高斯模糊算法,一般又会分成两步:

  1. 先施加垂直方向的高斯模糊算法;
  2. 在垂直模糊的基础上进行水平方向的高斯模糊算法。
    当然,也可以先水平后垂直,结果是一样的。  分两步高斯模糊算法和一步进行两个方向的高斯模糊算法的结果基本是一致的,但是却可以提高算法的效率。有人可能说,多模糊了一步,为啥还提高了效率。这么来说吧,如果是3x3大小的高斯模糊:
    分两步要获取的像素数量是 3 + 3 = 6;而一步却是3 x 3 = 9。如果是5x5大小的高斯模糊:分两步要获取的像素数量是 5+5=10;而一步却是5 x 5=25 。显然可以算法执行效率。


渲染流程代码


对于第一步,首先是渲染到纹理对象,这输入渲染到纹理的知识,此处不再赘述,大致大代码结构如下:

···

frameBuffer.bind();

renderScene();

frameBuffer.unbind();

···


把renderScene放到frameBuffer.bind之后,会把场景绘制到frameBuffer关联的纹理对象上面。


然后是第二步,执行高斯模糊算法进行


1pass(params={},count = 1,inputFrameBuffer){
 2        let {options,fullScreen } = this;
 3        inputFrameBuffer = inputFrameBuffer || this.inputFrameBuffer;
 4        let {gl,gaussianBlurProgram,verticalBlurFrameBuffer,horizontalBlurFrameBuffer} = this;
 5        let {width,height} = options;    
 6
 7        gl.useProgram(gaussianBlurProgram);
 8        if(width == null){
 9          width = verticalBlurFrameBuffer.width;
10          height = verticalBlurFrameBuffer.height;
11        }
12        verticalBlurFrameBuffer.bind();
13        fullScreen.enable(gaussianBlurProgram,true);
14        gl.activeTexture(gl.TEXTURE0 + inputFrameBuffer.textureUnit); //  激活gl.TEXTURE0
15        gl.bindTexture(gl.TEXTURE_2D, inputFrameBuffer.colorTexture); // 绑定贴图对象
16        gl.uniform1i(gaussianBlurProgram.uColorTexture, inputFrameBuffer.textureUnit);
17        gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]);
18        gl.uniform2fv(gaussianBlurProgram.uDirection,[0,1]); // 垂直方向
19        gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 3); 
20        gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5);
21        gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0);
22
23
24        fullScreen.draw();
25        verticalBlurFrameBuffer.unbind();
26
27        if(horizontalBlurFrameBuffer){  // renderToScreen
28          horizontalBlurFrameBuffer.bind(gl);
29        }
30        gl.activeTexture(gl.TEXTURE0 + verticalBlurFrameBuffer.textureUnit); //  激活gl.TEXTURE0
31        gl.bindTexture(gl.TEXTURE_2D, verticalBlurFrameBuffer.colorTexture); // 绑定贴图对象
32        gl.uniform1i(gaussianBlurProgram.uColorTexture, verticalBlurFrameBuffer.textureUnit);
33        gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]);
34        gl.uniform2fv(gaussianBlurProgram.uDirection,[1,0]); // 水平方向
35        gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 2); 
36        gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5);
37        gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0);
38
39        fullScreen.draw();
40        if(horizontalBlurFrameBuffer){
41          horizontalBlurFrameBuffer.unbind();
42        }
43        if(count > 1){
44          this.pass(params,count - 1,this.horizontalBlurFrameBuffer);
45        }
46        return horizontalBlurFrameBuffer;
47
48    }


其中inputFrameBuffer 是第一步渲染时候的frameBuffer对象,作为输入参数传递过来。 然后开始执行垂直方向的高斯模糊算法,


1verticalBlurFrameBuffer.bind();
 2        fullScreen.enable(gaussianBlurProgram,true);
 3        gl.activeTexture(gl.TEXTURE0 + inputFrameBuffer.textureUnit); //  激活gl.TEXTURE0
 4        gl.bindTexture(gl.TEXTURE_2D, inputFrameBuffer.colorTexture); // 绑定贴图对象
 5        gl.uniform1i(gaussianBlurProgram.uColorTexture, inputFrameBuffer.textureUnit);
 6        gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]);
 7        gl.uniform2fv(gaussianBlurProgram.uDirection,[0,1]); // 垂直方向
 8        gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 3); 
 9        gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5);
10        gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0);
11
12
13        fullScreen.draw();
14        verticalBlurFrameBuffer.unbind();


在之后执行水平方向的模糊算法:


1 if(horizontalBlurFrameBuffer){  // renderToScreen
 2          horizontalBlurFrameBuffer.bind(gl);
 3        }
 4        gl.activeTexture(gl.TEXTURE0 + verticalBlurFrameBuffer.textureUnit); //  激活gl.TEXTURE0
 5        gl.bindTexture(gl.TEXTURE_2D, verticalBlurFrameBuffer.colorTexture); // 绑定贴图对象
 6        gl.uniform1i(gaussianBlurProgram.uColorTexture, verticalBlurFrameBuffer.textureUnit);
 7        gl.uniform2fv(gaussianBlurProgram.uTexSize, [width,height]);
 8        gl.uniform2fv(gaussianBlurProgram.uDirection,[1,0]); // 水平方向
 9        gl.uniform1f(gaussianBlurProgram.uExposure,params.exposure || 2); 
10        gl.uniform1f(gaussianBlurProgram.uRadius,params.radius || 5);
11        gl.uniform1f(gaussianBlurProgram.uUseLinear,params.useLinear || 0.0);
12
13        fullScreen.draw();
14        if(horizontalBlurFrameBuffer){
15          horizontalBlurFrameBuffer.unbind();
16        }


shader 代码


shader 代码分成两部分,一个顶点着色器代码:


1const gaussianBlurVS =  `
2  attribute vec3 aPosition;
3  attribute vec2 aUv;
4  varying vec2 vUv;
5  void main() {
6    vUv = aUv;
7    gl_Position = vec4(aPosition, 1.0);
8  }
9`;


另外一个是片元着色器代码:


1const gaussianBlurFS = `
 2precision highp float;
 3precision highp int;
 4#define HIGH_PRECISION
 5#define SHADER_NAME ShaderMaterial
 6#define MAX_KERNEL_RADIUS 49
 7#define SIGMA 11
 8varying vec2 vUv;
 9uniform sampler2D uColorTexture;
10uniform vec2 uTexSize;
11uniform vec2 uDirection;
12uniform float uExposure;
13uniform bool uUseLinear;
14uniform float uRadius;
15
16float gaussianPdf(in float x, in float sigma) {
17  return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;
18}
19void main() {
20  vec2 invSize = 1.0 / uTexSize;
21  float fSigma = float(SIGMA);
22  float weightSum = gaussianPdf(0.0, fSigma);
23  vec4 diffuseSum = texture2D( uColorTexture, vUv).rgba * weightSum;
24  float radius = uRadius;
25
26  for( int i = 1; i < MAX_KERNEL_RADIUS; i ++ ) {
27    float x = float(i);
28    if(x > radius){
29      break;
30    }
31    float gaussianPdf(x, fSigma),t = x;
32    vec2 uvOffset = uDirection * invSize * t;
33    vec4 sample1 = texture2D( uColorTexture, vUv + uvOffset).rgba;
34    vec4 sample2 = texture2D( uColorTexture, vUv - uvOffset).rgba;
35    diffuseSum += (sample1 + sample2) * w;
36    weightSum += 2.0 * w;
37
38  }
39  vec4 result = vec4(1.0) - exp(-diffuseSum/weightSum * uExposure);
40  gl_FragColor = result;
41}
42`


最终渲染的效果如下,案例中渲染的是一个球体的线框:


微信图片_20220425132146.png


应用案例


目前项目中用到的主要是发光楼宇的效果。下面是几个案例图,分享给大家看看:


微信图片_20220425132150.png

微信图片_20220425132153.png


当然还有更多的应用场景,读者可以自行探索。


参考文档

http://www.ruanyifeng.com/blog/2012/11/gaussian_blur.html


相关文章
|
6月前
|
算法
【MATLAB】数据拟合第12期-基于高斯核回归的拟合算法
【MATLAB】数据拟合第12期-基于高斯核回归的拟合算法
291 0
|
6月前
|
算法
【MATLAB】语音信号识别与处理:高斯加权移动平均滤波算法去噪及谱相减算法呈现频谱
【MATLAB】语音信号识别与处理:高斯加权移动平均滤波算法去噪及谱相减算法呈现频谱
245 0
|
5月前
|
算法
m基于GA遗传优化的高斯白噪声信道SNR估计算法matlab仿真
**MATLAB2022a模拟展示了遗传算法在AWGN信道中估计SNR的效能。该算法利用生物进化原理全局寻优,解决通信系统中复杂环境下的SNR估计问题。核心代码执行多代选择、重组和突变操作,逐步优化SNR估计。结果以图形形式对比了真实SNR与估计值,并显示了均方根误差(RMSE),体现了算法的准确性。**
59 0
|
6月前
|
机器学习/深度学习 算法 图形学
告别3D高斯Splatting算法,带神经补偿的频谱剪枝高斯场SUNDAE开源了
【5月更文挑战第26天】SUNDAE,一种结合频谱剪枝和神经补偿的高斯场方法,已开源,解决了3D高斯Splatting的内存消耗问题。SUNDAE通过建模基元间关系并剪枝不必要的元素,降低内存使用,同时用神经网络补偿质量损失。在Mip-NeRF360数据集上,SUNDAE实现26.80 PSNR和145 FPS,内存仅为104MB,优于传统算法。然而,其计算复杂性、参数优化及对其他3D表示方法的适用性仍有待改进。代码开源,期待进一步研究。[论文链接](https://arxiv.org/abs/2405.00676)
50 2
|
算法 定位技术
最优化方法(最速下降、牛顿法、高斯牛顿法、LM算法)
最优化方法(最速下降、牛顿法、高斯牛顿法、LM算法)
654 0
最优化方法(最速下降、牛顿法、高斯牛顿法、LM算法)
|
机器学习/深度学习 人工智能 资源调度
【机器学习】聚类算法——高斯混合聚类(理论+图解)
【机器学习】聚类算法——高斯混合聚类(理论+图解)
392 0
【机器学习】聚类算法——高斯混合聚类(理论+图解)
|
算法 数据挖掘
模糊聚类FCM算法和基于GA遗传优化的FCM聚类算法matlab仿真
模糊聚类FCM算法和基于GA遗传优化的FCM聚类算法matlab仿真
277 0
模糊聚类FCM算法和基于GA遗传优化的FCM聚类算法matlab仿真
|
机器学习/深度学习 传感器 算法
【边缘检测】基于模糊算法的图像边缘检测附matlab代码
【边缘检测】基于模糊算法的图像边缘检测附matlab代码
|
算法 Python
算法与python:使用高斯消元法计算行列式的值,并分析时间复杂度
算法与python:使用高斯消元法计算行列式的值,并分析时间复杂度
203 0
|
机器学习/深度学习 传感器 算法
【智能优化算法-灰狼算法】基于Cat混沌与高斯变异的灰狼优化算法求解单目标优化问题附Matlab代码
【智能优化算法-灰狼算法】基于Cat混沌与高斯变异的灰狼优化算法求解单目标优化问题附Matlab代码
【智能优化算法-灰狼算法】基于Cat混沌与高斯变异的灰狼优化算法求解单目标优化问题附Matlab代码