Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术

💡 本系列文章收录于个人专栏 ShaderMyHead,欢迎订阅。

一、着色器片段(Chunk)

Cocos Creator 支持创建着色器片段文件(.chunk 格式)在跨文件中复用。在资源管理器某文件夹下点击右键,选择「创建 → 着色器片段」即可创建一个 Chunk 文件:

我们创建一个 normal-vert.chunk 文件,用来存放经常使用的顶点着色器代码:

c 复制代码
precision highp float;
#include <cc-global>
#if USE_LOCAL
  #include <builtin/uniforms/cc-local>
#endif
in vec3 a_position;
in vec2 a_texCoord;   
out vec2 uv;

#if USE_LOCAL
  in vec4 a_color; 
  out vec4 v_color; 
#endif


vec4 vert() {
  vec4 pos = vec4(a_position, 1);

  #if USE_LOCAL
    pos = cc_matWorld * pos; 
    v_color = a_color;
  #endif

  pos = cc_matViewProj * pos;
  uv = a_texCoord;
  
  return  pos;
}

之后我们就可以在任意的 Effect 文件中,使用 #include 语句引入这个 Chunk 文件的相对路径来进行复用。

本文将介绍绿幕背景抠除技术(Chromakey)的实现,我们使用一个复用上方顶点着色器代码段的 Effect 文件来进行扩展开发:

c 复制代码
CCEffect %{
  techniques:
  - name: chromakey
    passes:
    - vert: vs:vert
      frag: fs:frag
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
      depthStencilState:      
          depthTest: false  
          depthWrite: false
}%


CCProgram vs %{
  #include "../../resources/chunk/normal-vert.chunk"     // 复用 Chunk
}%

CCProgram fs %{
  precision highp float;
  #include <sprite-texture> 

  in vec2 uv;

  vec4 frag() {
    vec4 color = texture(cc_spriteTexture, uv); 
    
    // 在这里扩展片元着色器的开发

    return color;
  }
}%

💡 更多 Chunk 相关的内容,可查阅官方文档《着色器片段》

二、绿幕背景抠除

绿幕背景抠除技术在影视领域非常常见,泛指一个动画、影视片段通过后期处理,移除和替换其背景色(常规为比较单一的颜色,且不仅限于绿色)的能力。

💡 绿幕背景的颜色,一般会与前景的人和物的颜色尽可能地分开,方便后期做颜色分割的处理。例如拍摄中有演员穿了绿色的服装,则可以选用蓝色的背景作为"绿幕"。

在 Cocos Creator 中,我们可以通过剔除指定色值的顶点像素,来轻松移除一张图甚至一个动画的背景。

假设我们有一个 Animation Clip 动画,是由多张具有绿色渐变背景的 SpriteFrame 构成:

需要关注的点是,图片中的绿色背景并非单一的绿色,且前景的角色 RGB 通道中自然也包含有 G 通道的色值,我们无法只移除固定色值的像素。

因此「确定需移除像素的RGB分量范围」,是该技术的关键。

上方动画的渐变背景,我们可以通过自行取色的方式,来确定其渐变最深和最浅两色的色值:

可以看到其 G 通道的取值范围是 [86, 229],转换为着色器里的取值范围为:

js 复制代码
[86/255, 229/255] ≈ [0.33, 0.91] 

💡 计算机存储的基本单位是字节(1 Byte = 8 bit),而 8 bit 二进制数的取值范围正好是 0~255,因此常规 RGB 各通道的色彩取值范围被限定为 [0, 255],来节省颜色数据的存储空间。

而在着色器领域,是使用浮点范围 [0.0, 1.0] 来表示颜色通道取值范围,因此本案例需要除以 255 来转换为着色器支持的格式。

我们可以在片元着色器中先判断当前像素的 G 分量值是否处于此区间,是的话则使用 GLSL 内置的 discard 指令来抛弃该像素:

c 复制代码
  vec4 frag() {
    vec4 color = texture(cc_spriteTexture, uv); 
    
    // 判断 G 通道分量值
    if (color.g >= 0.33 && color.g <= 0.91) {
      discard;    // 使用 GLSL 内置的 discard 指令抛弃该像素,并退出当前片元着色器的执行
    }

    return color;
  }

此时你会发现绿色渐变的背景被移除了,但角色也丢失了大部分像素:

这是因为很多较亮的颜色,其 RGB 的通道值都是比较大的,仅判断 G 通道会导致这些像素被命中和被剔除:

因此需要进一步判断 R 和 B 两个通道的取值范围,这两个通道分量取值范围是:

js 复制代码
// R 通道取值范围
[15/255, 18/255] ≈ [0.06, 0.07]

// B 通道取值范围
[7/255, 14/255] ≈ [0.03, 0.05]

我们进一步扩展片元着色器代码,判断像素的 R 和 B 通道。鉴于往往前景色和绿幕背景色差别较大,所以边界的计算无需非常精确,例如只判断这两个通道分量值是否小于 0.08,甚至 G 通道只需判断分量值是否大于 0.3 即可:

js 复制代码
    if (color.r < 0.08 && color.b < 0.08 && color.g > 0.3) {
      discard;
    }

执行效果如下:

细看会发现在个别帧里,依旧会出现一些预料之外的绿线(角色脚下位置)。

我们使用截图工具截取问题帧,对未被抠除的绿色像素进行取色,会发现其 R、B 两通道的值,超过了我们前面对背景渐变取色后设定的取值范围:

这是由于该动画在导出帧图片后,我使用了工具对每帧图片都做了压缩处理(来减小图片文件体积),进而出现部分像素的色彩失真。

针对此问题,我们尝试进一步增大 R 跟 B 俩通道的边界阈值(从 0.08 增大到 0.15):

js 复制代码
    if (color.r < 0.15 && color.b < 0.15 && color.g > 0.3) {
      discard;
    }

再执行动画时,杂点均被清除:

💡 本文提供的是一个比较理想化的案例,如果背景色和前景色有很多相近的色值,则需要做不少额外处理(例如判断顶点坐标、使用蒙版,或者借用工具更换背景色等)。

最后在底部添加一个自定义的背景图节点,便最终实现了电影绿幕换底的后期能力:

相关推荐
成长ing1213820 分钟前
cocos creator 3.x shader 流光
前端·cocos creator
VaJoy17 小时前
Cocos Creator Shader 入门 ⑾ —— 光照跟随
cocos creator
成长ing121382 天前
闪白效果
前端·cocos creator
冷水金枪鱼3 天前
Light2D光照系统(基于CocosCreater引擎3.x/2.x)
cocos creator
VaJoy5 天前
Cocos Creator Shader 入门 ⑽ —— 拖尾效果的实现
cocos creator
VaJoy15 天前
Cocos Creator Shader 入门 ⑼ —— 溶解动画
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator