Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度

💡 本系列文章收录于个人专栏 ShaderMyHead:juejin.cn/column/7505...

在上篇文章我们编写了最简单的一个着色器 Effect 文件,来为一个节点染上完全不透明的绿色:

c 复制代码
  vec4 frag() {
    // 在片元着色器里固定返回绿色的 RGBA 色值,
    // 因为 A 值为 1.0,故完全不透明
    return vec4(0.0, 1.0, 0.0, 1.0);
  }

如果只想给节点设置 50% 的透明度,我们可能会觉得,把片元着色器入口函数返回的 Alpha 值更改为 0.5 即可:

c 复制代码
  vec4 frag() {
    // A 值修改为 0.5
    return vec4(0.0, 1.0, 0.0, 0.5);
  }

但会发现该节点的透明度并没有发生任何变化(染色节点依旧是完全不透明的):

这是因为 GPU 从性能上考虑,会默认使用 opaque 的不透明方案来进行渲染(即完全不考虑 Alpha 值)。

一、开启 Blending 混合模式

在 Cocos Creator 的 Effect 里,要通知 GPU 使透明度生效,必须显式开启 Blending 混合模式 。其开启方式很简单,在 passes 中补充混合状态相关配置即可:

yaml 复制代码
      blendState:       # 混合状态配置
        targets:
        - blend: true                     # 开启混合模式
          blendSrc: src_alpha             # 源颜色(片元着色器输出的颜色)混合因子使用源颜色的 Alpha 值
          blendDst: one_minus_src_alpha   # 目标颜色(帧缓冲中的颜色)混合因子使用 (1 - 源颜色的Alpha值)

其中 blendSrc 指定了混合源(即片元着色器输出的新色值)的 RGB 混合因子,这里我们设置为 src_alpha,表示直接使用片元着色器输出色值的 Alpha 值(即 0.5)作为混合因子。

blendDst 指定了混合目标(缓存中的旧色值,可以理解为前一帧的色值)的 RGB 混合因子,这里我们设置为 one_minus_src_alpha,表示 1 减去片元着色器输出的 Alpha 值之后的计算结果(即 1 - 0.5 = 0.5),来作为混合因子。

根据上述配置,GPU 会把混合源的 RGB 分量都乘以 0.5,把混合目标的 RGB 分量也都乘以 0.5,再把两个计算结果加起来得到最终要渲染的 RGB 值:

js 复制代码
// [ 源颜色 × src_alpha ] + [ 目标颜色 × (1 - src_alpha) ]
R = (0.5 * 源颜色R) + (0.5 * 目标颜色R);
G = (0.5 * 源颜色G) + (0.5 * 目标颜色G);
B = (0.5 * 源颜色B) + (0.5 * 目标颜色B);

此时节点已能按照预期渲染出 50% 透明度的效果:

💡 从该案例可以获悉,GPU 通过颜色加权混合来模拟透明效果,Alpha 值仅作为混合权重参考。

另外 blendSrcblendDst 的可选值清单如下:

名称 含义 说明 / 使用场景示例
one 常数 1 保留源或目标颜色全部值(不变)
zero 常数 0 完全舍弃颜色(结果为黑或透明)
src_alpha 源颜色的 Alpha 值 常用于半透明混合:src × alpha + dst × (1 - alpha)
one_minus_src_alpha 1 - 源颜色的 alpha 值 常用于目标颜色的反向衰减
dst_alpha 目标颜色的 Alpha 值 适用于反向混合、遮罩等高级透明处理
one_minus_dst_alpha 1 - 目标颜色的 alpha 值 用于源颜色按目标透明度做加权
src_color 源颜色的 RGB 分量 可实现基于自身颜色亮度混合,粒子特效中偶尔使用
one_minus_src_color 1 - 源颜色的 RGB 分量 反色混合,有时用于闪烁或特效
dst_color 目标颜色的 RGB 分量 粒子、特殊光照叠加中可能用到
one_minus_dst_color 1 - 目标颜色的 RGB 分量 偏暗调混合用法
src_alpha_saturate min(src_alpha, 1 - dst_alpha),仅用于 blendSrc 可防止过度叠加,适合绘制阴影或遮罩边缘
constant_color 固定颜色(需另行设置) 较少使用,需在代码中设定 gl.blendColor()
one_minus_constant_color 1 - constant_color 同上,反色混合
constant_alpha 固定 Alpha 值(需另行设置) 同样依赖 blendColor(),用于统一透明值控制
one_minus_constant_alpha 1 - constant_alpha constant_alpha 搭配使用

为了验证 blendSrcblendDst 仅用作源颜色和目标颜色的混合(而非直接修改节点的透明度),我们尝试把混合配置修改为:

