本文由“壹伴編輯器”提供技術支
前言
咳咳,上篇文章《爲什麼選擇 TypeScript ?》得到了許多朋友的認可,讓我動力滿滿,以後要加油寫出更多好文章分享給大家鴨!
客套話就不再多說了哈哈,今天給大家帶來的是高斯模糊在 Shader 中的實現!
> 這裏預告一下,Shader 入門系列文章《一起學 Shader》已經在積極籌劃中(文件夾已經建好了),感興趣的小夥伴關注一下啦~
本文由“壹伴編輯器”提供技術支
預覽
模糊前
模糊後
深度模糊後
本文由“壹伴編輯器”提供技術支
正文
高斯模糊
在我們開始討論代碼之前,我們要先稍微瞭解以下幾點...
> 下面的講解比較籠統,水平不夠,請見諒!
高斯模糊是什麼?
高斯模糊(Gaussian Blur),也叫高斯平滑,是一種生活中比較常見的圖像處理效果。
經過高斯模糊處理的圖像看起來就像是在一塊毛玻璃後面,也就是俗稱的“毛玻璃效果”。
高斯模糊也常用於處理噪點過高的圖像,使圖像看起來更平滑。
(神祕的微笑)
—▼—
實現原理是什麼?
從數學的角度來看,高斯模糊的處理過程就是圖像與其正態分佈做卷積。
- 正態分佈
正態分佈(Normal distribution)是一種概率分佈,主要特徵爲集中性、對稱性和均勻變動性等。
因正態分佈又稱高斯分佈(Gaussian distribution),所以這種技術就叫做高斯模糊。
我們可以計算當前像素一定範圍內的像素的權重,越靠近當前像素權重越大,形成一個符合正態分佈的權重矩陣。
(圖片來源於網絡,侵刪)
- 卷積
卷積(Convolution)是一種積分變換的數學運算方法。
利用卷積算法,我們可以將當前像素的顏色與周圍像素的顏色按比例進行融合,得到一個相對均勻的顏色。
(圖片來源於網絡,侵刪)
- 卷積核
其中還涉及到一個名爲卷積核(Convolution kernel)的概念,卷積核一般爲矩陣,我們可以將它想象成卷積過程中使用的模板,模板中包含了當前像素周圍每個像素顏色的權重。
> 下圖中間的那部分就是卷積核
(圖片來源於網絡,侵刪)
—▼—
稍微總結
用大白話來解釋高斯模糊,就是採集當前像素一定範圍內的顏色,將採集到的顏色按比例進行合成(越靠近當前像素的顏色比例越高,也就是正態分佈的體現),得到一個比較均勻的顏色。
將圖像中的每個像素都按照上面的流程進行處理,最後就可以得到更爲平滑(模糊)的圖像。
當然採集的範圍越大,得到的圖像就會越模糊。
代碼實現
下面我將在 Cocos Creator 2.3.3 中實現一個高斯模糊的 Shader,除了前面部分屬性定義,核心的邏輯是通用的。
> Shader 文件已添加至 Eazax-CCC 項目,點擊文章底部“閱讀原文”即可獲取
完整代碼
// 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: '節點尺寸' } }
}%
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 循環的次數必須爲常量
const float RADIUS = 20.0;
// 獲取模糊顏色
vec4 getBlurColor (vec2 pos) {
vec4 color = vec4(0); // 初始顏色
float sum = 0.0; // 總權重
// 卷積過程
for (float r = -RADIUS; r <= RADIUS; r++) { // 水平方向
for (float c = -RADIUS; c <= RADIUS; c++) { // 垂直方向
vec2 target = pos + vec2(r / size.x, c / size.y); // 目標像素位置
float weight = (RADIUS - abs(r)) * (RADIUS - abs(c)); // 計算權重
color += texture2D(texture, target) * weight; // 累加顏色
sum += weight; // 累加權重
}
}
color /= sum; // 求出平均值
return color;
}
void main () {
vec4 color = getBlurColor(v_uv0); // 獲取模糊後的顏色
color.a = v_color.a; // 還原透明度
gl_FragColor = color;
}
}%
—▼—
代碼分析
- CCEffect
首先頭部是平平無奇的 YAML 格式的屬性定義代碼塊。唯一特別的地方就是多了個 size 屬性,用於輸入作用節點的尺寸。
properties:
size: { value: [500.0, 500.0], editor: { tooltip: '節點尺寸' } }
你可能會好奇(也許不會)爲什麼要傳入節點尺寸,這裏稍微說明一下:
1. 在片段着色器階段的頂點座標用視口座標(Viewport Coordinates)表示,視口座標是標準化(Normalize)後的屏幕座標(Screen Coordinates),其可用範圍是(0.0, 0.0)到(1.0, 1.0),原點爲左下角。
例如:屏幕正中間的視口座標應爲(0.5, 0.5)。
2. 我們傳入尺寸的目的就是便於我們計算頂點的實際位置。
例如:在一個 720 x 1280 的屏幕中,像素與像素之間的水平距離爲 1.0 / 720.0,垂直距離爲 1.0 / 1280.0。
- 頂點着色器(Vertex Shader)
緊跟其後的是一個平平無奇的頂點着色器,未對頂點作任何特殊處理,直接將頂點座標以及顏色信息傳遞給下一個着色器。
> 這部分代碼在上面完整代碼裏有,我這裏就不貼了,因爲實在是太平平無奇了...
> 不如貼個貓包(貓貓表情包)緩和一下氣氛吧~
- 片段着色器(Fragment Shader)
> 重頭戲來了!(敲黑板)
1. 首先我們拿到了從頂點着色器傳遞過來的頂點座標和顏色信息,另外還接收到了 texture 和 size 屬性。
in vec2 v_uv0;
in vec4 v_color;
uniform sampler2D texture;
// 接收傳入的 size 屬性
uniform Properties {
vec2 size;
};
2. 接着定義了一個常量 RADIUS 來表示模糊採樣的半徑,半徑越大,採樣的顏色越多,圖像也就越模糊。
> 在 GLSL 中循環的次數必須爲常量,因爲循環語句會被展開爲原生 GPU 指令,所以必須確定循環展開次數,Shader 編譯器才能正確地生成 GPU 指令。
const float RADIUS = 20.0;
然後定義了一個函數 getBlurColor 來獲取模糊後的顏色,該函數接收一個頂點座標作爲參數,經卷積加權平均計算後返回最終顏色。(詳細過程請看註釋)
// 獲取模糊顏色
vec4 getBlurColor (vec2 pos) {
vec4 color = vec4(0); // 初始顏色
float sum = 0.0; // 總權重
// 卷積過程
for (float r = -RADIUS; r <= RADIUS; r++) { // 水平方向
for (float c = -RADIUS; c <= RADIUS; c++) { // 垂直方向
vec2 target = pos + vec2(r / size.x, c / size.y); // 目標像素位置
float weight = (RADIUS - abs(r)) * (RADIUS - abs(c)); // 計算權重
color += texture2D(texture, target) * weight; // 累加顏色
sum += weight; // 累加權重
}
}
color /= sum; // 求出一個平均值
return color;
}
3. 然後是着色器的主函數,在獲取到模糊的顏色之後,將顏色透明度還原爲輸入的透明度,最後將舞臺交還給渲染管線。
void main () {
vec4 color = getBlurColor(v_uv0); // 獲取模糊後的顏色
color.a = v_color.a; // 還原透明度
gl_FragColor = color;
}
本文由“壹伴編輯器”提供技術支
結束語
以上內容皆爲陳皮皮的個人觀點。
文采不佳,如果寫得不好還請各位多多包涵。如果有哪些地方說的不對,還請各位指出,希望與大家共同進步。
接下來我會持續分享自己所學的知識與見解,歡迎各位關注本公衆號。
我們,下次見!
本文由“壹伴編輯器”提供技術支
掃描二維碼
獲取更多精彩
菜鳥小棧
點擊 閱讀原文 獲取 Shader 文件