前言
咳咳,上篇文章《为什么选择 TypeScript ?》得到了许多朋友的认可,让我动力满满,以后要加油写出更多好文章分享给大家鸭!
客套话就不再多说了哈哈,今天给大家带来的是高斯模糊在 Shader 中的实现!
这里预告一下,Shader 入门系列文章已经在积极筹划中(文件夹已经建好了),感兴趣的小伙伴关注一下啦~

预览
模糊前

模糊后

深度模糊后

正文
高斯模糊
在我们开始讨论代码之前,我们要先稍微了解以下几点…
下面的讲解比较笼统,水平不够,请见谅!
高斯模糊是什么?
高斯模糊(Gaussian Blur),也叫高斯平滑,是一种生活中比较常见的图像处理效果。
经过高斯模糊处理的图像看起来就像是在一块毛玻璃后面,也就是俗称的“毛玻璃效果”。
高斯模糊也常用于处理噪点过高的图像,使图像看起来更平滑。

实现原理是什么?
从数学的角度来看,高斯模糊的处理过程就是图像与其正态分布做卷积。
- 正态分布
正态分布(Normal distribution)是一种概率分布,主要特征为集中性 、对称性和均匀变动性等。
因正态分布又称高斯分布(Gaussian distribution),所以这种技术就叫做高斯模糊。
我们可以计算当前像素一定范围内的像素的权重,越靠近当前像素权重越大,形成一个符合正态分布的权重矩阵。

- 卷积
卷积(Convolution)是一种积分变换的数学运算方法。
利用卷积算法,我们可以将当前像素的颜色与周围像素的颜色按比例进行融合,得到一个相对均匀的颜色。

- 卷积核
其中还涉及到一个名为 卷积核(Convolution kernel)的概念,卷积核一般为矩阵,我们可以将它想象成卷积过程中使用的模板,模板中包含了当前像素周围每个像素颜色的权重。
下图中间的那部分就是卷积核

稍微总结
用大白话来解释高斯模糊,就是采集当前像素一定范围内的颜色,将采集到的颜色按比例进行合成(越靠近当前像素的颜色比例越高,也就是正态分布的体现),得到一个比较均匀的颜色。
将图像中的每个像素都按照上面的流程进行处理,最后就可以得到更为平滑(模糊)的图像。
当然采集的范围越大,得到的图像就会越模糊。

代码实现
下面我将在 Cocos Creator 2.3.3 中实现一个高斯模糊的 Shader,除了前面部分属性定义,核心的逻辑是通用的。
Shader 文件已添加至 Eazax-CCC 项目,这里是 传送门
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
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; }; 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 属性,用于输入作用节点的尺寸。
1 2
| properties: size: { value: [500.0, 500.0], editor: { tooltip: '节点尺寸' } }
|
你可能会好奇(也许不会)为什么要传入节点尺寸,这里稍微说明一下:
在片段着色器阶段的 uv 坐标为纹理坐标(Texture Coordinate),其可用范围是(0.0, 0.0)到(1.0, 1.0),原点为左下角。
例如:屏幕正中间的像素坐标为(0.5, 0.5)。
我们传入尺寸的目的就是便于我们计算顶点的实际位置。
例如:在一个 720 x 1280 的屏幕中,像素与像素之间的水平距离为 1.0 / 720.0,垂直距离为 1.0 / 1280.0。
- 顶点着色器(Vertex Shader)
紧跟其后的是一个平平无奇的顶点着色器,未对顶点作任何特殊处理,直接将顶点坐标以及颜色信息传递给下一个着色器。
这部分代码在上面完整代码里有,我这里就不贴了,因为实在是太平平无奇了…
不如贴个猫包(猫猫表情包)缓和一下气氛吧~

- 片段着色器(Fragment Shader)
重头戏来了!(敲黑板)
- 首先我们拿到了从顶点着色器传递过来的顶点坐标 和颜色信息 ,另外还接收到了 texture 和 size 属性。
1 2 3 4 5 6 7 8 9
| in vec2 v_uv0; in vec4 v_color;
uniform sampler2D texture;
uniform Properties { vec2 size; };
|
- 接着定义了一个常量 RADIUS 来表示模糊采样的半径,半径越大,采样的颜色越多,图像也就越模糊。
在 GLSL 中循环的次数必须为常量,因为循环语句会被展开为原生 GPU 指令,所以必须确定循环展开次数,Shader 编译器才能正确地生成 GPU 指令。
1
| const float RADIUS = 20.0
|
然后定义了一个函数 getBlurColor 来获取模糊后的颜色,该函数接收一个顶点坐标作为参数,经卷积加权平均计算后返回最终颜色。(详细过程请看注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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; }
|
- 然后是着色器的主函数,在获取到模糊的颜色之后,将颜色透明度还原为输入的透明度,最后将舞台交还给渲染管线。
1 2 3 4 5
| void main () { vec4 color = getBlurColor(v_uv0) color.a = v_color.a gl_FragColor = color }
|
传送门
高斯模糊 Shader 文件
微信推文版本
个人博客:菜鸟小栈
开源主页:陈皮皮
Eazax Cocos 游戏开发工具包
更多分享
多平台通用的屏幕分辨率适配方案
围绕物体旋转的方案以及现成的组件
一个全能的挖孔 Shader
一个开源的自动代码混淆插件
微信小游戏接入好友排行榜(开放数据域)
为什么选择使用 TypeScript ?
公众号
菜鸟小栈
我是陈皮皮,这是我的个人公众号,专注但不仅限于游戏开发、前端和后端技术记录与分享。
每一篇原创都非常用心,你的关注就是我原创的动力!
Input and output.
