<十五>入门CocosShader之改造精灵Shader

前言

在CocosEffect中内置有一个shader--builtin-sprite,它在基础绘图 shader 的基础上增加了纹理的处理。

Sprite Shader

在资源管理器中创建一个sprite.effect,拷贝 builtin-sprite 内所有的内容

// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
CCEffect %{
  techniques:
  - 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:
        alphaThreshold: { value: 0.5 } # 定义属性透明度阈值,在启用透明测试的时候,透明度低于阈值内值的片段会被丢弃
        # 这里对应的uniform是在sprite-fs的alpha-test中定义的
}%

CCProgram sprite-vs %{
  precision highp float;
  #include <builtin/uniforms/cc-global>
  #if USE_LOCAL
    #include <builtin/uniforms/cc-local>
  #endif
  #if SAMPLE_FROM_RT
    #include <common/common-define>
  #endif
  in vec3 a_position;
  in vec2 a_texCoord;
  in vec4 a_color;

  out vec4 color;
  out vec2 uv0;

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

    //因为2D大部分图像都是用来做UI的,UI的变化频率很大
    //假设所有的UI都是动态的,因此需要每帧检查对象是否需要重新计算
    //这时就会在CPU进行模型空间到世界空间到转换,但这种做法不一定适合所有的项目
    //可能有些项目的UI比较固定变化不大,顶点数据直接提供模型空间即可,后续的坐标转换都在GPU上进行,可以减少CPU的开销
    //因此提供了USE_LOCAL这样一个宏定义,让用户自己决定2D顶点数据的计算方式

    #if USE_LOCAL 
      pos = cc_matWorld * pos;
    #endif

    //这里的宏是处理像素对齐的,比如像素风游戏每个像素的边缘都很是清晰分明的,在进行多次坐标转换时精度差值也会变大
    //可能会出现像素抖动的现象
    //这里的像素对齐就是对观察空间的像素做取整处理
    //然后再让它转换到裁剪空间,可以有效避免像素抖动

    #if USE_PIXEL_ALIGNMENT
      pos = cc_matView * pos;
      pos.xyz = floor(pos.xyz);
      pos = cc_matProj * pos;
    #else
      pos = cc_matViewProj * pos;
    #endif

    uv0 = a_texCoord;
    #if SAMPLE_FROM_RT
      CC_HANDLE_RT_SAMPLE_FLIP(uv0);
    #endif
    color = a_color;

    return pos;
  }
}%

CCProgram sprite-fs %{
  precision highp float;
  #include <builtin/internal/embedded-alpha>
  #include <builtin/internal/alpha-test>

  in vec4 color;

  #if USE_TEXTURE
    in vec2 uv0;
    //顶点属性的内存分配
    #pragma builtin(local)//申请local内存
    layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;//layout代表绑定到local内存指定位置
  #endif

  vec4 frag () {
    vec4 o = vec4(1, 1, 1, 1);

    #if USE_TEXTURE
      //o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);
      o *= texture(cc_spriteTexture,uv0);//直接做纹理处理即可
      #if IS_GRAY //引擎设置的灰度计算
        float gray  = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;
        o.r = o.g = o.b = gray;
      #endif
    #endif

    o *= color;
    ALPHA_TEST(o);//在启用alpha测试时,会调用,如果小于阈值会被丢弃,以达到该区域呈现透明的效果
    return o;
  }
}%

创建一个sprite材质并应用sprite.effect.
在场景中创建一个精灵并挂载sprite材质
可以看到精灵显示变成了纯白色,这是因为默认的宏是禁用状态,在材质属性中勾选启用纹理即可。
上述是在编辑器环境下对材质的赋值,很多情况下希望在运行时动态修改。这就需要在代码中去调用相应的API。

import { _decorator, Component, EffectAsset, Graphics, Material, Node, Sprite } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Draw')
export class Draw extends Component {
    @property(EffectAsset)
    effect: EffectAsset = null;//声明一个属性去关联EffectAsset
    start() {
        // //获取基础绘制组件Graphics
        // const g = this.getComponent(Graphics);
        // //绘制并填充矩形
        // g.fillRect(0, 0, 400, 300);
        
        //new一个材质
        const mat = new Material();
        //初始化
        mat.initialize({ effectAsset: this.effect ,defines: {USE_TEXTURE:true}});

        //添加材质到sprite
        const spComp = this.node.getComponent(Sprite);
        spComp.customMaterial = mat;
        
    }

}


补充

include 机制:
类似 C/C++ 的头文件 include 机制,可以在任意 shader 代码(CCProgram 块或独立的头文件)中引入其他代码片段:

// 内置头文件引入
#include <cc-global> 
// 自定义头文件引入,采用的是:
// 1. 相对于项目目录 assets/chunks 文件夹位置(即使 assets 下没有 chunks 文件夹,也会假设有一个 chunks 文件夹,并相对于这个文件夹查找)
// 2. 相对于当前文件路径查找
// 前者优先。
// 更多有关绝对路径或同文件引用,请查看:https://docs.cocos.com/creator/3.2/manual/zh/material-system/effect-syntax.html#include-机制
#include "../headers/my-shading-algorithm.chunk" 

Cocos Creator 所有的内置 chunk 都在“资源管理器” internal 下的 chunks 文件夹里,引用内置的 chunk,直接 "#include" 即可,不用带后缀。

预处理宏定义:
在任意 shader 代码(CCProgram 块或独立的头文件)中,都可以通过 大写字母 + _ 的方式定义宏。每一个宏都是一个开关,默认是关闭状态,可以在编辑器或者运行时开启,需要通过 if 语句进行判断。如果不是 GLSL 内置宏,请不要使用 ifdef 或者 if defined 做判断,否则始终为 true。

// USE_TEXTURE 就是一个宏定义,所有的宏定义最终都会被序列化到“属性检查器”面板上,方便随时启/禁用。
// 如果宏定义处于禁用中, #if 到 #endif 内的代码不会执行
#if USE_TEXTURE
  in vec2 uv0;
  #pragma builtin(local)
  layout(set = 2, binding = 10) uniform sampler2D cc_spriteTexture;
#endif

// 也可以通过代码对宏进行启/禁用
const meshRenderer = this.getComponent(MeshRenderer);
const mat = meshRenderer.material;
mat.recompileShaders({ USE_TEXTURE: true }); 

Cocos Effect 所编写的 Shader 是无法单独使用的,必须要结合材质使用。材质是一个具备与光交互,供渲染器读取的数据集,可以为渲染器提供贴图纹理、光照算法等,可以直接将材质资源挂载到模型对象身上。材质可以持有多个 technique,但是在运行时有且仅能使用一个。材质包含的可配置参数如下:

effectAsset 或 effectName:effect 资源引用,指定使用哪个 EffectAsset 所描述的流程进行渲染。(必备)
technique:指定使用 EffectAsset 中的第几个 technique,默认为第 0 个。
defines:宏定义列表,指定开启哪些宏定义,默认全部关闭。
states:管线状态重载列表,指定对渲染管线状态(深度模板透明混合等)有哪些重载,默认与 effect 声明一致。

const mat = new Material();
mat.initialize({
  effectName: 'pipeline/skybox',
  defines: {
    USE_RGBE_CUBEMAP: true
  }
});
posted @ 2025-02-20 17:13  EricShx  阅读(48)  评论(0)    收藏  举报