高斯模糊 Shader

前言

咳咳,上篇文章《為什么選擇 TypeScript 娄琉?》得到了許多朋友的認(rèn)可次乓,讓我動(dòng)力滿滿,以后要加油寫出更多好文章分享給大家鴨孽水!

客套話就不再多說了哈哈票腰,今天給大家?guī)淼氖?strong>高斯模糊在 Shader 中的實(shí)現(xiàn)!

這里預(yù)告一下女气,Shader 入門系列文章已經(jīng)在積極籌劃中(文件夾已經(jīng)建好了)杏慰,感興趣的小伙伴關(guān)注一下啦~


image

預(yù)覽

模糊前

image

模糊后

image

深度模糊后

image

正文

高斯模糊

在我們開始討論代碼之前,我們要先稍微了解以下幾點(diǎn)...

下面的講解比較籠統(tǒng)主卫,水平不夠逃默,請(qǐng)見諒!

高斯模糊是什么簇搅?

高斯模糊(Gaussian Blur)完域,也叫高斯平滑,是一種生活中比較常見的圖像處理效果瘩将。

經(jīng)過高斯模糊處理的圖像看起來就像是在一塊毛玻璃后面吟税,也就是俗稱的“毛玻璃效果”。

高斯模糊也常用于處理噪點(diǎn)過高的圖像姿现,使圖像看起來更平滑肠仪。

image

實(shí)現(xiàn)原理是什么?

從數(shù)學(xué)的角度來看备典,高斯模糊的處理過程就是圖像與其正態(tài)分布卷積异旧。

- 正態(tài)分布

正態(tài)分布(Normal distribution)是一種概率分布振劳,主要特征為集中性 炭玫、對(duì)稱性均勻變動(dòng)性等裙椭。

因正態(tài)分布又稱高斯分布(Gaussian distribution)捌朴,所以這種技術(shù)就叫做高斯模糊溪烤。

我們可以計(jì)算當(dāng)前像素一定范圍內(nèi)的像素的權(quán)重稀火,越靠近當(dāng)前像素權(quán)重越大坏晦,形成一個(gè)符合正態(tài)分布的權(quán)重矩陣雌澄。

image

- 卷積

卷積(Convolution)是一種積分變換的數(shù)學(xué)運(yùn)算方法倚喂。

利用卷積算法每篷,我們可以將當(dāng)前像素的顏色與周圍像素的顏色按比例進(jìn)行融合,得到一個(gè)相對(duì)均勻的顏色端圈。

image

- 卷積核

其中還涉及到一個(gè)名為 卷積核(Convolution kernel)的概念焦读,卷積核一般為矩陣,我們可以將它想象成卷積過程中使用的模板舱权,模板中包含了當(dāng)前像素周圍每個(gè)像素顏色的權(quán)重矗晃。

下圖中間的那部分就是卷積核

image

稍微總結(jié)

用大白話來解釋高斯模糊,就是采集當(dāng)前像素一定范圍內(nèi)的顏色刑巧,將采集到的顏色按比例進(jìn)行合成(越靠近當(dāng)前像素的顏色比例越高喧兄,也就是正態(tài)分布的體現(xiàn))无畔,得到一個(gè)比較均勻的顏色。

將圖像中的每個(gè)像素都按照上面的流程進(jìn)行處理吠冤,最后就可以得到更為平滑(模糊)的圖像浑彰。

當(dāng)然采集的范圍越大,得到的圖像就會(huì)越模糊拯辙。

image

代碼實(shí)現(xiàn)

下面我將在 Cocos Creator 2.3.3 中實(shí)現(xiàn)一個(gè)高斯模糊的 Shader郭变,除了前面部分屬性定義,核心的邏輯是通用的涯保。

Shader 文件已添加至 Eazax-CCC 項(xiàng)目诉濒,這里是 傳送門

完整代碼

// Eazax-CCC 高斯模糊 1.0.0.20200523

CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        size: { value: [500.0, 500.0], editor: { tooltip: '節(jié)點(diǎn)尺寸' } }
}%