yaml 复制代码
      blendState: # 添加混合状态
        targets:
        - blend: true
          blendSrc: zero             # 源颜色(片元着色器输出的颜色)混合因子为 0
          blendDst: src_alpha        # 目标颜色(缓存中的旧颜色)混合因子使用源颜色的 Alpha 值

执行结果如下:

此时染色节点混合后的 RGB 为:

js 复制代码
R = (0 * 源颜色R) + (src_alpha * 目标颜色R) = 0.5 * 目标颜色R;
G = (0 * 源颜色G) + (src_alpha * 目标颜色G) = 0.5 * 目标颜色G;
B = (0 * 源颜色B) + (src_alpha * 目标颜色B) = 0.5 * 目标颜色B;

相当于把目标颜色(底部的树叶图腾)的 RGB 分量都降低了 50%,表现为树叶图腾节点被染色节点覆盖的区域,其颜色强度减半。

二、关闭深度缓冲区

当我们克隆上述的半透明染色节点,且将它们叠加到一起时,会发现交叠部分的绿色透明度没有发生叠加:

这是在 GPU 的深度检测流程中出现的问题 ------ 每次光栅化之后(片元着色器之前),GPU 都会通过深度缓存中获取信息,来判断当前像素是否被其它已渲染的像素挡住了?如果是的话会直接丢弃这个像素,进而节省大量的片元着色器执行时间。

上图两个节点交叠的部分,正是被 GPU 视为遮挡的部分,导致该区域更晚处理的像素被丢弃。

对于完全不透明的物体而言,GPU 的这块操作是无感且高效的,但对于带透明度的物体就会导致视觉上的错误。

为了处理此问题,对于携带透明度的物体需要手动加上 depthStencilState 配置,关闭像素在光栅化阶段把深度信息写入缓存的能力,绕过 GPU 后续在该物体区域的深度检测:

yaml 复制代码
    - vert: vs:vert
      frag: fs:frag
      blendState: 
        targets:
        - blend: true
          blendSrc: src_alpha        
          blendDst: one_minus_src_alpha   
      depthStencilState:      # 新增 depthStencilState 配置
        depthWrite: false     # 关闭深度缓存写入

💡 3D 游戏中,物体若是不透明 的,保持 depthWrite: true 会获得更佳性能。

修改后的效果如下:

可以看到两个染色节点中间交叠的部分显示出了更深的绿色,可以正常呈现出节点的层次感了。

💡 透明混合不是线性相加,而是按比例加权(由 blendSrcblendDst 决定),因此两个 50% 透明度的绿色节点交叠部分不会是 100% 不透明。

三、2D 游戏关闭深度测试

GPU 的深度检测(Depth Test)是基于 Z 轴数值的检测。

常规纯 2D 游戏的前后关系主要由层级决定(SiblingIndex),而不使用 Z 值(或默认所有节点都在一个深度平面,Z 值都是 0)。

因此对于纯 2D 的游戏而言,Effect 中通过配置 depthTest: false 禁用深度检测功能可以避免恼人的层级渲染问题,也可以减少少量的 GPU 计算功耗:

yaml 复制代码
      # 纯2D游戏使用
      depthStencilState:
          depthTest: false        # 【新增】关闭深度测试
          depthWrite: false       # 关闭深度缓存写入

💡 关闭深度测试后,不写 depthWrite: false 虽然也不会有渲染上的视觉问题,但 GPU 仍然会将深度值(NDC 的 Z 分量)写入到深度缓冲区。显示地关闭深度缓存写入,可以额外提升些许性能。

四、遗留问题

最后其实还存在一个问题 ------ 被染色的节点如果带有纹理(Sprite Frame),染色后我们只会看到单一的绿色,其原本的纹理图案完全丢失了:

我们将在下篇文章通过纹理采样来处理此问题。

相关推荐
VaJoy3 天前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy4 天前
Cocos Creator Shader —— 附录
cocos creator
成长ing121385 天前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk22 天前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121382 个月前
点击音效系统
前端·cocos creator
blakeyi2 个月前
vscode保存自动刷新cocos creator编辑器
ide·vscode·cocos creator·热更新
烧仙草奶茶2 个月前
【cocos creator 3.x】3Dui创建,模型遮挡ui效果
ui·3d·cocos creator·cocos3d
糖墨夕3 个月前
【1】Coco2d creator资源管理注意事项 - meta 文件
前端·cocos creator·cocos2d-x
Setsuna_F_Seiei3 个月前
前端切图仔的一次不务正业游戏开发之旅
前端·游戏·cocos creator