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;
    }

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

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

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

相关推荐
VaJoy2 天前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy3 天前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator
VaJoy6 天前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy6 天前
Cocos Creator Shader —— 附录
cocos creator
成长ing121387 天前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk24 天前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121382 个月前
点击音效系统
前端·cocos creator
blakeyi2 个月前
vscode保存自动刷新cocos creator编辑器
ide·vscode·cocos creator·热更新
烧仙草奶茶3 个月前
【cocos creator 3.x】3Dui创建,模型遮挡ui效果
ui·3d·cocos creator·cocos3d