CCProgram vs %{
  precision highp float;

  #include <cc-global>

  in vec3 a_position;
  in vec2 a_uv0;
  in vec4 a_color;
 
  out vec2 v_uv0;
  out vec4 v_color;
 
  void main () {
    gl_Position = cc_matViewProj * vec4(a_position, 1);
    v_uv0 = a_uv0;
    v_color = a_color;
  }
}%


CCProgram fs %{
  precision highp float;

  in vec2 v_uv0;
  in vec4 v_color;

  uniform sampler2D texture;

  uniform Properties {
    vec2 size;
  };
  
  // 模糊半徑
  // for 循環(huán)的次數(shù)必須為常量
  const float RADIUS = 20.0;

  // 獲取模糊顏色
  vec4 getBlurColor (vec2 pos) {
    vec4 color = vec4(0); // 初始顏色
    float sum = 0.0; // 總權(quán)重
    // 卷積過程
    for (float r = -RADIUS; r <= RADIUS; r++) { // 水平方向
      for (float c = -RADIUS; c <= RADIUS; c++) { // 垂直方向
        vec2 target = pos + vec2(r / size.x, c / size.y); // 目標(biāo)像素位置
        float weight = (RADIUS - abs(r)) * (RADIUS - abs(c)); // 計(jì)算權(quán)重
        color += texture2D(texture, target) * weight; // 累加顏色
        sum += weight; // 累加權(quán)重
      }
    }
    color /= sum; // 求出平均值
    return color;
  }
 
  void main () {
    vec4 color = getBlurColor(v_uv0); // 獲取模糊后的顏色
    color.a = v_color.a; // 還原透明度
    gl_FragColor = color;
  }
}%

代碼分析

- CCEffect

首先頭部是平平無奇的 YAML 格式的屬性定義代碼塊。唯一特別的地方就是多了個(gè) size 屬性夕春,用于輸入作用節(jié)點(diǎn)的尺寸未荒。

properties:
  size: { value: [500.0, 500.0], editor: { tooltip: '節(jié)點(diǎn)尺寸' } }

你可能會(huì)好奇(也許不會(huì))為什么要傳入節(jié)點(diǎn)尺寸,這里稍微說明一下:

  1. 在片段著色器階段的 uv 坐標(biāo)為紋理坐標(biāo)(Texture Coordinate)及志,其可用范圍是(0.0, 0.0)到(1.0, 1.0)片排,原點(diǎn)為左下角。

    例如:屏幕正中間的像素坐標(biāo)為(0.5, 0.5)速侈。

  2. 我們傳入尺寸的目的就是便于我們計(jì)算頂點(diǎn)的實(shí)際位置率寡。

    例如:在一個(gè) 720 x 1280 的屏幕中,像素與像素之間的水平距離為 1.0 / 720.0倚搬,垂直距離為 1.0 / 1280.0冶共。

- 頂點(diǎn)著色器(Vertex Shader)

緊跟其后的是一個(gè)平平無奇的頂點(diǎn)著色器,未對(duì)頂點(diǎn)作任何特殊處理每界,直接將頂點(diǎn)坐標(biāo)以及顏色信息傳遞給下一個(gè)著色器捅僵。

這部分代碼在上面完整代碼里有,我這里就不貼了盆犁,因?yàn)閷?shí)在是太平平無奇了...

不如貼個(gè)貓包(貓貓表情包)緩和一下氣氛吧~

image

- 片段著色器(Fragment Shader)

重頭戲來了C馈(敲黑板)

  1. 首先我們拿到了從頂點(diǎn)著色器傳遞過來的頂點(diǎn)坐標(biāo)顏色信息 篡九,另外還接收到了 texturesize 屬性谐岁。
in vec2 v_uv0;
in vec4 v_color;

uniform sampler2D texture;

// 接收傳入的 size 屬性
uniform Properties {
  vec2 size;
};
  1. 接著定義了一個(gè)常量 RADIUS 來表示模糊采樣的半徑,半徑越大榛臼,采樣的顏色越多伊佃,圖像也就越模糊

在 GLSL 中循環(huán)的次數(shù)必須為常量沛善,因?yàn)檠h(huán)語句會(huì)被展開為原生 GPU 指令航揉,所以必須確定循環(huán)展開次數(shù),Shader 編譯器才能正確地生成 GPU 指令金刁。

