<七>Cocos Shader语法

Cocos Creator 中的着色器(Cocos Shader ,文件扩展名为 *.effect),是一种基于 YAML 和 GLSL 的单源码嵌入式领域特定语言(single-source embedded domain-specific language),YAML 部分声明流程控制清单,GLSL 部分声明实际的 Shader 片段,这两部分内容相互补充,共同构成了一个完整的渲染流程描述。

备注:如果使用VSCode编辑器编写 Cocos Shader,可在扩展商店中安装Cocos Effect 扩展,提供编写时的语法高亮提示。
image

1 Cocos Shader语法

1.1 语法结构 | 文件框架

Cocos Shader 通常由两个部分组成:

  • CCEffect:用于声明渲染技术(Technique)、渲染过程(Pass)、渲染状态、材质参数等属性。
  • CCProgram:用于声明顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)代码片段。
    这里以引擎内置的无光照着色器 builtin-unlit.effect 为例解析CocosShader的语法框架

1.1.1 创建Shader文件

内置的着色器是不可编辑。如果想要其变成可编辑的,就需要Copy一份到assets的子目录下。
双击打开Copy的着色器:

// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
CCEffect %{
  techniques:
  - name: opaque
    passes:
    - vert: unlit-vs:vert
      frag: unlit-fs:frag
      properties: &props
        mainTexture:    { value: grey }
        tilingOffset:   { value: [1, 1, 0, 0] }
        mainColor:      { value: [1, 1, 1, 1], linear: true, editor: { type: color } }
        colorScale:     { value: [1, 1, 1], target: colorScaleAndCutoff.xyz }
        alphaThreshold: { value: 0.5, target: colorScaleAndCutoff.w, editor: { parent: USE_ALPHA_TEST } }
        color:          { target: mainColor, linear: true, editor: { visible: false } } # backward compability
      migrations: &migs
        properties:
          mainColor:    { formerlySerializedAs: color }
  - name: transparent
    passes:
    - vert: unlit-vs:vert
      frag: unlit-fs:frag
      depthStencilState: &d1
        depthTest: true
        depthWrite: false
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
          blendDstAlpha: one_minus_src_alpha
      properties: *props
      migrations: *migs
  - name: add
    passes:
    - vert: unlit-vs:vert
      frag: unlit-fs:frag
      rasterizerState: &r1 { cullMode: none }
      depthStencilState: *d1
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one
          blendSrcAlpha: src_alpha
          blendDstAlpha: one
      properties: *props
      migrations: *migs
  - name: alpha-blend
    passes:
    - vert: unlit-vs:vert
      frag: unlit-fs:frag
      rasterizerState: *r1
      depthStencilState: *d1
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
          blendSrcAlpha: src_alpha
          blendDstAlpha: one_minus_src_alpha
      properties: *props
      migrations: *migs
}%

CCProgram unlit-vs %{
  precision highp float;
  #include <legacy/input>
  #include <builtin/uniforms/cc-global>
  #include <legacy/decode-base>
  #include <legacy/local-batch>
  #include <legacy/input>
  #include <legacy/fog-vs>

  #if USE_VERTEX_COLOR
    in lowp vec4 a_color;
    out lowp vec4 v_color;
  #endif

  #if USE_TEXTURE
    out vec2 v_uv;
    uniform TexCoords {
      vec4 tilingOffset;
    };
  #endif

  vec4 vert () {
    vec4 position;
    CCVertInput(position);

    mat4 matWorld;
    CCGetWorldMatrix(matWorld);

    #if USE_TEXTURE
      v_uv = a_texCoord * tilingOffset.xy + tilingOffset.zw;
      #if SAMPLE_FROM_RT
        CC_HANDLE_RT_SAMPLE_FLIP(v_uv);
      #endif
    #endif

    #if USE_VERTEX_COLOR
      v_color = a_color;
    #endif

    CC_TRANSFER_FOG(matWorld * position);
    return cc_matProj * (cc_matView * matWorld) * position;
  }
}%

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

  #if USE_ALPHA_TEST
    #pragma define-meta ALPHA_TEST_CHANNEL options([a, r, g, b])
  #endif

  #if USE_TEXTURE
    in vec2 v_uv;
    uniform sampler2D mainTexture;
  #endif

  uniform Constant {
    vec4 mainColor;
    vec4 colorScaleAndCutoff;
  };

  #if USE_VERTEX_COLOR
    in lowp vec4 v_color;
  #endif

  vec4 frag () {
    vec4 o = mainColor;
    o.rgb *= colorScaleAndCutoff.xyz;

    #if USE_VERTEX_COLOR
      o.rgb *= SRGBToLinear(v_color.rgb);//use linear
      o.a *= v_color.a;
    #endif

    #if USE_TEXTURE
      vec4 texColor = texture(mainTexture, v_uv);
      texColor.rgb = SRGBToLinear(texColor.rgb);
      o *= texColor;
    #endif

    #if USE_ALPHA_TEST
      if (o.ALPHA_TEST_CHANNEL < colorScaleAndCutoff.w) discard;
    #endif

    CC_APPLY_FOG(o);
    return CCFragOutput(o);
  }
}%

代码块折叠一下,可以看到有三部分内容:
image

实际对应的就是上面所说的CCEffect和CCProgram(顶点着色器和片元着色器)

1.1.2 CCEffect

在语法上:

CCEffect %{
 # 符合YAML语法结构的渲染流程描述信息
}%

