前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【前端er入门Shader系列】05—在cocos中使用shader实现简单特效

【前端er入门Shader系列】05—在cocos中使用shader实现简单特效

原创
作者头像
CS逍遥剑仙
修改2025-02-06 17:01:05
修改2025-02-06 17:01:05
2270
举报
文章被收录于专栏:禅林阆苑禅林阆苑

【前端er入门Shader系列】05—在cocos中使用shader实现简单特效

前面在浏览器环境中基于 WebGL 的 GLSL 代码编写顶点(Vertex)和片元(Fragment)的 Shader 程序,在游戏引擎中,为了适配工业化制作流,提升着色器片段的易用性,往往会有一些额外的处理,本章将介绍在 cocos creator 中编写 Shader 程序,可以参考官方文档 https://docs.cocos.com/creator/3.8/manual/zh/shader/

1. Cocos Shader 的代码结构

在游戏引擎中,为了适配工业化制作流,提升着色器片段的易用性,以及更灵活地复用 GLSL 代码,封装的 Shader 中除了包含 GLSL 代码,还会包含参数配置信息和渲染状态开关等,方便开发者在引擎中可视化配置 Shader 参数以及从外部程序传入参数动态渲染。因此引擎的 Shader 为了区分普通 Shader,一般会有个别名,如 cocos 和 Unity3D 中称为 Effect,也有些引擎叫 FX。

Cocos Effect 的基本结构如下:

代码语言:glsl
复制
CCEffect %{
  techniques:
  - passes:
    - vert: vs:vert # 指向 CCProgram unlit-vs 的 vert 入口函数
      frag: fs:frag
}%

CCProgram vs %{
  //...
  vec4 vert () {
    //
  }
}%

CCProgram fs %{
  //...
  vec4 frag () {
    //
  }
}%

Cocos Effect 是 cocos creator 中一种基于 YAML 和 GLSL 的单源码嵌入式领域特定语言,包含 YAML 和 GLSL 两部分:

  • YAML 声明流程控制清单

一个 YAML 可包含一套或多套 Technique 渲染方案,但每个 effect 同时只会激活一套 Technique,每套 Technique 方案包含一个或多个 Pass 融合,每个 Pass 就是一次 GPU 绘制,一般包括一对顶点着色器和片元着色器,有一些复杂效果需要使用 Paas 多次渲染同一个物体才能实现,大部分情况下只需要一个 Pass 即可,YAML 语法参考,而 Pass 中的各类参数可以参考 Pass参数

  • GLSL 声明着色片段

由 CCProgram 包裹的基于 GLSL 300es 格式的着色器 (shader) 片段,提供给 Pass 引用

在实际的复杂 Effect 开发中,一般会直接复制全局的 builtin-standard 进行修改,而非新建,这样能大大提高正确率。

关于 Effect 的绘制执行,需要注意,在前面章节中会编写一堆 WebGL 的绘制命令,而在 cocos 中创建 Canvas 节点时默认会创建一个 Camera 节点来自动调用绘制指令,其中 Camera 组件的 ClearFlags / ClearColor / Rect 属性分别对于 WebGL 中的 gl.viewport / gl.clear / gl.clearColor,其中 ClearFlags 的 SOLID_COLOR 模式会每帧清除屏幕内容,ClearColor 会在清屏后填充指定颜色,Rect 定义了屏幕空间视口,xy 值限制 -1~1,wh 值限制 0~1。

2. Cocos Shader 渲染无光照纯色球体

Cocos Effect 所编写的 Shader 须结合材质 material 使用,material 可以与光交互,可以为渲染器提供贴图纹理、光照算法等数据集。material 材质资源可以挂载到模型对象上使用,可以有多个 technique,但在运行时有且仅能使用一个。

接下来在 cocos 中使用 Cocos Effect 渲染一个无光照纯色球体。首先在 cocos 中创建 MaterialCtrl.effect 如下:

