webgl智慧樓宇發(fā)光效果算法系列之高斯模糊

如果使用過PS之類的圖像處理軟件娱局,相信對于模糊濾鏡不會陌生,圖像處理軟件提供了眾多的模糊算法泪漂。高斯模糊是其中的一種廊营。

在我們的智慧樓宇的項(xiàng)目中,要求對樓宇實(shí)現(xiàn)樓宇發(fā)光的效果萝勤。 比如如下圖所示的簡單樓宇效果:

building.gif

樓宇發(fā)光效果需要用的算法之一就是高斯模糊露筒。

高斯模糊簡介

高斯模糊算法是計算機(jī)圖形學(xué)領(lǐng)域中一種使用廣泛的技術(shù), 是一種圖像空間效果,用于對圖像進(jìn)行模糊處理敌卓,創(chuàng)建原始圖像的柔和模糊版本慎式。
使用高斯模糊的效果,結(jié)合一些其他的算法趟径,還可以產(chǎn)生發(fā)光瘪吏,光暈,景深蜗巧,熱霧和模糊玻璃效果掌眠。

高斯模糊的原理說明

圖像模糊的原理,簡單而言幕屹,就是針對圖像的每一個像素蓝丙,其顏色取其周邊像素的平均值。不同的模糊算法望拖,對周邊的定義不一樣渺尘,平均的算法也不一樣。 比如之前寫#過的一篇文章说敏,webgl實(shí)現(xiàn)徑向模糊,就是模糊算法中的一種鸥跟。

均值模糊

在理解高斯模糊之前,我們先理解比較容易的均值模糊像云。所謂均值模糊
其原理就是取像素點(diǎn)周圍(上下左右)像素的平均值(其中也會包括自身)锌雀。如下圖所示:


image.png

可以看出蚂夕,對于某個像素點(diǎn)迅诬,當(dāng)搜索半徑為1的時候,影響其顏色值的像素是9個像素(包括自己和周邊的8個像素)婿牍。假設(shè)每個像素對于中心像素的影響都是一樣的侈贷,那么每個像素的影響度就是1/9。如下圖所示:


卷積核

上面這個3*3的影響度的數(shù)字矩陣,通常稱之為卷積核俏蛮。

那么最終中心點(diǎn)的值的求和如下圖所示:


image.png

最終的值是:

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

當(dāng)計算像素的顏色時候撑蚌,對于像素的RGB每一個通道都進(jìn)行的上述平均計算即可。

上面的計算過程就是一種卷積濾鏡搏屑。所謂卷積濾鏡争涌,通俗來說,就是一種組合一組數(shù)值的算法辣恋。

如果搜索半徑變成2亮垫,則會變成25個像素的平均,搜索半徑越大伟骨,就會越模糊饮潦。像素個數(shù)與搜索半徑的關(guān)系如下:

(1 + r * 2)的平方 // r = 1,結(jié)果為9携狭,r=2继蜡,結(jié)果為25,r=3 結(jié)果為49.

通常 NxN會被稱之卷積核的大小逛腿。比如3x3稀并,5x5。

在均值模糊的計算中单默,參與的每個像素稻轨,對中心像素的貢獻(xiàn)值都是一樣的,這是均值模糊的特點(diǎn)雕凹。也就是殴俱,每個像素的權(quán)重都是一樣的。

正態(tài)分布

如果使用簡單平均枚抵,顯然不是很合理线欲,因?yàn)閳D像都是連續(xù)的,越靠近的點(diǎn)關(guān)系越密切汽摹,越遠(yuǎn)離的點(diǎn)關(guān)系越疏遠(yuǎn)李丰。因此,加權(quán)平均更合理逼泣,距離越近的點(diǎn)權(quán)重越大趴泌,距離越遠(yuǎn)的點(diǎn)權(quán)重越小。

正態(tài)分布整好滿足上述的的分布需求拉庶,如下圖所示:


正態(tài)分布