在 Cocos Shader 中,由 CCEffect 包裹的部分是由 YAML 语法 声明的渲染流程相关的描述信息。

  • techniques
    image
    CCEffect包含了一个对象techniques,对象的值是一个数组。在Cocos Shader中,techniques是一个渲染技术数组,一个 CCEffect 中支持定义多个渲染技术,但在实际渲染时,同一个材质实例只能应用其中一个技术。
    以该Shader为例,其中包含了 4 个技术:
    • opaque
    • transparent
    • add
    • alpha-blend
      opaque 专门用于渲染不透明物体的渲染技术,transparent,add,alpha-blend 则用来渲染半透明物体。
      语法:
techniques:
- name: 渲染技术名
   passes:
   -xxx
   -xxx
   ...
- name: 渲染技术名
...
  • 渲染技术
    这里以opaque为例,展开代码块如下
  - name: opaque
    passes:
    - vert: unlit-vs:vert
      frag: unlit-fs:frag
      properties: &props
        mainTexture:    { value: grey }
        tilingOffset:   { value: [1, 1, 0, 0] }
        mainColor:      { value: [1, 1, 1, 1], linear: true, editor: { type: color } }
        colorScale:     { value: [1, 1, 1], target: colorScaleAndCutoff.xyz }
        alphaThreshold: { value: 0.5, target: colorScaleAndCutoff.w, editor: { parent: USE_ALPHA_TEST } }
        color:          { target: mainColor, linear: true, editor: { visible: false } } # backward compability
      migrations: &migs
        properties:
          mainColor:    { formerlySerializedAs: color }

每个渲染技术(technique)都包含了名称(name)和渲染过程(passes)
名称用于标记渲染技术的用途,渲染过程则定义了一个完整的渲染流程所需要的全部信息。
一个渲染技术可以包含多个渲染过程,渲染过程会按定义的先后顺序逐一执行
渲染过程(pass):
比对上述代码,一个渲染过程中,必须包含一个顶点着色器(Vertex Shader,VS)和一个片元着色器(Fragment Shader,FS),properties 和 migrations 都是可选配置参数项。

VS/FS 着色器需要指定使用的 CCProgram ,以及指定着色器的入口函数。 如果不指定入口函数,会默认使用 main。

一个渲染过程的语法结构如下:

- vert: CCProgram声明的顶点着色器名:顶点着色器的入口函数名
   frag: CCProgram声明的片元着色器名:片元着色器的入口函数名
   ...
...

每个渲染过程都只有 vert 和 frag 两个必填参数,分别用于声明当前渲染过程使用的顶点着色器和片元着色器,格式为 片段名: 入口函数名。

片段名可以是本文件中声明的 CCProgram 片段名,也可以是引擎提供的标准头文件。

需要注意:自定义着色器的代码中不应该使用 main 函数,Cocos Shader 在编译时会自动添加一个 main 函数并调用渲染过程的入口函数(例如 vert 或 frag),main 函数会将入口函数的返回值作为当前 Shader 的输出(例如 gl_Position 或 gl_FragColor)。

示例代码中一个pass的元素还有一部分没有提到:

  • 渲染过程属性
    渲染过程属性部分是渲染过程中的可选配置参数,以properties为例:
    properties 用于将 Shader 中定义的 uniform 进行别名映射。这个映射可以是某个 uniform 的完整映射,也可以是具体某个分量的映射(使用 target 参数)
    渲染过程中的 properties 用于配置相关属性描述。通过它,可以定义一个 uniform修饰的全局变量 在面板上的显示方式
CCEffect %{
  techniques:
  - name: opaque # 定义一个不透明的渲染技术
    passes:
    - vert: vs: entry # 选择一个 CCProgram 声明的顶点着色器 ‘vs’,入口函数是 ‘entry’
      frag: fs: entry # 选择一个 CCProgram 声明的片元着色器 ‘fs’,入口函数是 ‘entry’
      properties:
        mainTexture: { value: grey } # 着色器中需要同步定义一个 ‘uniform mainTexture’,该属性可在编辑器的属性检查器中进行配置
        colorScale: { value: [1, 1, 1], target: colorScaleAndCutoff.xyz } # 基于 ‘target’ 属性配置机制,着色器中需要同步定义一个 ‘uniform colorScaleAndCutoff’,并选取它的 x、y、z 分量填充 ‘colorScale’ 设置的数据
      depthStencilState: # 配置深度测试、模板测试和写入状态
        depthTest: true
        depthWrite: true
    ...
  ...
}%

其他的可选配置可参考:https://docs.cocos.com/creator/3.8/manual/zh/shader/pass-parameter-list.html

1.1.3 CCProgram

在 Cocos Shader 中由 CCProgram 包裹的部分是由 GLSL 语法 声明的 Shader 片段
语法结构:

CCProgram shader-name %{
  <required: precision settings>
  <optional: include>  
  <optional: ubo>
  <optional: custom attribute>
  <optional: >
  vec4 entry(){
    // 需要返回一个 vec4 类型数据
  }
}%

1.1.4 预处理宏定义

通过预处理宏,可在 Cocos Shader 编译时控制代码分支和组合,以实现高效便捷的 Shader 代码管理。
详细的介绍参考:https://docs.cocos.com/creator/3.8/manual/zh/shader/macros.html

posted @ 2025-01-24 13:32  EricShx  阅读(228)  评论(0)    收藏  举报