高斯模糊 Shader

前言

咳咳,上篇文章《为什么选择 TypeScript ?》得到了许多朋友的认可,让我动力满满,以后要加油写出更多好文章分享给大家鸭!

客套话就不再多说了哈哈,今天给大家带来的是高斯模糊Shader 中的实现!

这里预告一下,Shader 入门系列文章已经在积极筹划中(文件夹已经建好了),感兴趣的小伙伴关注一下啦~


image

预览

模糊前

image

模糊后

image

深度模糊后

image

正文

高斯模糊

在我们开始讨论代码之前,我们要先稍微了解以下几点...

下面的讲解比较笼统,水平不够,请见谅!

高斯模糊是什么?

高斯模糊(Gaussian Blur),也叫高斯平滑,是一种生活中比较常见的图像处理效果。

经过高斯模糊处理的图像看起来就像是在一块毛玻璃后面,也就是俗称的“毛玻璃效果”。

高斯模糊也常用于处理噪点过高的图像,使图像看起来更平滑。

image

实现原理是什么?

从数学的角度来看,高斯模糊的处理过程就是图像与其正态分布卷积

- 正态分布

正态分布(Normal distribution)是一种概率分布,主要特征为集中性对称性均匀变动性等。

因正态分布又称高斯分布(Gaussian distribution),所以这种技术就叫做高斯模糊。

我们可以计算当前像素一定范围内的像素的权重,越靠近当前像素权重越大,形成一个符合正态分布的权重矩阵。

image

- 卷积

卷积(Convolution)是一种积分变换的数学运算方法。

利用卷积算法,我们可以将当前像素的颜色与周围像素的颜色按比例进行融合,得到一个相对均匀的颜色。

image

- 卷积核

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

下图中间的那部分就是卷积核

image

稍微总结

用大白话来解释高斯模糊,就是采集当前像素一定范围内的颜色,将采集到的颜色按比例进行合成(越靠近当前像素的颜色比例越高,也就是正态分布的体现),得到一个比较均匀的颜色。

将图像中的每个像素都按照上面的流程进行处理,最后就可以得到更为平滑(模糊)的图像。

当然采集的范围越大,得到的图像就会越模糊。

image

代码实现

下面我将在 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. 在片段着色器阶段的 uv 坐标为纹理坐标(Texture Coordinate),其可用范围是(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)

紧跟其后的是一个平平无奇的顶点着色器,未对顶点作任何特殊处理,直接将顶点坐标以及颜色信息传递给下一个着色器。

这部分代码在上面完整代码里有,我这里就不贴了,因为实在是太平平无奇了...

不如贴个猫包(猫猫表情包)缓和一下气氛吧~

image

- 片段着色器(Fragment Shader)

重头戏来了!(敲黑板)

  1. 首先我们拿到了从顶点着色器传递过来的顶点坐标颜色信息 ,另外还接收到了 texturesize 属性。
in vec2 v_uv0;
in vec4 v_color;

uniform sampler2D texture;

// 接收传入的 size 属性
uniform Properties {
  vec2 size;
};
  1. 接着定义了一个常量 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;
}
  1. 然后是着色器的主函数,在获取到模糊的颜色之后,将颜色透明度还原为输入的透明度,最后将舞台交还给渲染管线。
void main () {
  vec4 color = getBlurColor(v_uv0); // 获取模糊后的颜色
  color.a = v_color.a; // 还原透明度
  gl_FragColor = color;
}

传送门

高斯模糊 Shader 文件

微信推文版本

个人博客:菜鸟小栈

开源主页:陈皮皮

Eazax-CCC 游戏开发脚手架


更多分享

多平台通用的屏幕分辨率适配方案

围绕物体旋转的方案以及现成的组件

一个全能的挖孔 Shader

一个开源的自动代码混淆插件

微信小游戏接入好友排行榜(开放数据域)

为什么选择使用 TypeScript ?


公众号

菜鸟小栈

我是陈皮皮,这是我的个人公众号,专注但不仅限于游戏开发、前端和后端技术记录与分享。

每一篇原创都非常用心,你的关注就是我原创的动力!

Input and output.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352