代码语言:glsl
复制
CCEffect %{
  techniques:
  - name: opaque # technique名称
    passes:
    - vert: unlit-vs:vert # 顶点着色器
      frag: unlit-fs:frag # 片段着色器
      properties: &props # 面板参数
        a_color: { value: [1.0, 1.0, 1.0, 1.0], editor: { type: color } }
        colorScale: { value: [1.0, 1.0, 1.0], target: colorScaleAndCutoff.xyz }
      migrations: &migs
        properties:
}%

CCProgram unlit-vs %{
  // 定义默认精度
  precision highp float;
  #include <cc-global>
  in vec3 a_position;
  vec4 vert () {
    // 获取顶点位置信息 本地坐标系
    vec4 pos = vec4(a_position, 1);
    // 本地空间 -> 世界空间 -> 视图空间 -> 投影(裁剪坐标)
    pos = cc_matViewProj * pos;
    return pos;
  }
}%

CCProgram unlit-fs %{
  precision highp float;
  #include <output>
  // Cocos Shader要求,常量需要定义在uniform-block常量块(uniform-block)中
  uniform Constant {
    vec4 a_color;
    vec4 colorScaleAndCutoff; // 颜色调节参数,rgb为调节因子
  };
  vec4 frag () {
    vec4 o = a_color;
    o.rgb *= colorScaleAndCutoff.rgb;
    return o;
  }
}%

接着创建球体节点 Sphere,再创建材质 MaterialCtrl.mtl 并绑定 MaterialCtrl.effect,即可在编辑器中看到渲染结果。

properties: &props 用于定义Inspector面板上可操作的参数,可以定义向量、颜色、贴图、整数,支持在面板中调整参数并实时预览:

若需要在运行过程中修改参数,可以在节点上绑定脚本 MaterialCtrl.ts 如下:

