<十六>入门CocosShader之实现贴图溶解效果

前言

如果想实现一个溶解效果,就需要标记哪些像素要显示,哪些像素要消失。比较好的办法是用一张噪声图来给每一个像素做标记,噪声图类似下图。由于噪声图只有黑白灰三种颜色,黑白灰非均匀分布,因此,可以利用黑透、白不透、灰半透的原理实现不规则溶解效果。
溶解效果最简单的制作通常需要两个参数:一个是噪声图,一个是控制过滤黑/白像素的阈值。

基础了解:
image
image

实现溶解效果

接上一篇中实现的sprite shader来实现溶解效果

  1. 添加实现溶解效果需要的两个参数:噪声图 和 控制过滤参数
  2. 处理溶解逻辑
// 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中定义的
        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 <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内存指定位置

    uniform sampler2D u_dissolveMap;// 熔岩形状的纹理;
  #endif

  //定义溶解阈值,注意在CocosCreator中所有的非sample的unifrom都应以block的形式声明,否则会报错
  uniform Dissolve{
    float dissolveThreshold;// 熔岩阀值[0, 1];
  };

  vec4 frag () {
    vec4 o = vec4(1, 1, 1, 1);
    //在主贴图之前处理溶解逻辑
    float value = 1.0;
    #if USE_TEXTURE
        vec4 dissolveMap = texture(u_dissolveMap, uv0); // 如果颜色的 r 分量小于阀值,将这个着色操作丢弃;
        value *= dissolveMap.r;//选取rgb任意一个通道
    #endif

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


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

    if (value < dissolveThreshold + 0.05) {
      o = vec4(0.9, 0.6, 0.3, o.a); // 溶解的边缘设置一个边缘过度色,让溶解效果更平滑一些
    }
    ALPHA_TEST(o);//在启用alpha测试时,会调用,如果小于阈值会被丢弃,以达到该区域呈现透明的效果
    return o;
  }
}%

3.测试运行
image

  1. 代码控制动态消融效果
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

    _value = 0
    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;
        this.node.once(Node.EventType.TOUCH_START, this.disApear,this);


    }

    disApear(){
        if(this._value >= 1)return;
        this._value += 0.01;
        const sprite = this.getComponent(Sprite);
        const mat = sprite.customMaterial;
        mat.setProperty('dissolveThreshold', this._value);
        this.scheduleOnce(()=>{
            this.disApear();
        },0.01);
    }

}


posted @ 2025-02-20 17:45  EricShx  阅读(66)  评论(0)    收藏  举报