const float RADIUS = 20.0;

然后定義了一個(gè)函數(shù) getBlurColor 來獲取模糊后的顏色帅涂,該函數(shù)接收一個(gè)頂點(diǎn)坐標(biāo)作為參數(shù)议薪,經(jīng)卷積加權(quán)平均計(jì)算后返回最終顏色。(詳細(xì)過程請(qǐng)看注釋)

// 獲取模糊顏色
vec4 getBlurColor (vec2 pos) {
  vec4 color = vec4(0); // 初始顏色
  float sum = 0.0; // 總權(quán)重
  // 卷積過程
  for (float r = -RADIUS; r <= RADIUS; r++) { // 水平方向
    for (float c = -RADIUS; c <= RADIUS; c++) { // 垂直方向
      vec2 target = pos + vec2(r / size.x, c / size.y); // 目標(biāo)像素位置
      float weight = (RADIUS - abs(r)) * (RADIUS - abs(c)); // 計(jì)算權(quán)重
      color += texture2D(texture, target) * weight; // 累加顏色
      sum += weight; // 累加權(quán)重
    }
  }
  color /= sum; // 求出一個(gè)平均值
  return color;
}
  1. 然后是著色器的主函數(shù)媳友,在獲取到模糊的顏色之后斯议,將顏色透明度還原為輸入的透明度,最后將舞臺(tái)交還給渲染管線醇锚。
void main () {
  vec4 color = getBlurColor(v_uv0); // 獲取模糊后的顏色
  color.a = v_color.a; // 還原透明度
  gl_FragColor = color;
}

傳送門

高斯模糊 Shader 文件

微信推文版本

個(gè)人博客:菜鳥小棧

開源主頁:陳皮皮

Eazax-CCC 游戲開發(fā)腳手架


更多分享

多平臺(tái)通用的屏幕分辨率適配方案

圍繞物體旋轉(zhuǎn)的方案以及現(xiàn)成的組件

一個(gè)全能的挖孔 Shader

一個(gè)開源的自動(dòng)代碼混淆插件

微信小游戲接入好友排行榜(開放數(shù)據(jù)域)

為什么選擇使用 TypeScript 哼御?


公眾號(hào)

菜鳥小棧

我是陳皮皮,這是我的個(gè)人公眾號(hào)焊唬,專注但不僅限于游戲開發(fā)恋昼、前端和后端技術(shù)記錄與分享。

每一篇原創(chuàng)都非常用心赶促,你的關(guān)注就是我原創(chuàng)的動(dòng)力液肌!

Input and output.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸥滨,隨后出現(xiàn)的幾起案子矩屁,更是在濱河造成了極大的恐慌,老刑警劉巖爵赵,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吝秕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡空幻,警方通過查閱死者的電腦和手機(jī)烁峭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秕铛,“玉大人约郁,你說我怎么就攤上這事〉剑” “怎么了鬓梅?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谨湘。 經(jīng)常有香客問我绽快,道長(zhǎng),這世上最難降的妖魔是什么紧阔? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任坊罢,我火速辦了婚禮,結(jié)果婚禮上擅耽,老公的妹妹穿的比我還像新娘活孩。我一直安慰自己,他們只是感情好乖仇,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布憾儒。 她就那樣靜靜地躺著询兴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪起趾。 梳的紋絲不亂的頭發(fā)上蕉朵,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音阳掐,去河邊找鬼始衅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缭保,可吹牛的內(nèi)容都是我干的汛闸。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼艺骂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼诸老!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钳恕,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤别伏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后忧额,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厘肮,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年睦番,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了类茂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡托嚣,死狀恐怖巩检,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情示启,我是刑警寧澤兢哭,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站夫嗓,受9級(jí)特大地震影響迟螺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啤月,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一煮仇、第九天 我趴在偏房一處隱蔽的房頂上張望劳跃。 院中可真熱鬧谎仲,春花似錦、人聲如沸刨仑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辙诞,卻和暖如春辙售,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背飞涂。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工旦部, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人较店。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓士八,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梁呈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子婚度,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350