webgl智慧楼宇发光效果算法系列之高斯模糊
webgl智慧楼宇发光效果算法系列之高斯模糊
如果使用过PS之类的图像处理软件,相信对于模糊滤镜不会陌生,图像处理软件提供了众多的模糊算法。高斯模糊是其中的一种。
在我们的智慧楼宇的项目中,要求对楼宇实现楼宇发光的效果。 比如如下图所示的简单楼宇效果:
楼宇发光效果需要用的算法之一就是高斯模糊。
高斯模糊简介
高斯模糊算法是计算机图形学领域中一种使用广泛的技术, 是一种图像空间效果,用于对图像进行模糊处理,创建原始图像的柔和模糊版本。
使用高斯模糊的效果,结合一些其他的算法,还可以产生发光,光晕,景深,热雾和模糊玻璃效果。
高斯模糊的原理说明
图像模糊的原理,简单而言,就是针对图像的每一个像素,其颜色取其周边像素的平均值。不同的模糊算法,对周边的定义不一样,平均的算法也不一样。 比如之前写#过的一篇文章,webgl实现径向模糊,就是模糊算法中的一种。
均值模糊
在理解高斯模糊之前,我们先理解比较容易的均值模糊。所谓均值模糊
其原理就是取像素点周围(上下左右)像素的平均值(其中也会包括自身)。如下图所示:
可以看出,对于某个像素点,当搜索半径为1的时候,影响其颜色值的像素是9个像素(包括自己和周边的8个像素)。假设每个像素对于中心像素的影响都是一样的,那么每个像素的影响度就是1/9。如下图所示:
上面这个3*3的影响度的数字矩阵,通常称之为卷积核。
那么最终中心点的值的求和如下图所示:
最终的值是:
(8 * 1 + 1 * 2 / (8 + 1) ) = 10/9
当计算像素的颜色时候,对于像素的RGB每一个通道都进行的上述平均计算即可。
上面的计算过程就是一种卷积滤镜。所谓卷积滤镜,通俗来说,就是一种组合一组数值的算法。
如果搜索半径变成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功能。
- 对纹理对象进行施加高斯模糊算法,得到最终的高斯模糊的纹理对象。
上面第二部,施加高斯模糊算法,一般又会分成两步:
- 先施加垂直方向的高斯模糊算法;
- 在垂直模糊的基础上进行水平方向的高斯模糊算法。
当然,也可以先水平后垂直,结果是一样的。 分两步高斯模糊算法和一步进行两个方向的高斯模糊算法的结果基本是一致的,但是却可以提高算法的效率。 有人可能说,多模糊了一步,为啥还提高了效率。 这么来说吧,如果是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关联的纹理对象上面。
然后是第二步,执行高斯模糊算法进行
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 代码
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彪叔” 可以及时收到更多有价值的文章。
webgl智慧楼宇发光效果算法系列之高斯模糊的更多相关文章
- 基于 HTML5 WebGL 的智慧楼宇可视化系统
前言 可视化的智慧楼宇在 21 世纪是有急迫需求的,中国被世界称为"基建狂魔",全球高层建筑数量位居首位,所以对于楼宇的监控是必不可少.智慧楼宇可视化系统更多突出的是管理方面的功能 ...
- 基于 HTML5 WebGL 的智慧楼宇三维可视化监控
前言 可视化的智慧楼宇在 21 世纪是有急迫需求的,中国被世界称为"基建狂魔",全球高层建筑数量位居首位,所以对于楼宇的监控是必不可少.智慧楼宇可视化系统更多突出的是管理方面的功能 ...
- 使用three.js(webgl)搭建智慧楼宇、设备检测、数字孪生——第十三课
老子云:有道无术,术尚可求,有术无道,止于术. 咱开篇引用老子的话术,也没其它意思,只是最近学习中忽有感悟,索性就写了上来. 这句话用现代辩证思维理解,这里的"道" 大抵是指方法论 ...
- 基于 HTML5 WebGL 的 智慧楼宇能源监控系统
前言 21世纪,在能源危机和全球气候变暖的压力下,太阳能等可再生能源越来越受到关注,其中光伏建筑一体化逐渐成为绿色发展方式和生活方式,加强节能降耗,支持低碳产业和新能源.可再生能源发展,也已经成为国家 ...
- 【C#实现漫画算法系列】-判断 2 的乘方
微信上关注了算法爱好者这个公众号,有一个漫画算法系列的文章生动形象,感觉特别好,给大家推荐一下(没收过广告费哦),原文链接:漫画算法系列.也看到了许多同学用不同的语言来实现算法,作为一枚C#资深爱好的 ...
- JAVA算法系列 冒泡排序
java算法系列之排序 手写冒泡 冒泡算是最基础的一个排序算法,简单的可以理解为,每一趟都拿i与i+1进行比较,两个for循环,时间复杂度为 O(n^2),同时本例与选择排序进行了比较,选择排序又叫直 ...
- JAVA算法系列 快速排序
java算法系列之排序 手写快排 首先说一下什么是快排,比冒泡效率要高,快排的基本思路是首先找到一个基准元素,比如数组中最左边的那个位置,作为基准元素key,之后在最左边和最右边设立两个哨兵,i 和 ...
- javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例
栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...
- 三白话经典算法系列 Shell排序实现
山是包插入的精髓排序排序,这种方法,也被称为窄增量排序.因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元 ...
随机推荐
- NB-IoT的同步信号解析
NB-IoT的小区搜索和LTE的小区搜索是类似的,每个UE都是通过对同步信号的检测,来实现与小区时间和频率上的同步,以此来获取小区的ID.NB-IoT的同步信号包括NPSS和NSSS. NPSS用于完 ...
- 关于Linux操作系统的文件管理与常用命令
1.显示文件内容命令:cat more less head tail cat命令 : cat命令连接文件并打印到标准输出设备上,cat经常用来显示文件的内容,类似于下的 ...
- day81:luffy:课程分类页面&课程信息页面&指定分类显示课程信息&分页显示课程信息
目录 1.构建课程前端初始页面 2.course后端的准备工作 3.后端实现课程分类列表接口 4.前端发送请求-获取课程分类信息 5.后端实现课程列表信息的接口 6.前端显示列表课程信息 7.按照指定 ...
- Parameter 'name' implicitly has an 'any' type.
出现在vue3版本 找到tsconfig.json文件 增加"noImplicitAny":flase,或把"strict":true改成"stric ...
- Arduino IDE搭建ESP8266开发环境,文件下载过慢解决方法 | ESP-01制作WiFi开关教程,改造宿舍灯
1. Arduino IDE配置ESP8266环境 参考:https://www.jianshu.com/p/cb0274d612b5 首先从 Arduino 官网 下载最新版本的 Arduino I ...
- ArrayList使用及原理
之前面试时,经常被问到ArrayList的原理,今天整理了一些ArrayList的使用原理和必问的知识点. ArrayList的继承关系 定义一个ArrayList的方法 ArrayList的三个构造 ...
- leetcode6:binary-tree-postorder-traversal
题目描述 求给定的二叉树的后序遍历. 例如: 给定的二叉树为{1,#,2,3}, 1↵ ↵ 2↵ /↵ 3↵ 返回[3,2,1]. 备注:用递归来解这道题太没有新意了,可以给出迭代的解法么? Give ...
- 云计算之路-出海记:命令行下的 AWS
俗话说"三百六十行,行行出状元",自从有了电脑之后,三百六十行又多了一行 -- 命令行.GUI 的诞生开创了繁荣的 PC "窗口"(windows)时代,互联网 ...
- linux中配置yum文件
yum简介:yum的宗旨是自动化地升级,安装/移除rpm包,收集rpm包的相关信息,检查依赖性并自动提示用户解决. yum的关键之处是要有可靠的repository,顾名思义,这是软件的仓库,它可以是 ...
- layui table表格详解
上次做table有些东西 忘记了 这次当作来个分析总结一下 跟大家共同学习 闲话不多说 直接上例子 代码: <form id="form1" runat="s ...