代码语言:typescript
复制
// 应用于 Sphere
import { _decorator, Component, MeshRenderer, Color, Vec4 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('MaterialCtrl')
export class MaterialCtrl extends Component {
  start() {
    const target = this.node.getComponent(MeshRenderer);
    const color = new Vec4(0.6, 0.6, 1, 1.0); // 不使用Color,因为Color的颜色分量 [0,255]
    target.sharedMaterial.setProperty('a_color', color);
    const colorScaleAndCutoff = new Vec4(1.0, 1.0, 1.0, 1.0);
    target.sharedMaterial.setProperty('colorScaleAndCutoff', colorScaleAndCutoff);
  }
}

3. Cocos Effect 实现纹理混合

在游戏开发中经常会需要在已有模型中融合一些特效贴图的效果,如下图所示,可以通过增加一个贴图参数,如 detailTexture,再配合一些简单的运算轻松实现,可以参考 PS 中的图层的混合参数(Blending)。

代码语言:glsl
复制
CCEffect %{
  techniques:
  - name: opaque
    passes:
    - vert: legacy/main-functions/general-vs:vert # builtin header
      frag: unlit-fs:frag
      properties: &props
        mainTexture: { value: white }
        detailTexture: { value: black }
        method: { value: 0 }
        mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
  - name: transparent
    passes:
    - vert: legacy/main-functions/general-vs:vert # builtin header
      frag: unlit-fs:frag
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
          blendSrcAlpha: src_alpha
          blendDstAlpha: one_minus_src_alpha
      properties: *props
}%

CCProgram unlit-fs %{
  precision highp float;
  #include <legacy/output>
  #include <legacy/fog-fs>

  in vec2 v_uv;
  in vec3 v_position;

  uniform sampler2D mainTexture;
  uniform sampler2D detailTexture;

  uniform Constant {
    vec4 mainColor;
    int method; 
  };

  vec4 frag () {
    vec4 col = mainColor * texture(mainTexture, v_uv);
    vec4 detailColor = texture(detailTexture,v_uv);

    if(method == 0){
      //直接相加
      col.rgb += detailColor.rgb;
    }
    else if(method == 1){
      //直接相乘
      col.rgb *= detailColor.rgb;
    }
    else if(method == 2){
      //插值混合
      col.rgb = mix(col.rgb,detailColor.rgb,0.618);
    }
    else if(method == 3){
      //加权求和
      col.rgb = col.rgb * 0.2 + detailColor.rgb * 1.2;
    }
    else if(method == 4){
      //灰度混合
      float gray = detailColor.r * 0.299 + detailColor.g * 0.587 + detailColor.b * 0.114;
      col.rgb = mix(col.rgb, detailColor.rgb * 1.5, gray);
    }
    else if(method == 5){
      //相减
      col.rgb = col.rgb - detailColor.rgb;
    }
    CC_APPLY_FOG(col, v_position);
    return CCFragOutput(col);
  }
}%

4. Cocos Effect 使用噪声图实现溶解效果

使用上面的知识,已经可以尝试实现游戏特效中最常见的溶解特效了!

在游戏开发中,会经常使用噪声图实现一些特殊的效果,从程序的角度来看,黑白噪声图可以向程序中引入一些随机变量。借助一张噪声贴图采样得到的随机变量和一个溶解阈值参数即可控制溶解效果。

代码语言:glsl
复制
CCEffect %{
  techniques:
  - name: test # technique名字
    passes:
    - vert: sprite-vs:vert
      frag: sprite-fs:frag
      depthStencilState: # 深度&模板检测状态
        depthTest: false
        depthWrite: false
      blendState: # 图像混合测试状态
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
          blendDstAlpha: one_minus_src_alpha
      rasterizerState: # 光栅化状态,目前仅支持"面剔除"选项
        cullMode: none
      properties: # Pass面板上的配置参数
        u_dissolveMap: { value: white, editor: { tooltip: '噪声贴图' } }
        dissolveThreshold: { value: 0.5, editor: { range: [0, 1, 0.01], slide: true, tooltip: '溶解阈值' } }
}%

CCProgram sprite-vs %{
  precision highp float;
  #include <cc-global>

  in vec3 a_position; // GLSL es300 中使用 in / out 关键字代替 attribute / varing,但旧关键字仍然兼容
  in vec2 a_texCoord;
  in vec4 a_color;

  out vec4 color;
  out vec2 uv0;

  vec4 vert () {
    vec4 pos = vec4(a_position, 1);
    pos = cc_matViewProj * pos;
    uv0 = a_texCoord;
    color = a_color;
    return pos;
  }
}%

CCProgram sprite-fs %{
  precision highp float;

  in vec4 color;
  uniform Dissolve{ // Cocos Shader 规定,所有非 sampler 类型的 uniform 都应以 UBO(Uniform Buffer Object/Uniform Block)形式声明
    float dissolveThreshold; // 溶解阈值[0, 1]
  };

  #if USE_TEXTURE
    in vec2 uv0;
    uniform sampler2D u_dissolveMap; // 噪声图纹理
    #pragma builtin(local)
    layout(set = 2, binding = 10) uniform sampler2D cc_spriteTexture; // 新版内存分配
  #endif

  vec4 frag () {
    vec4 o = vec4(1, 1, 1, 1);
    float value = 1.0;

    #if USE_TEXTURE
      vec4 dissolveMap = texture(u_dissolveMap, uv0);
      value *= dissolveMap.r; // 取贴图 r 分量
    #endif

    if (value < dissolveThreshold) {
      discard; // 小于阈值的片段丢弃,形成溶解
    }

    #if USE_TEXTURE
      o *= texture(cc_spriteTexture, uv0); // 与原纹理混合
    #endif

    o *= color;
    if (value < dissolveThreshold + 0.05) {
      o = vec4(0.9, 0.6, 0.3, o.a); // 溶解的边缘设置边缘过度色
    }

    return o;
  }
}%

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 【前端er入门Shader系列】05—在cocos中使用shader实现简单特效
    • 1. Cocos Shader 的代码结构
    • 2. Cocos Shader 渲染无光照纯色球体
    • 3. Cocos Effect 实现纹理混合
    • 4. Cocos Effect 使用噪声图实现溶解效果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档