可以看出嗜憔,正態(tài)分布是一種鐘形曲線,越接近中心氏仗,取值越大吉捶,越遠(yuǎn)離中心,取值越小。

在計算平均值的時候呐舔,我們只需要將"中心點(diǎn)"作為原點(diǎn)币励,其他點(diǎn)按照其在正態(tài)曲線上的位置,分配權(quán)重珊拼,就可以得到一個加權(quán)平均值食呻。

高斯函數(shù)

高斯函數(shù)是描述正態(tài)分布的數(shù)學(xué)公式。公式如下:

一維高斯函數(shù)

其中澎现,μ是x的均值搁进,可以理解為正態(tài)分布的中心位置,σ是x的方差昔头。因?yàn)橛嬎闫骄档臅r候饼问,中心點(diǎn)就是原點(diǎn),所以μ等于0揭斧。


一維高斯函數(shù)

如果是二維莱革,則有:


二維高斯函數(shù)

可以看出二維高斯函數(shù)中,x和y相對是獨(dú)立的。也就是說:

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

這個特性的好處是讹开,可以把二維的高斯函數(shù)盅视,拆解成兩個獨(dú)立的一維高斯函數(shù)∧只鳎可以提高效率。實(shí)際上,高斯模糊運(yùn)用的一維高斯函數(shù),而不是使用二維。

高斯模糊

高斯模糊的原理和前面介紹的均值模糊的原理基本上一樣,只是均值模糊在計算平均值的時候,周邊像素的權(quán)重都是一樣的芝发。而高斯模糊下独悴,周邊像素的權(quán)重值卻使用高斯函數(shù)進(jìn)行計算坟奥,這也是高斯模糊的之所以被稱為高斯模糊的原因孝偎。

比如當(dāng)σ取值為則模糊半徑為1的權(quán)重矩陣如下:


高斯權(quán)重矩陣

這9個點(diǎn)的權(quán)重總和等于0.4787147迅涮,如果只計算這9個點(diǎn)的加權(quán)平均据悔,還必須讓它們的權(quán)重之和等于1菠隆,因此上面9個值還要分別除以0.4787147兵琳,得到最終的權(quán)重矩陣狂秘。

高斯權(quán)重矩陣

渲染流程

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

  1. 首先躯肌,按照正常流程把場景或者圖像渲染到一個紋理對象上面者春,需要使用FrameBuffer功能。
  2. 對紋理對象進(jìn)行施加高斯模糊算法清女,得到最終的高斯模糊的紋理對象钱烟。

上面第二部,施加高斯模糊算法嫡丙,一般又會分成兩步:

  1. 先施加垂直方向的高斯模糊算法拴袭;
  2. 在垂直模糊的基礎(chǔ)上進(jìn)行水平方向的高斯模糊算法。
    當(dāng)然曙博,也可以先水平后垂直,結(jié)果是一樣的父泳。 分兩步高斯模糊算法和一步進(jìn)行兩個方向的高斯模糊算法的結(jié)果基本是一致的泰佳,但是卻可以提高算法的效率。 有人可能說尘吗,多模糊了一步逝她,為啥還提高了效率。 這么來說吧睬捶,如果是3x3大小的高斯模糊:
    分兩步要獲取的像素數(shù)量是 3 + 3 = 6黔宛; 而一步卻是3 x 3 = 9。 如果是5x5大小的高斯模糊:分兩步要獲取的像素數(shù)量是 5+5=10擒贸; 而一步卻是5 x 5=25 臀晃。顯然可以算法執(zhí)行效率。

渲染流程代碼

對于第一步介劫,首先是渲染到紋理對象徽惋,這輸入渲染到紋理的知識,此處不再贅述座韵,大致大代碼結(jié)構(gòu)如下:
···
frameBuffer.bind();
renderScene();
frameBuffer.unbind();
···

把renderScene放到frameBuffer.bind之后险绘,會把場景繪制到frameBuffer關(guān)聯(lián)的紋理對象上面。

然后是第二步誉碴,執(zhí)行高斯模糊算法進(jìn)行

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對象宦棺,作為輸入?yún)?shù)傳遞過來。 然后開始執(zhí)行垂直方向的高斯模糊算法,

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();

在之后執(zhí)行水平方向的模糊算法:

 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 代碼分成兩部分黔帕,一個頂點(diǎn)著色器代碼:

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;
}
`

最終渲染的效果如下:


image.png

應(yīng)用案例

目前用到的主要是發(fā)光樓宇的效果代咸。 下面是幾個案例圖,分享給大家看看:


案例1
案例2

參考文檔

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

如果對可視化感興趣成黄,可以和我交流呐芥,微信541002349. 另外關(guān)注公眾號“ITMan彪叔” 可以及時收到更多有價值的文章逻杖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市思瘟,隨后出現(xiàn)的幾起案子荸百,更是在濱河造成了極大的恐慌,老刑警劉巖潮太,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件管搪,死亡現(xiàn)場離奇詭異虾攻,居然都是意外死亡铡买,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門霎箍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奇钞,“玉大人,你說我怎么就攤上這事漂坏【鞍#” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵顶别,是天一觀的道長谷徙。 經(jīng)常有香客問我,道長驯绎,這世上最難降的妖魔是什么完慧? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮剩失,結(jié)果婚禮上屈尼,老公的妹妹穿的比我還像新娘。我一直安慰自己拴孤,他們只是感情好脾歧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著演熟,像睡著了一般鞭执。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芒粹,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天蚕冬,我揣著相機(jī)與錄音,去河邊找鬼是辕。 笑死囤热,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的获三。 我是一名探鬼主播旁蔼,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼锨苏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棺聊?” 一聲冷哼從身側(cè)響起伞租,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎限佩,沒想到半個月后葵诈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祟同,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年作喘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晕城。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡泞坦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出砖顷,到底是詐尸還是另有隱情贰锁,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布滤蝠,位于F島的核電站豌熄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏物咳。R本人自食惡果不足惜锣险,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望所森。 院中可真熱鬧囱持,春花似錦、人聲如沸焕济。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晴弃。三九已至掩幢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間上鞠,已是汗流浹背际邻。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芍阎,地道東北人世曾。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像谴咸,于是被迫代替她去往敵國和親轮听。 傳聞我的和親對象是個殘疾皇子骗露,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 注:以下文章思想來源于阮一峰老師高斯模糊的算法 最近項(xiàng)目中需要模糊圖片,在網(wǎng)上查了很久血巍,終于將功能實(shí)現(xiàn)萧锉。但是對于其...
    Wolfog閱讀 1,432評論 0 0
  • 通常,圖像處理軟件會提供"模糊"(blur)濾鏡述寡,使圖片產(chǎn)生模糊的效果柿隙。 "模糊"的算法有很多種,其中有一種叫做"...
    一川煙草i蓑衣閱讀 517評論 0 1
  • 高斯模糊算法是一種把高斯原理運(yùn)用到模糊領(lǐng)域的一種算法鲫凶。我們都知道禀崖,圖像中微分和查分操作能夠消除掉高頻的信息,從而留...
    雨幻逐光閱讀 2,520評論 0 0
  • 最新剛好遇到個需求是要求做高斯模糊的掀序,雖然現(xiàn)有已經(jīng)有一些框架可以提供調(diào)用帆焕,但關(guān)鍵還是要理解原理才行惭婿,思考的過程才是...
    Hohohong閱讀 13,555評論 1 37
  • 卷積在信號處理領(lǐng)域有極其廣泛的應(yīng)用不恭,也有嚴(yán)格的物理和數(shù)學(xué)定義。OpenCV中對圖像進(jìn)行模糊操作财饥,其背后的原理就是卷...
    兩行哥閱讀 1,915評論 3 15